summaryrefslogtreecommitdiff
path: root/netwerk
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk')
-rw-r--r--netwerk/base/ADivertableParentChannel.h45
-rw-r--r--netwerk/base/ARefBase.h32
-rw-r--r--netwerk/base/ArrayBufferInputStream.cpp120
-rw-r--r--netwerk/base/ArrayBufferInputStream.h38
-rw-r--r--netwerk/base/AutoClose.h77
-rw-r--r--netwerk/base/BackgroundFileSaver.cpp1273
-rw-r--r--netwerk/base/BackgroundFileSaver.h418
-rw-r--r--netwerk/base/CaptivePortalService.cpp366
-rw-r--r--netwerk/base/CaptivePortalService.h70
-rw-r--r--netwerk/base/ChannelDiverterChild.cpp27
-rw-r--r--netwerk/base/ChannelDiverterChild.h26
-rw-r--r--netwerk/base/ChannelDiverterParent.cpp75
-rw-r--r--netwerk/base/ChannelDiverterParent.h40
-rw-r--r--netwerk/base/Dashboard.cpp921
-rw-r--r--netwerk/base/Dashboard.h105
-rw-r--r--netwerk/base/DashboardTypes.h63
-rw-r--r--netwerk/base/EventTokenBucket.cpp462
-rw-r--r--netwerk/base/EventTokenBucket.h149
-rw-r--r--netwerk/base/LoadContextInfo.cpp181
-rw-r--r--netwerk/base/LoadContextInfo.h61
-rw-r--r--netwerk/base/LoadInfo.cpp925
-rw-r--r--netwerk/base/LoadInfo.h163
-rw-r--r--netwerk/base/LoadTainting.h43
-rw-r--r--netwerk/base/MemoryDownloader.cpp92
-rw-r--r--netwerk/base/MemoryDownloader.h65
-rw-r--r--netwerk/base/NetStatistics.h100
-rw-r--r--netwerk/base/NetUtil.jsm461
-rw-r--r--netwerk/base/NetworkActivityMonitor.cpp300
-rw-r--r--netwerk/base/NetworkActivityMonitor.h46
-rw-r--r--netwerk/base/NetworkInfoServiceCocoa.cpp104
-rw-r--r--netwerk/base/NetworkInfoServiceImpl.h18
-rw-r--r--netwerk/base/NetworkInfoServiceLinux.cpp104
-rw-r--r--netwerk/base/NetworkInfoServiceWindows.cpp60
-rw-r--r--netwerk/base/PollableEvent.cpp347
-rw-r--r--netwerk/base/PollableEvent.h38
-rw-r--r--netwerk/base/Predictor.cpp2550
-rw-r--r--netwerk/base/Predictor.h480
-rw-r--r--netwerk/base/PrivateBrowsingChannel.h132
-rw-r--r--netwerk/base/ProxyAutoConfig.cpp1015
-rw-r--r--netwerk/base/ProxyAutoConfig.h104
-rw-r--r--netwerk/base/RedirectChannelRegistrar.cpp87
-rw-r--r--netwerk/base/RedirectChannelRegistrar.h44
-rw-r--r--netwerk/base/ReferrerPolicy.h186
-rw-r--r--netwerk/base/RequestContextService.cpp217
-rw-r--r--netwerk/base/RequestContextService.h47
-rw-r--r--netwerk/base/ShutdownLayer.cpp80
-rw-r--r--netwerk/base/ShutdownLayer.h22
-rw-r--r--netwerk/base/SimpleBuffer.cpp95
-rw-r--r--netwerk/base/SimpleBuffer.h57
-rw-r--r--netwerk/base/StreamingProtocolService.cpp72
-rw-r--r--netwerk/base/StreamingProtocolService.h36
-rw-r--r--netwerk/base/TLSServerSocket.cpp515
-rw-r--r--netwerk/base/TLSServerSocket.h81
-rw-r--r--netwerk/base/ThrottleQueue.cpp392
-rw-r--r--netwerk/base/ThrottleQueue.h65
-rw-r--r--netwerk/base/Tickler.cpp277
-rw-r--r--netwerk/base/Tickler.h131
-rw-r--r--netwerk/base/moz.build316
-rw-r--r--netwerk/base/mozIThirdPartyUtil.idl167
-rw-r--r--netwerk/base/netCore.h14
-rw-r--r--netwerk/base/nsASocketHandler.h96
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.cpp288
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.h129
-rw-r--r--netwerk/base/nsAsyncStreamCopier.cpp419
-rw-r--r--netwerk/base/nsAsyncStreamCopier.h83
-rw-r--r--netwerk/base/nsAuthInformationHolder.cpp74
-rw-r--r--netwerk/base/nsAuthInformationHolder.h48
-rw-r--r--netwerk/base/nsBase64Encoder.cpp67
-rw-r--r--netwerk/base/nsBase64Encoder.h32
-rw-r--r--netwerk/base/nsBaseChannel.cpp937
-rw-r--r--netwerk/base/nsBaseChannel.h300
-rw-r--r--netwerk/base/nsBaseContentStream.cpp137
-rw-r--r--netwerk/base/nsBaseContentStream.h82
-rw-r--r--netwerk/base/nsBufferedStreams.cpp832
-rw-r--r--netwerk/base/nsBufferedStreams.h122
-rw-r--r--netwerk/base/nsChannelClassifier.cpp696
-rw-r--r--netwerk/base/nsChannelClassifier.h65
-rw-r--r--netwerk/base/nsDNSPrefetch.cpp106
-rw-r--r--netwerk/base/nsDNSPrefetch.h53
-rw-r--r--netwerk/base/nsDirectoryIndexStream.cpp359
-rw-r--r--netwerk/base/nsDirectoryIndexStream.h49
-rw-r--r--netwerk/base/nsDownloader.cpp114
-rw-r--r--netwerk/base/nsDownloader.h40
-rw-r--r--netwerk/base/nsFileStreams.cpp1153
-rw-r--r--netwerk/base/nsFileStreams.h333
-rw-r--r--netwerk/base/nsIApplicationCache.idl205
-rw-r--r--netwerk/base/nsIApplicationCacheChannel.idl57
-rw-r--r--netwerk/base/nsIApplicationCacheContainer.idl19
-rw-r--r--netwerk/base/nsIApplicationCacheService.idl110
-rw-r--r--netwerk/base/nsIArrayBufferInputStream.idl26
-rw-r--r--netwerk/base/nsIAsyncStreamCopier.idl64
-rw-r--r--netwerk/base/nsIAsyncStreamCopier2.idl59
-rw-r--r--netwerk/base/nsIAsyncVerifyRedirectCallback.idl19
-rw-r--r--netwerk/base/nsIAuthInformation.idl118
-rw-r--r--netwerk/base/nsIAuthModule.idl145
-rw-r--r--netwerk/base/nsIAuthPrompt.idl80
-rw-r--r--netwerk/base/nsIAuthPrompt2.idl103
-rw-r--r--netwerk/base/nsIAuthPromptAdapterFactory.idl22
-rw-r--r--netwerk/base/nsIAuthPromptCallback.idl44
-rw-r--r--netwerk/base/nsIAuthPromptProvider.idl34
-rw-r--r--netwerk/base/nsIBackgroundFileSaver.idl182
-rw-r--r--netwerk/base/nsIBrowserSearchService.idl530
-rw-r--r--netwerk/base/nsIBufferedStreams.idl37
-rw-r--r--netwerk/base/nsIByteRangeRequest.idl27
-rw-r--r--netwerk/base/nsICacheInfoChannel.idl84
-rw-r--r--netwerk/base/nsICachingChannel.idl129
-rw-r--r--netwerk/base/nsICancelable.idl24
-rw-r--r--netwerk/base/nsICaptivePortalService.idl59
-rw-r--r--netwerk/base/nsIChannel.idl357
-rw-r--r--netwerk/base/nsIChannelEventSink.idl101
-rw-r--r--netwerk/base/nsIChannelWithDivertableParentListener.idl46
-rw-r--r--netwerk/base/nsIChildChannel.idl35
-rw-r--r--netwerk/base/nsIClassOfService.idl47
-rw-r--r--netwerk/base/nsIContentSniffer.idl35
-rw-r--r--netwerk/base/nsICryptoFIPSInfo.idl15
-rw-r--r--netwerk/base/nsICryptoHMAC.idl108
-rw-r--r--netwerk/base/nsICryptoHash.idl105
-rw-r--r--netwerk/base/nsIDashboard.idl54
-rw-r--r--netwerk/base/nsIDashboardEventNotifier.idl23
-rw-r--r--netwerk/base/nsIDeprecationWarner.idl23
-rw-r--r--netwerk/base/nsIDivertableChannel.idl78
-rw-r--r--netwerk/base/nsIDownloader.idl51
-rw-r--r--netwerk/base/nsIEncodedChannel.idl55
-rw-r--r--netwerk/base/nsIExternalProtocolHandler.idl17
-rw-r--r--netwerk/base/nsIFileStreams.idl237
-rw-r--r--netwerk/base/nsIFileURL.idl29
-rw-r--r--netwerk/base/nsIForcePendingChannel.idl22
-rw-r--r--netwerk/base/nsIFormPOSTActionChannel.idl17
-rw-r--r--netwerk/base/nsIHttpAuthenticatorCallback.idl31
-rw-r--r--netwerk/base/nsIHttpPushListener.idl36
-rw-r--r--netwerk/base/nsIIOService.idl218
-rw-r--r--netwerk/base/nsIIOService2.idl82
-rw-r--r--netwerk/base/nsIIncrementalDownload.idl109
-rw-r--r--netwerk/base/nsIIncrementalStreamLoader.idl100
-rw-r--r--netwerk/base/nsIInputStreamChannel.idl64
-rw-r--r--netwerk/base/nsIInputStreamPump.idl73
-rw-r--r--netwerk/base/nsILoadContextInfo.idl89
-rw-r--r--netwerk/base/nsILoadGroup.idl104
-rw-r--r--netwerk/base/nsILoadGroupChild.idl42
-rw-r--r--netwerk/base/nsILoadInfo.idl732
-rw-r--r--netwerk/base/nsIMIMEInputStream.idl47
-rw-r--r--netwerk/base/nsIMultiPartChannel.idl35
-rw-r--r--netwerk/base/nsINSSErrorsService.idl69
-rw-r--r--netwerk/base/nsINestedURI.idl46
-rw-r--r--netwerk/base/nsINetAddr.idl88
-rw-r--r--netwerk/base/nsINetUtil.idl230
-rw-r--r--netwerk/base/nsINetworkInfoService.idl57
-rw-r--r--netwerk/base/nsINetworkInterceptController.idl144
-rw-r--r--netwerk/base/nsINetworkLinkService.idl108
-rw-r--r--netwerk/base/nsINetworkPredictor.idl165
-rw-r--r--netwerk/base/nsINetworkPredictorVerifier.idl40
-rw-r--r--netwerk/base/nsINetworkProperties.idl18
-rw-r--r--netwerk/base/nsINullChannel.idl16
-rw-r--r--netwerk/base/nsIOService.cpp1880
-rw-r--r--netwerk/base/nsIOService.h203
-rw-r--r--netwerk/base/nsIParentChannel.idl41
-rw-r--r--netwerk/base/nsIParentRedirectingChannel.idl46
-rw-r--r--netwerk/base/nsIPermission.idl77
-rw-r--r--netwerk/base/nsIPermissionManager.idl271
-rw-r--r--netwerk/base/nsIPrivateBrowsingChannel.idl58
-rw-r--r--netwerk/base/nsIProgressEventSink.idl80
-rw-r--r--netwerk/base/nsIPrompt.idl97
-rw-r--r--netwerk/base/nsIProtocolHandler.idl321
-rw-r--r--netwerk/base/nsIProtocolProxyCallback.idl42
-rw-r--r--netwerk/base/nsIProtocolProxyFilter.idl74
-rw-r--r--netwerk/base/nsIProtocolProxyService.idl281
-rw-r--r--netwerk/base/nsIProtocolProxyService2.idl30
-rw-r--r--netwerk/base/nsIProxiedChannel.idl27
-rw-r--r--netwerk/base/nsIProxiedProtocolHandler.idl55
-rw-r--r--netwerk/base/nsIProxyInfo.idl89
-rw-r--r--netwerk/base/nsIRandomGenerator.idl24
-rw-r--r--netwerk/base/nsIRedirectChannelRegistrar.idl72
-rw-r--r--netwerk/base/nsIRedirectResultListener.idl22
-rw-r--r--netwerk/base/nsIRequest.idl217
-rw-r--r--netwerk/base/nsIRequestContext.idl95
-rw-r--r--netwerk/base/nsIRequestObserver.idl41
-rw-r--r--netwerk/base/nsIRequestObserverProxy.idl31
-rw-r--r--netwerk/base/nsIResumableChannel.idl39
-rw-r--r--netwerk/base/nsISecCheckWrapChannel.idl24
-rw-r--r--netwerk/base/nsISecureBrowserUI.idl24
-rw-r--r--netwerk/base/nsISecurityEventSink.idl26
-rw-r--r--netwerk/base/nsISecurityInfoProvider.idl21
-rw-r--r--netwerk/base/nsISensitiveInfoHiddenURI.idl17
-rw-r--r--netwerk/base/nsISerializationHelper.idl29
-rw-r--r--netwerk/base/nsIServerSocket.idl237
-rw-r--r--netwerk/base/nsISimpleStreamListener.idl25
-rw-r--r--netwerk/base/nsISocketFilter.idl53
-rw-r--r--netwerk/base/nsISocketTransport.idl256
-rw-r--r--netwerk/base/nsISocketTransportService.idl135
-rw-r--r--netwerk/base/nsISpeculativeConnect.idl77
-rw-r--r--netwerk/base/nsIStandardURL.idl80
-rw-r--r--netwerk/base/nsIStreamListener.idl40
-rw-r--r--netwerk/base/nsIStreamListenerTee.idl50
-rw-r--r--netwerk/base/nsIStreamLoader.idl77
-rw-r--r--netwerk/base/nsIStreamTransportService.idl68
-rw-r--r--netwerk/base/nsIStreamingProtocolController.idl186
-rw-r--r--netwerk/base/nsIStreamingProtocolService.idl35
-rw-r--r--netwerk/base/nsISyncStreamListener.idl19
-rw-r--r--netwerk/base/nsISystemProxySettings.idl42
-rw-r--r--netwerk/base/nsITLSServerSocket.idl201
-rw-r--r--netwerk/base/nsIThreadRetargetableRequest.idl35
-rw-r--r--netwerk/base/nsIThreadRetargetableStreamListener.idl33
-rw-r--r--netwerk/base/nsIThrottledInputChannel.idl80
-rw-r--r--netwerk/base/nsITimedChannel.idl80
-rw-r--r--netwerk/base/nsITraceableChannel.idl34
-rw-r--r--netwerk/base/nsITransport.idl163
-rw-r--r--netwerk/base/nsIUDPSocket.idl353
-rw-r--r--netwerk/base/nsIURI.idl290
-rw-r--r--netwerk/base/nsIURIClassifier.idl65
-rw-r--r--netwerk/base/nsIURIWithBlobImpl.idl20
-rw-r--r--netwerk/base/nsIURIWithPrincipal.idl27
-rw-r--r--netwerk/base/nsIURIWithQuery.idl30
-rw-r--r--netwerk/base/nsIURL.idl122
-rw-r--r--netwerk/base/nsIURLParser.idl98
-rw-r--r--netwerk/base/nsIUnicharStreamLoader.idl88
-rw-r--r--netwerk/base/nsIUploadChannel.idl56
-rw-r--r--netwerk/base/nsIUploadChannel2.idl67
-rw-r--r--netwerk/base/nsIncrementalDownload.cpp936
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.cpp214
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.h54
-rw-r--r--netwerk/base/nsInputStreamChannel.cpp112
-rw-r--r--netwerk/base/nsInputStreamChannel.h47
-rw-r--r--netwerk/base/nsInputStreamPump.cpp766
-rw-r--r--netwerk/base/nsInputStreamPump.h105
-rw-r--r--netwerk/base/nsLoadGroup.cpp1087
-rw-r--r--netwerk/base/nsLoadGroup.h105
-rw-r--r--netwerk/base/nsMIMEInputStream.cpp390
-rw-r--r--netwerk/base/nsMIMEInputStream.h29
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.cpp390
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.h106
-rw-r--r--netwerk/base/nsNetAddr.cpp152
-rw-r--r--netwerk/base/nsNetAddr.h31
-rw-r--r--netwerk/base/nsNetSegmentUtils.h23
-rw-r--r--netwerk/base/nsNetUtil.cpp2459
-rw-r--r--netwerk/base/nsNetUtil.h1000
-rw-r--r--netwerk/base/nsNetUtilInlines.h395
-rw-r--r--netwerk/base/nsNetworkInfoService.cpp113
-rw-r--r--netwerk/base/nsNetworkInfoService.h40
-rw-r--r--netwerk/base/nsPACMan.cpp769
-rw-r--r--netwerk/base/nsPACMan.h248
-rw-r--r--netwerk/base/nsPILoadGroupInternal.idl28
-rw-r--r--netwerk/base/nsPISocketTransportService.idl61
-rw-r--r--netwerk/base/nsPreloadedStream.cpp153
-rw-r--r--netwerk/base/nsPreloadedStream.h52
-rw-r--r--netwerk/base/nsProtocolProxyService.cpp2146
-rw-r--r--netwerk/base/nsProtocolProxyService.h414
-rw-r--r--netwerk/base/nsProxyInfo.cpp126
-rw-r--r--netwerk/base/nsProxyInfo.h82
-rw-r--r--netwerk/base/nsReadLine.h134
-rw-r--r--netwerk/base/nsRequestObserverProxy.cpp195
-rw-r--r--netwerk/base/nsRequestObserverProxy.h58
-rw-r--r--netwerk/base/nsSecCheckWrapChannel.cpp196
-rw-r--r--netwerk/base/nsSecCheckWrapChannel.h106
-rw-r--r--netwerk/base/nsSerializationHelper.cpp73
-rw-r--r--netwerk/base/nsSerializationHelper.h38
-rw-r--r--netwerk/base/nsServerSocket.cpp560
-rw-r--r--netwerk/base/nsServerSocket.h67
-rw-r--r--netwerk/base/nsSimpleNestedURI.cpp190
-rw-r--r--netwerk/base/nsSimpleNestedURI.h75
-rw-r--r--netwerk/base/nsSimpleStreamListener.cpp83
-rw-r--r--netwerk/base/nsSimpleStreamListener.h36
-rw-r--r--netwerk/base/nsSimpleURI.cpp847
-rw-r--r--netwerk/base/nsSimpleURI.h110
-rw-r--r--netwerk/base/nsSocketTransport2.cpp3245
-rw-r--r--netwerk/base/nsSocketTransport2.h471
-rw-r--r--netwerk/base/nsSocketTransportService2.cpp1627
-rw-r--r--netwerk/base/nsSocketTransportService2.h282
-rw-r--r--netwerk/base/nsStandardURL.cpp3619
-rw-r--r--netwerk/base/nsStandardURL.h405
-rw-r--r--netwerk/base/nsStreamListenerTee.cpp142
-rw-r--r--netwerk/base/nsStreamListenerTee.h43
-rw-r--r--netwerk/base/nsStreamListenerWrapper.cpp32
-rw-r--r--netwerk/base/nsStreamListenerWrapper.h43
-rw-r--r--netwerk/base/nsStreamLoader.cpp169
-rw-r--r--netwerk/base/nsStreamLoader.h58
-rw-r--r--netwerk/base/nsStreamTransportService.cpp563
-rw-r--r--netwerk/base/nsStreamTransportService.h48
-rw-r--r--netwerk/base/nsSyncStreamListener.cpp181
-rw-r--r--netwerk/base/nsSyncStreamListener.h45
-rw-r--r--netwerk/base/nsTemporaryFileInputStream.cpp252
-rw-r--r--netwerk/base/nsTemporaryFileInputStream.h64
-rw-r--r--netwerk/base/nsTransportUtils.cpp142
-rw-r--r--netwerk/base/nsTransportUtils.h26
-rw-r--r--netwerk/base/nsUDPSocket.cpp1613
-rw-r--r--netwerk/base/nsUDPSocket.h131
-rw-r--r--netwerk/base/nsURIHashKey.h61
-rw-r--r--netwerk/base/nsURLHelper.cpp1168
-rw-r--r--netwerk/base/nsURLHelper.h250
-rw-r--r--netwerk/base/nsURLHelperOSX.cpp216
-rw-r--r--netwerk/base/nsURLHelperUnix.cpp112
-rw-r--r--netwerk/base/nsURLHelperWin.cpp117
-rw-r--r--netwerk/base/nsURLParsers.cpp702
-rw-r--r--netwerk/base/nsURLParsers.h124
-rw-r--r--netwerk/base/nsUnicharStreamLoader.cpp250
-rw-r--r--netwerk/base/nsUnicharStreamLoader.h60
-rw-r--r--netwerk/base/rust-url-capi/.gitignore2
-rw-r--r--netwerk/base/rust-url-capi/Cargo.toml19
-rw-r--r--netwerk/base/rust-url-capi/src/error_mapping.rs68
-rw-r--r--netwerk/base/rust-url-capi/src/lib.rs477
-rw-r--r--netwerk/base/rust-url-capi/src/rust-url-capi.h45
-rw-r--r--netwerk/base/rust-url-capi/src/string_utils.rs57
-rw-r--r--netwerk/base/rust-url-capi/test/Makefile4
-rw-r--r--netwerk/base/rust-url-capi/test/test.cpp141
-rw-r--r--netwerk/base/security-prefs.js119
-rw-r--r--netwerk/build/moz.build72
-rw-r--r--netwerk/build/nsNetCID.h1079
-rw-r--r--netwerk/build/nsNetModule.cpp1190
-rw-r--r--netwerk/cache/moz.build51
-rw-r--r--netwerk/cache/nsApplicationCache.h29
-rw-r--r--netwerk/cache/nsApplicationCacheService.cpp268
-rw-r--r--netwerk/cache/nsApplicationCacheService.h28
-rw-r--r--netwerk/cache/nsCache.cpp95
-rw-r--r--netwerk/cache/nsCache.h42
-rw-r--r--netwerk/cache/nsCacheDevice.h63
-rw-r--r--netwerk/cache/nsCacheEntry.cpp508
-rw-r--r--netwerk/cache/nsCacheEntry.h302
-rw-r--r--netwerk/cache/nsCacheEntryDescriptor.cpp1475
-rw-r--r--netwerk/cache/nsCacheEntryDescriptor.h237
-rw-r--r--netwerk/cache/nsCacheMetaData.cpp162
-rw-r--r--netwerk/cache/nsCacheMetaData.h45
-rw-r--r--netwerk/cache/nsCacheRequest.h158
-rw-r--r--netwerk/cache/nsCacheService.cpp3209
-rw-r--r--netwerk/cache/nsCacheService.h392
-rw-r--r--netwerk/cache/nsCacheSession.cpp147
-rw-r--r--netwerk/cache/nsCacheSession.h67
-rw-r--r--netwerk/cache/nsCacheUtils.cpp89
-rw-r--r--netwerk/cache/nsCacheUtils.h43
-rw-r--r--netwerk/cache/nsDeleteDir.cpp454
-rw-r--r--netwerk/cache/nsDeleteDir.h80
-rw-r--r--netwerk/cache/nsDiskCache.h72
-rw-r--r--netwerk/cache/nsDiskCacheBinding.cpp371
-rw-r--r--netwerk/cache/nsDiskCacheBinding.h123
-rw-r--r--netwerk/cache/nsDiskCacheBlockFile.cpp404
-rw-r--r--netwerk/cache/nsDiskCacheBlockFile.h69
-rw-r--r--netwerk/cache/nsDiskCacheDevice.cpp1149
-rw-r--r--netwerk/cache/nsDiskCacheDevice.h116
-rw-r--r--netwerk/cache/nsDiskCacheDeviceSQL.cpp2906
-rw-r--r--netwerk/cache/nsDiskCacheDeviceSQL.h290
-rw-r--r--netwerk/cache/nsDiskCacheEntry.cpp133
-rw-r--r--netwerk/cache/nsDiskCacheEntry.h100
-rw-r--r--netwerk/cache/nsDiskCacheMap.cpp1430
-rw-r--r--netwerk/cache/nsDiskCacheMap.h577
-rw-r--r--netwerk/cache/nsDiskCacheStreams.cpp693
-rw-r--r--netwerk/cache/nsDiskCacheStreams.h70
-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/cache/nsMemoryCacheDevice.cpp617
-rw-r--r--netwerk/cache/nsMemoryCacheDevice.h124
-rw-r--r--netwerk/cache2/AppCacheStorage.cpp177
-rw-r--r--netwerk/cache2/AppCacheStorage.h37
-rw-r--r--netwerk/cache2/CacheEntry.cpp1920
-rw-r--r--netwerk/cache2/CacheEntry.h418
-rw-r--r--netwerk/cache2/CacheFile.cpp2377
-rw-r--r--netwerk/cache2/CacheFile.h272
-rw-r--r--netwerk/cache2/CacheFileChunk.cpp932
-rw-r--r--netwerk/cache2/CacheFileChunk.h252
-rw-r--r--netwerk/cache2/CacheFileContextEvictor.cpp663
-rw-r--r--netwerk/cache2/CacheFileContextEvictor.h96
-rw-r--r--netwerk/cache2/CacheFileIOManager.cpp4274
-rw-r--r--netwerk/cache2/CacheFileIOManager.h489
-rw-r--r--netwerk/cache2/CacheFileInputStream.cpp723
-rw-r--r--netwerk/cache2/CacheFileInputStream.h79
-rw-r--r--netwerk/cache2/CacheFileMetadata.cpp1095
-rw-r--r--netwerk/cache2/CacheFileMetadata.h227
-rw-r--r--netwerk/cache2/CacheFileOutputStream.cpp483
-rw-r--r--netwerk/cache2/CacheFileOutputStream.h73
-rw-r--r--netwerk/cache2/CacheFileUtils.cpp575
-rw-r--r--netwerk/cache2/CacheFileUtils.h164
-rw-r--r--netwerk/cache2/CacheHashUtils.cpp206
-rw-r--r--netwerk/cache2/CacheHashUtils.h67
-rw-r--r--netwerk/cache2/CacheIOThread.cpp646
-rw-r--r--netwerk/cache2/CacheIOThread.h147
-rw-r--r--netwerk/cache2/CacheIndex.cpp3810
-rw-r--r--netwerk/cache2/CacheIndex.h1153
-rw-r--r--netwerk/cache2/CacheIndexContextIterator.cpp45
-rw-r--r--netwerk/cache2/CacheIndexContextIterator.h32
-rw-r--r--netwerk/cache2/CacheIndexIterator.cpp118
-rw-r--r--netwerk/cache2/CacheIndexIterator.h59
-rw-r--r--netwerk/cache2/CacheLog.cpp22
-rw-r--r--netwerk/cache2/CacheLog.h20
-rw-r--r--netwerk/cache2/CacheObserver.cpp581
-rw-r--r--netwerk/cache2/CacheObserver.h121
-rw-r--r--netwerk/cache2/CacheStorage.cpp245
-rw-r--r--netwerk/cache2/CacheStorage.h81
-rw-r--r--netwerk/cache2/CacheStorageService.cpp2290
-rw-r--r--netwerk/cache2/CacheStorageService.h422
-rw-r--r--netwerk/cache2/OldWrappers.cpp1155
-rw-r--r--netwerk/cache2/OldWrappers.h284
-rw-r--r--netwerk/cache2/moz.build59
-rw-r--r--netwerk/cache2/nsICacheEntry.idl292
-rw-r--r--netwerk/cache2/nsICacheEntryDoomCallback.idl15
-rw-r--r--netwerk/cache2/nsICacheEntryOpenCallback.idl91
-rw-r--r--netwerk/cache2/nsICacheStorage.idl134
-rw-r--r--netwerk/cache2/nsICacheStorageService.idl126
-rw-r--r--netwerk/cache2/nsICacheStorageVisitor.idl33
-rw-r--r--netwerk/cache2/nsICacheTesting.idl20
-rw-r--r--netwerk/cookie/CookieServiceChild.cpp243
-rw-r--r--netwerk/cookie/CookieServiceChild.h67
-rw-r--r--netwerk/cookie/CookieServiceParent.cpp157
-rw-r--r--netwerk/cookie/CookieServiceParent.h46
-rw-r--r--netwerk/cookie/PCookieService.ipdl109
-rw-r--r--netwerk/cookie/moz.build55
-rw-r--r--netwerk/cookie/nsCookie.cpp177
-rw-r--r--netwerk/cookie/nsCookie.h140
-rw-r--r--netwerk/cookie/nsCookieService.cpp5164
-rw-r--r--netwerk/cookie/nsCookieService.h371
-rw-r--r--netwerk/cookie/nsICookie.idl85
-rw-r--r--netwerk/cookie/nsICookie2.idl63
-rw-r--r--netwerk/cookie/nsICookieManager.idl88
-rw-r--r--netwerk/cookie/nsICookieManager2.idl164
-rw-r--r--netwerk/cookie/nsICookiePermission.idl109
-rw-r--r--netwerk/cookie/nsICookieService.idl193
-rw-r--r--netwerk/cookie/test/browser/browser.ini5
-rw-r--r--netwerk/cookie/test/browser/browser_originattributes.js113
-rw-r--r--netwerk/cookie/test/browser/file_empty.html3
-rw-r--r--netwerk/cookie/test/unit/test_bug1155169.js73
-rw-r--r--netwerk/cookie/test/unit/test_bug1267910.js196
-rw-r--r--netwerk/cookie/test/unit/test_bug643051.js29
-rw-r--r--netwerk/cookie/test/unit/test_eviction.js296
-rw-r--r--netwerk/cookie/test/unit/test_parser_0001.js29
-rw-r--r--netwerk/cookie/test/unit/test_parser_0019.js29
-rw-r--r--netwerk/cookie/test/unit/xpcshell.ini10
-rw-r--r--netwerk/cookie/test/unit_ipc/test_ipc_parser_0001.js9
-rw-r--r--netwerk/cookie/test/unit_ipc/test_ipc_parser_0019.js9
-rw-r--r--netwerk/cookie/test/unit_ipc/xpcshell.ini10
-rw-r--r--netwerk/dns/ChildDNSService.cpp330
-rw-r--r--netwerk/dns/ChildDNSService.h60
-rw-r--r--netwerk/dns/DNS.cpp368
-rw-r--r--netwerk/dns/DNS.h183
-rw-r--r--netwerk/dns/DNSListenerProxy.cpp43
-rw-r--r--netwerk/dns/DNSListenerProxy.h72
-rw-r--r--netwerk/dns/DNSRequestChild.cpp313
-rw-r--r--netwerk/dns/DNSRequestChild.h61
-rw-r--r--netwerk/dns/DNSRequestParent.cpp132
-rw-r--r--netwerk/dns/DNSRequestParent.h50
-rw-r--r--netwerk/dns/GetAddrInfo.cpp369
-rw-r--r--netwerk/dns/GetAddrInfo.h67
-rw-r--r--netwerk/dns/PDNSParams.h23
-rw-r--r--netwerk/dns/PDNSRequest.ipdl36
-rw-r--r--netwerk/dns/PDNSRequestParams.ipdlh31
-rw-r--r--netwerk/dns/effective_tld_names.dat10834
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp779
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderOperator.h152
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp302
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderReply.h164
-rw-r--r--netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm244
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm297
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm70
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm221
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm100
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DataReader.jsm133
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm98
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm875
-rw-r--r--netwerk/dns/mdns/libmdns/moz.build56
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp285
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h48
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js201
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.manifest3
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp209
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h50
-rw-r--r--netwerk/dns/mdns/libmdns/nsMulticastDNSModule.cpp61
-rw-r--r--netwerk/dns/mdns/moz.build13
-rw-r--r--netwerk/dns/mdns/nsIDNSServiceDiscovery.idl219
-rw-r--r--netwerk/dns/moz.build78
-rw-r--r--netwerk/dns/nameprep.c347
-rw-r--r--netwerk/dns/nameprep_template.c139
-rw-r--r--netwerk/dns/nameprepdata.c2588
-rw-r--r--netwerk/dns/nsDNSService2.cpp1071
-rw-r--r--netwerk/dns/nsDNSService2.h72
-rw-r--r--netwerk/dns/nsEffectiveTLDService.cpp368
-rw-r--r--netwerk/dns/nsEffectiveTLDService.h98
-rw-r--r--netwerk/dns/nsHostResolver.cpp1579
-rw-r--r--netwerk/dns/nsHostResolver.h372
-rw-r--r--netwerk/dns/nsIDNKitInterface.h195
-rw-r--r--netwerk/dns/nsIDNSListener.idl46
-rw-r--r--netwerk/dns/nsIDNSRecord.idl100
-rw-r--r--netwerk/dns/nsIDNSService.idl171
-rw-r--r--netwerk/dns/nsIDNService.cpp959
-rw-r--r--netwerk/dns/nsIDNService.h195
-rw-r--r--netwerk/dns/nsIEffectiveTLDService.idl125
-rw-r--r--netwerk/dns/nsIIDNService.idl58
-rw-r--r--netwerk/dns/nsPIDNSService.idl34
-rw-r--r--netwerk/dns/prepare_tlds.py121
-rw-r--r--netwerk/dns/punycode.c289
-rw-r--r--netwerk/dns/punycode.h108
-rw-r--r--netwerk/ipc/ChannelEventQueue.cpp98
-rw-r--r--netwerk/ipc/ChannelEventQueue.h237
-rw-r--r--netwerk/ipc/NeckoChannelParams.ipdlh196
-rw-r--r--netwerk/ipc/NeckoChild.cpp431
-rw-r--r--netwerk/ipc/NeckoChild.h107
-rw-r--r--netwerk/ipc/NeckoCommon.cpp18
-rw-r--r--netwerk/ipc/NeckoCommon.h130
-rw-r--r--netwerk/ipc/NeckoMessageUtils.h189
-rw-r--r--netwerk/ipc/NeckoParent.cpp907
-rw-r--r--netwerk/ipc/NeckoParent.h227
-rw-r--r--netwerk/ipc/PChannelDiverter.ipdl25
-rw-r--r--netwerk/ipc/PDataChannel.ipdl25
-rw-r--r--netwerk/ipc/PNecko.ipdl145
-rw-r--r--netwerk/ipc/PRtspChannel.ipdl25
-rw-r--r--netwerk/ipc/PRtspController.ipdl64
-rw-r--r--netwerk/ipc/moz.build40
-rw-r--r--netwerk/locales/en-US/necko.properties49
-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.idl365
-rw-r--r--netwerk/mime/nsIMIMEService.idl75
-rw-r--r--netwerk/mime/nsMIMEHeaderParamImpl.cpp1346
-rw-r--r--netwerk/mime/nsMIMEHeaderParamImpl.h42
-rw-r--r--netwerk/mime/nsMimeTypes.h229
-rw-r--r--netwerk/moz.build37
-rw-r--r--netwerk/necko-config.h.in24
-rw-r--r--netwerk/protocol/about/moz.build34
-rw-r--r--netwerk/protocol/about/nsAboutBlank.cpp59
-rw-r--r--netwerk/protocol/about/nsAboutBlank.h35
-rw-r--r--netwerk/protocol/about/nsAboutCache.cpp585
-rw-r--r--netwerk/protocol/about/nsAboutCache.h136
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.cpp603
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.h98
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.cpp439
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.h90
-rw-r--r--netwerk/protocol/about/nsAboutProtocolUtils.h71
-rw-r--r--netwerk/protocol/about/nsIAboutModule.idl88
-rw-r--r--netwerk/protocol/data/DataChannelChild.cpp77
-rw-r--r--netwerk/protocol/data/DataChannelChild.h43
-rw-r--r--netwerk/protocol/data/DataChannelParent.cpp89
-rw-r--r--netwerk/protocol/data/DataChannelParent.h41
-rw-r--r--netwerk/protocol/data/moz.build24
-rw-r--r--netwerk/protocol/data/nsDataChannel.cpp91
-rw-r--r--netwerk/protocol/data/nsDataChannel.h28
-rw-r--r--netwerk/protocol/data/nsDataHandler.cpp250
-rw-r--r--netwerk/protocol/data/nsDataHandler.h41
-rw-r--r--netwerk/protocol/data/nsDataModule.cpp21
-rw-r--r--netwerk/protocol/device/AndroidCaptureProvider.cpp301
-rw-r--r--netwerk/protocol/device/AndroidCaptureProvider.h68
-rw-r--r--netwerk/protocol/device/CameraStreamImpl.cpp114
-rw-r--r--netwerk/protocol/device/CameraStreamImpl.h71
-rw-r--r--netwerk/protocol/device/RawStructs.h60
-rw-r--r--netwerk/protocol/device/moz.build27
-rw-r--r--netwerk/protocol/device/nsDeviceCaptureProvider.h31
-rw-r--r--netwerk/protocol/device/nsDeviceChannel.cpp154
-rw-r--r--netwerk/protocol/device/nsDeviceChannel.h26
-rw-r--r--netwerk/protocol/device/nsDeviceProtocolHandler.cpp93
-rw-r--r--netwerk/protocol/device/nsDeviceProtocolHandler.h34
-rw-r--r--netwerk/protocol/file/moz.build30
-rw-r--r--netwerk/protocol/file/nsFileChannel.cpp486
-rw-r--r--netwerk/protocol/file/nsFileChannel.h45
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.cpp274
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.h27
-rw-r--r--netwerk/protocol/file/nsIFileChannel.idl17
-rw-r--r--netwerk/protocol/file/nsIFileProtocolHandler.idl65
-rw-r--r--netwerk/protocol/ftp/FTPChannelChild.cpp932
-rw-r--r--netwerk/protocol/ftp/FTPChannelChild.h164
-rw-r--r--netwerk/protocol/ftp/FTPChannelParent.cpp896
-rw-r--r--netwerk/protocol/ftp/FTPChannelParent.h146
-rw-r--r--netwerk/protocol/ftp/PFTPChannel.ipdl74
-rw-r--r--netwerk/protocol/ftp/doc/rfc959.txt3933
-rw-r--r--netwerk/protocol/ftp/doc/testdoc4
-rw-r--r--netwerk/protocol/ftp/ftpCore.h15
-rw-r--r--netwerk/protocol/ftp/moz.build45
-rw-r--r--netwerk/protocol/ftp/nsFTPChannel.cpp294
-rw-r--r--netwerk/protocol/ftp/nsFTPChannel.h122
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.cpp2256
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.h231
-rw-r--r--netwerk/protocol/ftp/nsFtpControlConnection.cpp189
-rw-r--r--netwerk/protocol/ftp/nsFtpControlConnection.h85
-rw-r--r--netwerk/protocol/ftp/nsFtpProtocolHandler.cpp426
-rw-r--r--netwerk/protocol/ftp/nsFtpProtocolHandler.h87
-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.html13
-rw-r--r--netwerk/protocol/ftp/test/frametest/menu.html373
-rw-r--r--netwerk/protocol/http/ASpdySession.cpp127
-rw-r--r--netwerk/protocol/http/ASpdySession.h120
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.cpp151
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.h46
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.cpp71
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.h52
-rw-r--r--netwerk/protocol/http/AlternateServices.cpp1075
-rw-r--r--netwerk/protocol/http/AlternateServices.h191
-rw-r--r--netwerk/protocol/http/CacheControlParser.cpp123
-rw-r--r--netwerk/protocol/http/CacheControlParser.h44
-rw-r--r--netwerk/protocol/http/ConnectionDiagnostics.cpp211
-rw-r--r--netwerk/protocol/http/HSTSPrimerListener.cpp273
-rw-r--r--netwerk/protocol/http/HSTSPrimerListener.h108
-rw-r--r--netwerk/protocol/http/Http2Compression.cpp1487
-rw-r--r--netwerk/protocol/http/Http2Compression.h202
-rw-r--r--netwerk/protocol/http/Http2HuffmanIncoming.h4054
-rw-r--r--netwerk/protocol/http/Http2HuffmanOutgoing.h278
-rw-r--r--netwerk/protocol/http/Http2Push.cpp510
-rw-r--r--netwerk/protocol/http/Http2Push.h129
-rw-r--r--netwerk/protocol/http/Http2Session.cpp3884
-rw-r--r--netwerk/protocol/http/Http2Session.h508
-rw-r--r--netwerk/protocol/http/Http2Stream.cpp1472
-rw-r--r--netwerk/protocol/http/Http2Stream.h346
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.cpp3715
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.h660
-rw-r--r--netwerk/protocol/http/HttpChannelChild.cpp2849
-rw-r--r--netwerk/protocol/http/HttpChannelChild.h390
-rw-r--r--netwerk/protocol/http/HttpChannelParent.cpp1821
-rw-r--r--netwerk/protocol/http/HttpChannelParent.h269
-rw-r--r--netwerk/protocol/http/HttpChannelParentListener.cpp407
-rw-r--r--netwerk/protocol/http/HttpChannelParentListener.h89
-rw-r--r--netwerk/protocol/http/HttpInfo.cpp18
-rw-r--r--netwerk/protocol/http/HttpInfo.h25
-rw-r--r--netwerk/protocol/http/HttpLog.h58
-rw-r--r--netwerk/protocol/http/InterceptedChannel.cpp540
-rw-r--r--netwerk/protocol/http/InterceptedChannel.h134
-rw-r--r--netwerk/protocol/http/NullHttpChannel.cpp772
-rw-r--r--netwerk/protocol/http/NullHttpChannel.h66
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.cpp333
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.h84
-rw-r--r--netwerk/protocol/http/PAltDataOutputStream.ipdl32
-rw-r--r--netwerk/protocol/http/PHttpChannel.ipdl178
-rw-r--r--netwerk/protocol/http/PHttpChannelParams.h222
-rw-r--r--netwerk/protocol/http/PSpdyPush.h56
-rw-r--r--netwerk/protocol/http/README119
-rw-r--r--netwerk/protocol/http/TimingStruct.h41
-rw-r--r--netwerk/protocol/http/TunnelUtils.cpp1678
-rw-r--r--netwerk/protocol/http/TunnelUtils.h250
-rw-r--r--netwerk/protocol/http/UserAgentOverrides.jsm182
-rw-r--r--netwerk/protocol/http/UserAgentUpdates.jsm285
-rw-r--r--netwerk/protocol/http/WellKnownOpportunisticUtils.js43
-rw-r--r--netwerk/protocol/http/WellKnownOpportunisticUtils.manifest3
-rw-r--r--netwerk/protocol/http/http2_huffman_table.txt257
-rw-r--r--netwerk/protocol/http/make_incoming_tables.py194
-rw-r--r--netwerk/protocol/http/make_outgoing_tables.py55
-rw-r--r--netwerk/protocol/http/moz.build121
-rw-r--r--netwerk/protocol/http/nsAHttpConnection.h263
-rw-r--r--netwerk/protocol/http/nsAHttpTransaction.h301
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.cpp1526
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.h112
-rw-r--r--netwerk/protocol/http/nsHttp.cpp481
-rw-r--r--netwerk/protocol/http/nsHttp.h276
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.cpp135
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.h35
-rw-r--r--netwerk/protocol/http/nsHttpAtomList.h97
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.cpp607
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.h259
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.cpp151
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.h35
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.cpp116
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.h32
-rw-r--r--netwerk/protocol/http/nsHttpChannel.cpp8563
-rw-r--r--netwerk/protocol/http/nsHttpChannel.h620
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.cpp1682
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.h192
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.cpp168
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.h56
-rw-r--r--netwerk/protocol/http/nsHttpConnection.cpp2319
-rw-r--r--netwerk/protocol/http/nsHttpConnection.h378
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.cpp339
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.h186
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.cpp4006
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.h636
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.cpp722
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.h95
-rw-r--r--netwerk/protocol/http/nsHttpHandler.cpp2493
-rw-r--r--netwerk/protocol/http/nsHttpHandler.h683
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.cpp441
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.h287
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.cpp533
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.h31
-rw-r--r--netwerk/protocol/http/nsHttpPipeline.cpp911
-rw-r--r--netwerk/protocol/http/nsHttpPipeline.h105
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.cpp369
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.h128
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.cpp1221
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.h195
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.cpp2461
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.h487
-rw-r--r--netwerk/protocol/http/nsICorsPreflightCallback.h28
-rw-r--r--netwerk/protocol/http/nsIHstsPrimingCallback.idl50
-rw-r--r--netwerk/protocol/http/nsIHttpActivityObserver.idl127
-rw-r--r--netwerk/protocol/http/nsIHttpAuthManager.idl115
-rw-r--r--netwerk/protocol/http/nsIHttpAuthenticableChannel.idl122
-rw-r--r--netwerk/protocol/http/nsIHttpAuthenticator.idl223
-rw-r--r--netwerk/protocol/http/nsIHttpChannel.idl472
-rw-r--r--netwerk/protocol/http/nsIHttpChannelAuthProvider.idl79
-rw-r--r--netwerk/protocol/http/nsIHttpChannelChild.idl34
-rw-r--r--netwerk/protocol/http/nsIHttpChannelInternal.idl314
-rw-r--r--netwerk/protocol/http/nsIHttpEventSink.idl37
-rw-r--r--netwerk/protocol/http/nsIHttpHeaderVisitor.idl26
-rw-r--r--netwerk/protocol/http/nsIHttpProtocolHandler.idl126
-rw-r--r--netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl26
-rw-r--r--netwerk/protocol/moz.build7
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.cpp193
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.h42
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.cpp404
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.h107
-rw-r--r--netwerk/protocol/res/moz.build26
-rw-r--r--netwerk/protocol/res/nsIResProtocolHandler.idl14
-rw-r--r--netwerk/protocol/res/nsISubstitutingProtocolHandler.idl46
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.cpp100
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.h65
-rw-r--r--netwerk/protocol/viewsource/moz.build21
-rw-r--r--netwerk/protocol/viewsource/nsIViewSourceChannel.idl38
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.cpp1018
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.h78
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.cpp175
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.h45
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.cpp381
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.h114
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.cpp104
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.h92
-rw-r--r--netwerk/protocol/websocket/PTransportProvider.ipdl27
-rw-r--r--netwerk/protocol/websocket/PWebSocket.ipdl70
-rw-r--r--netwerk/protocol/websocket/PWebSocketEventListener.ipdl51
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.cpp4107
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.h337
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.cpp723
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.h97
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.cpp306
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.h72
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.cpp117
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.h62
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.cpp129
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.h43
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.cpp578
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.h124
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.cpp169
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.h79
-rw-r--r--netwerk/protocol/websocket/WebSocketLog.h23
-rw-r--r--netwerk/protocol/websocket/moz.build56
-rw-r--r--netwerk/protocol/websocket/nsITransportProvider.idl36
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketChannel.idl220
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketEventService.idl79
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketListener.idl90
-rw-r--r--netwerk/protocol/wyciwyg/PWyciwygChannel.ipdl62
-rw-r--r--netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp764
-rw-r--r--netwerk/protocol/wyciwyg/WyciwygChannelChild.h124
-rw-r--r--netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp373
-rw-r--r--netwerk/protocol/wyciwyg/WyciwygChannelParent.h71
-rw-r--r--netwerk/protocol/wyciwyg/moz.build36
-rw-r--r--netwerk/protocol/wyciwyg/nsIWyciwygChannel.idl45
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwyg.cpp10
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwyg.h43
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp808
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwygChannel.h115
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp158
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.h23
-rw-r--r--netwerk/sctp/datachannel/DataChannel.cpp2661
-rw-r--r--netwerk/sctp/datachannel/DataChannel.h583
-rw-r--r--netwerk/sctp/datachannel/DataChannelListener.h45
-rw-r--r--netwerk/sctp/datachannel/DataChannelLog.h20
-rw-r--r--netwerk/sctp/datachannel/DataChannelProtocol.h80
-rw-r--r--netwerk/sctp/datachannel/moz.build40
-rw-r--r--netwerk/sctp/sctp_update.log18
-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.build97
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp.h659
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_asconf.c3522
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_asconf.h97
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_auth.c2336
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_auth.h216
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_bsd_addr.c1116
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_bsd_addr.h69
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_callout.c220
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_callout.h103
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_cc_functions.c2503
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_constants.h1094
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_crc32.c818
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_crc32.h54
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_header.h637
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_indata.c5373
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_indata.h131
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_input.c6429
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_input.h66
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_lock_userspace.h251
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_os.h94
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_os_userspace.h1171
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_output.c14561
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_output.h262
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_pcb.c8072
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_pcb.h880
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_peeloff.c326
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_peeloff.h68
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_process_lock.h642
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_sha1.c327
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_sha1.h97
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_ss_functions.c999
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_structs.h1275
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_sysctl.c1706
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_sysctl.h631
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_timer.c1603
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_timer.h107
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_uio.h1427
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_userspace.c389
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_usrreq.c8845
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_var.h506
-rwxr-xr-xnetwerk/sctp/src/netinet/sctputil.c7920
-rwxr-xr-xnetwerk/sctp/src/netinet/sctputil.h414
-rw-r--r--netwerk/sctp/src/netinet6/sctp6_usrreq.c1883
-rwxr-xr-xnetwerk/sctp/src/netinet6/sctp6_var.h88
-rwxr-xr-xnetwerk/sctp/src/user_atomic.h300
-rwxr-xr-xnetwerk/sctp/src/user_environment.c96
-rwxr-xr-xnetwerk/sctp/src/user_environment.h110
-rwxr-xr-xnetwerk/sctp/src/user_inpcb.h378
-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.h266
-rwxr-xr-xnetwerk/sctp/src/user_mbuf.c1431
-rwxr-xr-xnetwerk/sctp/src/user_mbuf.h438
-rwxr-xr-xnetwerk/sctp/src/user_queue.h639
-rwxr-xr-xnetwerk/sctp/src/user_recv_thread.c1524
-rwxr-xr-xnetwerk/sctp/src/user_recv_thread.h34
-rwxr-xr-xnetwerk/sctp/src/user_route.h130
-rwxr-xr-xnetwerk/sctp/src/user_socket.c3420
-rwxr-xr-xnetwerk/sctp/src/user_socketvar.h842
-rwxr-xr-xnetwerk/sctp/src/user_uma.h98
-rw-r--r--netwerk/sctp/src/usrsctp.h1220
-rwxr-xr-xnetwerk/sctp/update_sctp.sh33
-rw-r--r--netwerk/socket/moz.build37
-rw-r--r--netwerk/socket/nsINamedPipeService.idl77
-rw-r--r--netwerk/socket/nsISOCKSSocketInfo.idl24
-rw-r--r--netwerk/socket/nsISSLSocketControl.idl140
-rw-r--r--netwerk/socket/nsISocketProvider.idl124
-rw-r--r--netwerk/socket/nsISocketProviderService.idl20
-rw-r--r--netwerk/socket/nsITransportSecurityInfo.idl24
-rw-r--r--netwerk/socket/nsNamedPipeIOLayer.cpp952
-rw-r--r--netwerk/socket/nsNamedPipeIOLayer.h23
-rw-r--r--netwerk/socket/nsNamedPipeService.cpp324
-rw-r--r--netwerk/socket/nsNamedPipeService.h59
-rw-r--r--netwerk/socket/nsSOCKSIOLayer.cpp1612
-rw-r--r--netwerk/socket/nsSOCKSIOLayer.h25
-rw-r--r--netwerk/socket/nsSOCKSSocketProvider.cpp110
-rw-r--r--netwerk/socket/nsSOCKSSocketProvider.h35
-rw-r--r--netwerk/socket/nsSocketProviderService.cpp45
-rw-r--r--netwerk/socket/nsSocketProviderService.h24
-rw-r--r--netwerk/socket/nsUDPSocketProvider.cpp50
-rw-r--r--netwerk/socket/nsUDPSocketProvider.h23
-rw-r--r--netwerk/srtp/src/LICENSE35
-rw-r--r--netwerk/srtp/src/README174
-rw-r--r--netwerk/srtp/src/VERSION1
-rw-r--r--netwerk/srtp/src/configure.in209
-rw-r--r--netwerk/srtp/src/crypto/ae_xfm/xfm.c605
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes.c2079
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes_cbc.c540
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes_icm.c567
-rw-r--r--netwerk/srtp/src/crypto/cipher/cipher.c421
-rw-r--r--netwerk/srtp/src/crypto/cipher/null_cipher.c153
-rw-r--r--netwerk/srtp/src/crypto/hash/auth.c183
-rw-r--r--netwerk/srtp/src/crypto/hash/hmac.c268
-rw-r--r--netwerk/srtp/src/crypto/hash/null_auth.c162
-rw-r--r--netwerk/srtp/src/crypto/hash/sha1.c405
-rw-r--r--netwerk/srtp/src/crypto/include/aes.h89
-rw-r--r--netwerk/srtp/src/crypto/include/aes_cbc.h85
-rw-r--r--netwerk/srtp/src/crypto/include/aes_icm.h92
-rw-r--r--netwerk/srtp/src/crypto/include/alloc.h57
-rw-r--r--netwerk/srtp/src/crypto/include/auth.h171
-rw-r--r--netwerk/srtp/src/crypto/include/cipher.h230
-rw-r--r--netwerk/srtp/src/crypto/include/crypto.h78
-rw-r--r--netwerk/srtp/src/crypto/include/crypto_kernel.h280
-rw-r--r--netwerk/srtp/src/crypto/include/crypto_math.h239
-rw-r--r--netwerk/srtp/src/crypto/include/crypto_types.h220
-rw-r--r--netwerk/srtp/src/crypto/include/cryptoalg.h133
-rw-r--r--netwerk/srtp/src/crypto/include/datatypes.h508
-rw-r--r--netwerk/srtp/src/crypto/include/err.h174
-rw-r--r--netwerk/srtp/src/crypto/include/hmac.h78
-rw-r--r--netwerk/srtp/src/crypto/include/integers.h155
-rw-r--r--netwerk/srtp/src/crypto/include/kernel_compat.h84
-rw-r--r--netwerk/srtp/src/crypto/include/key.h82
-rw-r--r--netwerk/srtp/src/crypto/include/null_auth.h68
-rw-r--r--netwerk/srtp/src/crypto/include/null_cipher.h80
-rw-r--r--netwerk/srtp/src/crypto/include/prng.h89
-rw-r--r--netwerk/srtp/src/crypto/include/rand_source.h91
-rw-r--r--netwerk/srtp/src/crypto/include/rdb.h129
-rw-r--r--netwerk/srtp/src/crypto/include/rdbx.h221
-rw-r--r--netwerk/srtp/src/crypto/include/sha1.h108
-rw-r--r--netwerk/srtp/src/crypto/include/stat.h69
-rw-r--r--netwerk/srtp/src/crypto/include/xfm.h174
-rw-r--r--netwerk/srtp/src/crypto/kernel/alloc.c121
-rw-r--r--netwerk/srtp/src/crypto/kernel/crypto_kernel.c573
-rw-r--r--netwerk/srtp/src/crypto/kernel/err.c149
-rw-r--r--netwerk/srtp/src/crypto/kernel/key.c115
-rw-r--r--netwerk/srtp/src/crypto/math/datatypes.c722
-rw-r--r--netwerk/srtp/src/crypto/math/math.c802
-rw-r--r--netwerk/srtp/src/crypto/math/stat.c402
-rw-r--r--netwerk/srtp/src/crypto/replay/rdb.c137
-rw-r--r--netwerk/srtp/src/crypto/replay/rdbx.c346
-rw-r--r--netwerk/srtp/src/crypto/replay/ut_sim.c105
-rw-r--r--netwerk/srtp/src/crypto/rng/ctr_prng.c108
-rw-r--r--netwerk/srtp/src/crypto/rng/prng.c180
-rw-r--r--netwerk/srtp/src/crypto/rng/rand_linux_kernel.c65
-rw-r--r--netwerk/srtp/src/crypto/rng/rand_source.c158
-rw-r--r--netwerk/srtp/src/crypto/test/aes_calc.c154
-rw-r--r--netwerk/srtp/src/crypto/test/auth_driver.c200
-rw-r--r--netwerk/srtp/src/crypto/test/cipher_driver.c531
-rw-r--r--netwerk/srtp/src/crypto/test/datatypes_driver.c237
-rw-r--r--netwerk/srtp/src/crypto/test/env.c99
-rw-r--r--netwerk/srtp/src/crypto/test/kernel_driver.c126
-rw-r--r--netwerk/srtp/src/crypto/test/rand_gen.c140
-rw-r--r--netwerk/srtp/src/crypto/test/sha1_driver.c550
-rw-r--r--netwerk/srtp/src/crypto/test/stat_driver.c174
-rw-r--r--netwerk/srtp/src/include/config.h6
-rw-r--r--netwerk/srtp/src/include/ekt.h201
-rw-r--r--netwerk/srtp/src/include/getopt_s.h60
-rw-r--r--netwerk/srtp/src/include/rtp.h139
-rw-r--r--netwerk/srtp/src/include/rtp_priv.h74
-rw-r--r--netwerk/srtp/src/include/srtp.h1006
-rw-r--r--netwerk/srtp/src/include/srtp_priv.h256
-rw-r--r--netwerk/srtp/src/include/ut_sim.h80
-rw-r--r--netwerk/srtp/src/moz.build69
-rw-r--r--netwerk/srtp/src/srtp/ekt.c276
-rw-r--r--netwerk/srtp/src/srtp/srtp.c2167
-rw-r--r--netwerk/srtp/srtp_update.log1
-rw-r--r--netwerk/srtp/update_srtp.sh32
-rw-r--r--netwerk/standalone/moz.build54
-rw-r--r--netwerk/standalone/nsNetModuleStandalone.cpp119
-rw-r--r--netwerk/standalone/nsNetModuleStandalone.h13
-rw-r--r--netwerk/streamconv/converters/ParseFTPList.cpp1888
-rw-r--r--netwerk/streamconv/converters/ParseFTPList.h104
-rw-r--r--netwerk/streamconv/converters/moz.build39
-rw-r--r--netwerk/streamconv/converters/mozTXTToHTMLConv.cpp1427
-rw-r--r--netwerk/streamconv/converters/mozTXTToHTMLConv.h294
-rw-r--r--netwerk/streamconv/converters/nsBinHexDecoder.cpp520
-rw-r--r--netwerk/streamconv/converters/nsBinHexDecoder.h126
-rw-r--r--netwerk/streamconv/converters/nsDirIndex.cpp122
-rw-r--r--netwerk/streamconv/converters/nsDirIndex.h30
-rw-r--r--netwerk/streamconv/converters/nsDirIndexParser.cpp427
-rw-r--r--netwerk/streamconv/converters/nsDirIndexParser.h67
-rw-r--r--netwerk/streamconv/converters/nsFTPDirListingConv.cpp345
-rw-r--r--netwerk/streamconv/converters/nsFTPDirListingConv.h52
-rw-r--r--netwerk/streamconv/converters/nsHTTPCompressConv.cpp659
-rw-r--r--netwerk/streamconv/converters/nsHTTPCompressConv.h135
-rw-r--r--netwerk/streamconv/converters/nsICompressConvStats.idl17
-rw-r--r--netwerk/streamconv/converters/nsIndexedToHTML.cpp895
-rw-r--r--netwerk/streamconv/converters/nsIndexedToHTML.h63
-rw-r--r--netwerk/streamconv/converters/nsMultiMixedConv.cpp1121
-rw-r--r--netwerk/streamconv/converters/nsMultiMixedConv.h176
-rw-r--r--netwerk/streamconv/converters/nsTXTToHTMLConv.cpp314
-rw-r--r--netwerk/streamconv/converters/nsTXTToHTMLConv.h89
-rw-r--r--netwerk/streamconv/converters/nsUnknownDecoder.cpp836
-rw-r--r--netwerk/streamconv/converters/nsUnknownDecoder.h159
-rw-r--r--netwerk/streamconv/converters/parse-ftp/3-guess.in30
-rw-r--r--netwerk/streamconv/converters/parse-ftp/3-guess.out28
-rw-r--r--netwerk/streamconv/converters/parse-ftp/C-VMold.in16
-rw-r--r--netwerk/streamconv/converters/parse-ftp/C-VMold.out12
-rw-r--r--netwerk/streamconv/converters/parse-ftp/C-zVM.in61
-rw-r--r--netwerk/streamconv/converters/parse-ftp/C-zVM.out19
-rw-r--r--netwerk/streamconv/converters/parse-ftp/D-WinNT.in60
-rw-r--r--netwerk/streamconv/converters/parse-ftp/D-WinNT.out47
-rw-r--r--netwerk/streamconv/converters/parse-ftp/E-EPLF.in189
-rw-r--r--netwerk/streamconv/converters/parse-ftp/E-EPLF.out174
-rw-r--r--netwerk/streamconv/converters/parse-ftp/O-guess.in19
-rw-r--r--netwerk/streamconv/converters/parse-ftp/O-guess.out13
-rw-r--r--netwerk/streamconv/converters/parse-ftp/R-dls.in23
-rw-r--r--netwerk/streamconv/converters/parse-ftp/R-dls.out22
-rw-r--r--netwerk/streamconv/converters/parse-ftp/README28
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-HellSoft.in25
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-HellSoft.out16
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-NetPresenz.in31
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-NetPresenz.out17
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-NetWare.in51
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-NetWare.out31
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-Novonyx.in92
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-Novonyx.out78
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-Surge.in66
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-Surge.out52
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-WarFTPd.in38
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-WarFTPd.out22
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-WebStar.in41
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-WebStar.out21
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-WinNT.in68
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-WinNT.out72
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-hethmon.in42
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-hethmon.out30
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-murksw.in29
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-murksw.out14
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-ncFTPd.in1411
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-ncFTPd.out1377
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-no_ug.in84
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-no_ug.out26
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-nogid.in82
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-nogid.out47
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-proftpd.in21
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-proftpd.out7
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-wu.in71
-rw-r--r--netwerk/streamconv/converters/parse-ftp/U-wu.out15
-rw-r--r--netwerk/streamconv/converters/parse-ftp/V-MultiNet.in34
-rw-r--r--netwerk/streamconv/converters/parse-ftp/V-MultiNet.out14
-rw-r--r--netwerk/streamconv/converters/parse-ftp/V-VMS-mix.in22
-rw-r--r--netwerk/streamconv/converters/parse-ftp/V-VMS-mix.out10
-rw-r--r--netwerk/streamconv/moz.build25
-rw-r--r--netwerk/streamconv/mozITXTToHTMLConv.idl88
-rw-r--r--netwerk/streamconv/nsIDirIndex.idl82
-rw-r--r--netwerk/streamconv/nsIDirIndexListener.idl79
-rw-r--r--netwerk/streamconv/nsIStreamConverter.idl100
-rw-r--r--netwerk/streamconv/nsIStreamConverterService.idl80
-rw-r--r--netwerk/streamconv/nsITXTToHTMLConv.idl25
-rw-r--r--netwerk/streamconv/nsStreamConverterService.cpp558
-rw-r--r--netwerk/streamconv/nsStreamConverterService.h46
-rw-r--r--netwerk/streamconv/test/Converters.cpp140
-rw-r--r--netwerk/streamconv/test/Converters.h52
-rw-r--r--netwerk/streamconv/test/TestStreamConv.cpp261
-rw-r--r--netwerk/streamconv/test/moz.build22
-rw-r--r--netwerk/system/android/moz.build14
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.cpp63
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.h24
-rw-r--r--netwerk/system/linux/moz.build12
-rw-r--r--netwerk/system/linux/nsNotifyAddrListener_Linux.cpp549
-rw-r--r--netwerk/system/linux/nsNotifyAddrListener_Linux.h100
-rw-r--r--netwerk/system/mac/moz.build14
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.h54
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.mm530
-rw-r--r--netwerk/system/moz.build17
-rw-r--r--netwerk/system/win32/moz.build12
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.cpp743
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.h100
-rw-r--r--netwerk/test/NetwerkTestLogging.h23
-rw-r--r--netwerk/test/PropertiesTest.cpp131
-rw-r--r--netwerk/test/ReadNTLM.cpp325
-rw-r--r--netwerk/test/TestBind.cpp210
-rw-r--r--netwerk/test/TestBlockingSocket.cpp127
-rw-r--r--netwerk/test/TestCacheBlockFiles.cpp873
-rw-r--r--netwerk/test/TestCachePrefixKeyParser.cpp78
-rw-r--r--netwerk/test/TestCommon.h51
-rw-r--r--netwerk/test/TestCookie.cpp921
-rw-r--r--netwerk/test/TestDNS.cpp130
-rw-r--r--netwerk/test/TestDNSDaemon.cpp274
-rw-r--r--netwerk/test/TestFileInput2.cpp480
-rw-r--r--netwerk/test/TestIDN.cpp52
-rw-r--r--netwerk/test/TestIOThreads.cpp75
-rw-r--r--netwerk/test/TestIncrementalDownload.cpp133
-rw-r--r--netwerk/test/TestMakeAbs.cpp69
-rw-r--r--netwerk/test/TestNamedPipeService.cpp334
-rw-r--r--netwerk/test/TestOpen.cpp90
-rw-r--r--netwerk/test/TestOverlappedIO.cpp313
-rw-r--r--netwerk/test/TestProtocols.cpp890
-rw-r--r--netwerk/test/TestServ.cpp146
-rw-r--r--netwerk/test/TestServ.js164
-rw-r--r--netwerk/test/TestSocketIO.cpp343
-rw-r--r--netwerk/test/TestSocketInput.cpp154
-rw-r--r--netwerk/test/TestSocketTransport.cpp307
-rw-r--r--netwerk/test/TestStreamLoader.cpp101
-rw-r--r--netwerk/test/TestStreamPump.cpp171
-rw-r--r--netwerk/test/TestStreamTransport.cpp319
-rw-r--r--netwerk/test/TestUDPSocket.cpp473
-rw-r--r--netwerk/test/TestUDPSocketProvider.cpp159
-rw-r--r--netwerk/test/TestURLManipulation.html130
-rw-r--r--netwerk/test/TestURLParser.cpp135
-rw-r--r--netwerk/test/TestUpload.cpp169
-rw-r--r--netwerk/test/TestWriteSpeed.cpp116
-rw-r--r--netwerk/test/browser/browser.ini11
-rw-r--r--netwerk/test/browser/browser_NetUtil.js92
-rw-r--r--netwerk/test/browser/browser_about_cache.js71
-rw-r--r--netwerk/test/browser/browser_child_resource.js256
-rw-r--r--netwerk/test/browser/browser_nsIFormPOSTActionChannel.js284
-rw-r--r--netwerk/test/browser/browser_post_file.js101
-rw-r--r--netwerk/test/browser/dummy.html7
-rw-r--r--netwerk/test/crashtests/1274044-1.html7
-rw-r--r--netwerk/test/crashtests/1334468-1.html25
-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.list4
-rw-r--r--netwerk/test/gtest/TestProtocolProxyService.cpp128
-rw-r--r--netwerk/test/gtest/TestStandardURL.cpp69
-rw-r--r--netwerk/test/gtest/moz.build14
-rw-r--r--netwerk/test/httpserver/README101
-rw-r--r--netwerk/test/httpserver/TODO17
-rw-r--r--netwerk/test/httpserver/httpd.js5376
-rw-r--r--netwerk/test/httpserver/httpd.manifest3
-rw-r--r--netwerk/test/httpserver/moz.build25
-rw-r--r--netwerk/test/httpserver/nsIHttpServer.idl620
-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.sjs48
-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.js600
-rw-r--r--netwerk/test/httpserver/test/test_async_response_sending.js1683
-rw-r--r--netwerk/test/httpserver/test/test_basic_functionality.js176
-rw-r--r--netwerk/test/httpserver/test/test_body_length.js64
-rw-r--r--netwerk/test/httpserver/test/test_byte_range.js278
-rw-r--r--netwerk/test/httpserver/test/test_cern_meta.js76
-rw-r--r--netwerk/test/httpserver/test/test_default_index_handler.js290
-rw-r--r--netwerk/test/httpserver/test/test_empty_body.js55
-rw-r--r--netwerk/test/httpserver/test/test_errorhandler_exception.js84
-rw-r--r--netwerk/test/httpserver/test/test_header_array.js67
-rw-r--r--netwerk/test/httpserver/test/test_headers.js189
-rw-r--r--netwerk/test/httpserver/test/test_host.js666
-rw-r--r--netwerk/test/httpserver/test/test_linedata.js20
-rw-r--r--netwerk/test/httpserver/test/test_load_module.js16
-rw-r--r--netwerk/test/httpserver/test/test_name_scheme.js90
-rw-r--r--netwerk/test/httpserver/test/test_processasync.js304
-rw-r--r--netwerk/test/httpserver/test/test_qi.js110
-rw-r--r--netwerk/test/httpserver/test/test_registerdirectory.js263
-rw-r--r--netwerk/test/httpserver/test/test_registerfile.js50
-rw-r--r--netwerk/test/httpserver/test/test_registerprefix.js127
-rw-r--r--netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js135
-rw-r--r--netwerk/test/httpserver/test/test_response_write.js55
-rw-r--r--netwerk/test/httpserver/test/test_seizepower.js182
-rw-r--r--netwerk/test/httpserver/test/test_setindexhandler.js67
-rw-r--r--netwerk/test/httpserver/test/test_setstatusline.js172
-rw-r--r--netwerk/test/httpserver/test/test_sjs.js251
-rw-r--r--netwerk/test/httpserver/test/test_sjs_object_state.js290
-rw-r--r--netwerk/test/httpserver/test/test_sjs_state.js186
-rw-r--r--netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js74
-rw-r--r--netwerk/test/httpserver/test/test_start_stop.js189
-rw-r--r--netwerk/test/httpserver/test/xpcshell.ini37
-rw-r--r--netwerk/test/mochitests/empty.html16
-rw-r--r--netwerk/test/mochitests/file_loadinfo_redirectchain.sjs106
-rw-r--r--netwerk/test/mochitests/method.sjs12
-rw-r--r--netwerk/test/mochitests/mochitest.ini26
-rw-r--r--netwerk/test/mochitests/partial_content.sjs151
-rw-r--r--netwerk/test/mochitests/redirect.sjs8
-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/rel_preconnect.sjs15
-rw-r--r--netwerk/test/mochitests/signed_web_packaged_app.sjs78
-rw-r--r--netwerk/test/mochitests/test_arraybufferinputstream.html89
-rw-r--r--netwerk/test/mochitests/test_idn_redirect.html36
-rw-r--r--netwerk/test/mochitests/test_loadinfo_redirectchain.html213
-rw-r--r--netwerk/test/mochitests/test_partially_cached_content.html92
-rw-r--r--netwerk/test/mochitests/test_redirect_ref.html30
-rw-r--r--netwerk/test/mochitests/test_rel_preconnect.html62
-rw-r--r--netwerk/test/mochitests/test_uri_scheme.html51
-rw-r--r--netwerk/test/mochitests/test_user_agent_overrides.html240
-rw-r--r--netwerk/test/mochitests/test_user_agent_updates.html369
-rw-r--r--netwerk/test/mochitests/test_user_agent_updates_reset.html44
-rw-r--r--netwerk/test/mochitests/test_viewsource_unlinkable.html27
-rw-r--r--netwerk/test/mochitests/test_xhr_method_case.html66
-rw-r--r--netwerk/test/mochitests/user_agent.sjs21
-rw-r--r--netwerk/test/mochitests/user_agent_update.sjs10
-rw-r--r--netwerk/test/mochitests/web_packaged_app.sjs35
-rw-r--r--netwerk/test/moz.build70
-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.html12
-rw-r--r--netwerk/test/reftest/bug565432-1.html18
-rw-r--r--netwerk/test/reftest/reftest-stylo.list3
-rw-r--r--netwerk/test/reftest/reftest.list2
-rw-r--r--netwerk/test/sites.txt257
-rw-r--r--netwerk/test/unit/CA.cert.derbin0 -> 827 bytes
-rw-r--r--netwerk/test/unit/CA.key.pem30
-rw-r--r--netwerk/test/unit/client_cert_chooser.js26
-rw-r--r--netwerk/test/unit/client_cert_chooser.manifest2
-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.js147
-rw-r--r--netwerk/test/unit/head_cache2.js429
-rw-r--r--netwerk/test/unit/head_channels.js218
-rw-r--r--netwerk/test/unit/socks_client_subprocess.js42
-rw-r--r--netwerk/test/unit/test_1073747.js30
-rw-r--r--netwerk/test/unit/test_304_responses.js95
-rw-r--r--netwerk/test/unit/test_307_redirect.js91
-rw-r--r--netwerk/test/unit/test_421.js60
-rw-r--r--netwerk/test/unit/test_MIME_params.js560
-rw-r--r--netwerk/test/unit/test_NetUtil.js867
-rw-r--r--netwerk/test/unit/test_URIs.js608
-rw-r--r--netwerk/test/unit/test_URIs2.js693
-rw-r--r--netwerk/test/unit/test_XHR_redirects.js235
-rw-r--r--netwerk/test/unit/test_about_networking.js96
-rw-r--r--netwerk/test/unit/test_about_protocol.js50
-rw-r--r--netwerk/test/unit/test_aboutblank.js32
-rw-r--r--netwerk/test/unit/test_addr_in_use_error.js32
-rw-r--r--netwerk/test/unit/test_alt-data_simple.js111
-rw-r--r--netwerk/test/unit/test_alt-data_stream.js120
-rw-r--r--netwerk/test/unit/test_altsvc.js378
-rw-r--r--netwerk/test/unit/test_assoc.js102
-rw-r--r--netwerk/test/unit/test_auth_dialog_permission.js255
-rw-r--r--netwerk/test/unit/test_auth_jar.js49
-rw-r--r--netwerk/test/unit/test_auth_proxy.js399
-rw-r--r--netwerk/test/unit/test_authentication.js2074
-rw-r--r--netwerk/test/unit/test_authpromptwrapper.js233
-rw-r--r--netwerk/test/unit/test_backgroundfilesaver.js731
-rw-r--r--netwerk/test/unit/test_be_conservative.js213
-rw-r--r--netwerk/test/unit/test_bug1064258.js153
-rw-r--r--netwerk/test/unit/test_bug1195415.js116
-rw-r--r--netwerk/test/unit/test_bug1218029.js82
-rw-r--r--netwerk/test/unit/test_bug1279246.js97
-rw-r--r--netwerk/test/unit/test_bug203271.js177
-rw-r--r--netwerk/test/unit/test_bug248970_cache.js151
-rw-r--r--netwerk/test/unit/test_bug248970_cookie.js135
-rw-r--r--netwerk/test/unit/test_bug261425.js26
-rw-r--r--netwerk/test/unit/test_bug263127.js61
-rw-r--r--netwerk/test/unit/test_bug282432.js42
-rw-r--r--netwerk/test/unit/test_bug321706.js11
-rw-r--r--netwerk/test/unit/test_bug331825.js42
-rw-r--r--netwerk/test/unit/test_bug336501.js27
-rw-r--r--netwerk/test/unit/test_bug337744.js114
-rw-r--r--netwerk/test/unit/test_bug365133.js111
-rw-r--r--netwerk/test/unit/test_bug368702.js150
-rw-r--r--netwerk/test/unit/test_bug369787.js71
-rw-r--r--netwerk/test/unit/test_bug371473.js44
-rw-r--r--netwerk/test/unit/test_bug376660.js72
-rw-r--r--netwerk/test/unit/test_bug376844.js21
-rw-r--r--netwerk/test/unit/test_bug376865.js20
-rw-r--r--netwerk/test/unit/test_bug379034.js18
-rw-r--r--netwerk/test/unit/test_bug380994.js22
-rw-r--r--netwerk/test/unit/test_bug388281.js24
-rw-r--r--netwerk/test/unit/test_bug396389.js71
-rw-r--r--netwerk/test/unit/test_bug401564.js48
-rw-r--r--netwerk/test/unit/test_bug411952.js35
-rw-r--r--netwerk/test/unit/test_bug412457.js44
-rw-r--r--netwerk/test/unit/test_bug412945.js42
-rw-r--r--netwerk/test/unit/test_bug414122.js58
-rw-r--r--netwerk/test/unit/test_bug427957.js106
-rw-r--r--netwerk/test/unit/test_bug429347.js38
-rw-r--r--netwerk/test/unit/test_bug455311.js128
-rw-r--r--netwerk/test/unit/test_bug455598.js35
-rw-r--r--netwerk/test/unit/test_bug464591.js81
-rw-r--r--netwerk/test/unit/test_bug468426.js100
-rw-r--r--netwerk/test/unit/test_bug468594.js127
-rw-r--r--netwerk/test/unit/test_bug470716.js174
-rw-r--r--netwerk/test/unit/test_bug477578.js50
-rw-r--r--netwerk/test/unit/test_bug479413.js59
-rw-r--r--netwerk/test/unit/test_bug479485.js47
-rw-r--r--netwerk/test/unit/test_bug482601.js233
-rw-r--r--netwerk/test/unit/test_bug484684.js115
-rw-r--r--netwerk/test/unit/test_bug490095.js116
-rw-r--r--netwerk/test/unit/test_bug504014.js69
-rw-r--r--netwerk/test/unit/test_bug510359.js77
-rw-r--r--netwerk/test/unit/test_bug515583.js73
-rw-r--r--netwerk/test/unit/test_bug528292.js90
-rw-r--r--netwerk/test/unit/test_bug536324_64bit_content_length.js64
-rw-r--r--netwerk/test/unit/test_bug540566.js18
-rw-r--r--netwerk/test/unit/test_bug543805.js93
-rw-r--r--netwerk/test/unit/test_bug553970.js44
-rw-r--r--netwerk/test/unit/test_bug561042.js38
-rw-r--r--netwerk/test/unit/test_bug561276.js68
-rw-r--r--netwerk/test/unit/test_bug580508.js26
-rw-r--r--netwerk/test/unit/test_bug586908.js92
-rw-r--r--netwerk/test/unit/test_bug596443.js97
-rw-r--r--netwerk/test/unit/test_bug618835.js115
-rw-r--r--netwerk/test/unit/test_bug633743.js186
-rw-r--r--netwerk/test/unit/test_bug650995.js159
-rw-r--r--netwerk/test/unit/test_bug652761.js17
-rw-r--r--netwerk/test/unit/test_bug654926.js88
-rw-r--r--netwerk/test/unit/test_bug654926_doom_and_read.js77
-rw-r--r--netwerk/test/unit/test_bug654926_test_seek.js63
-rw-r--r--netwerk/test/unit/test_bug659569.js57
-rw-r--r--netwerk/test/unit/test_bug660066.js42
-rw-r--r--netwerk/test/unit/test_bug667818.js21
-rw-r--r--netwerk/test/unit/test_bug667907.js84
-rw-r--r--netwerk/test/unit/test_bug669001.js160
-rw-r--r--netwerk/test/unit/test_bug767025.js275
-rw-r--r--netwerk/test/unit/test_bug770243.js207
-rw-r--r--netwerk/test/unit/test_bug812167.js127
-rw-r--r--netwerk/test/unit/test_bug826063.js107
-rw-r--r--netwerk/test/unit/test_bug856978.js135
-rw-r--r--netwerk/test/unit/test_bug894586.js158
-rw-r--r--netwerk/test/unit/test_bug935499.js7
-rw-r--r--netwerk/test/unit/test_cache-control_request.js385
-rw-r--r--netwerk/test/unit/test_cache2-00-service-get.js16
-rw-r--r--netwerk/test/unit/test_cache2-01-basic.js28
-rw-r--r--netwerk/test/unit/test_cache2-01a-basic-readonly.js28
-rw-r--r--netwerk/test/unit/test_cache2-01b-basic-datasize.js32
-rw-r--r--netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js28
-rw-r--r--netwerk/test/unit/test_cache2-01d-basic-not-wanted.js28
-rw-r--r--netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js35
-rw-r--r--netwerk/test/unit/test_cache2-01f-basic-openTruncate.js24
-rw-r--r--netwerk/test/unit/test_cache2-02-open-non-existing.js28
-rw-r--r--netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js24
-rw-r--r--netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js28
-rw-r--r--netwerk/test/unit/test_cache2-05-visit.js78
-rw-r--r--netwerk/test/unit/test_cache2-06-pb-mode.js41
-rw-r--r--netwerk/test/unit/test_cache2-07-visit-memory.js82
-rw-r--r--netwerk/test/unit/test_cache2-07a-open-memory.js53
-rw-r--r--netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js18
-rw-r--r--netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js21
-rw-r--r--netwerk/test/unit/test_cache2-10-evict-direct.js20
-rw-r--r--netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js21
-rw-r--r--netwerk/test/unit/test_cache2-11-evict-memory.js61
-rw-r--r--netwerk/test/unit/test_cache2-12-evict-disk.js61
-rw-r--r--netwerk/test/unit/test_cache2-13-evict-non-existing.js13
-rw-r--r--netwerk/test/unit/test_cache2-14-concurent-readers.js31
-rw-r--r--netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js48
-rw-r--r--netwerk/test/unit/test_cache2-15-conditional-304.js39
-rw-r--r--netwerk/test/unit/test_cache2-16-conditional-200.js52
-rw-r--r--netwerk/test/unit/test_cache2-17-evict-all.js17
-rw-r--r--netwerk/test/unit/test_cache2-18-not-valid.js30
-rw-r--r--netwerk/test/unit/test_cache2-19-range-206.js44
-rw-r--r--netwerk/test/unit/test_cache2-20-range-200.js45
-rw-r--r--netwerk/test/unit/test_cache2-21-anon-storage.js38
-rw-r--r--netwerk/test/unit/test_cache2-22-anon-visit.js58
-rw-r--r--netwerk/test/unit/test_cache2-23-read-over-chunk.js35
-rw-r--r--netwerk/test/unit/test_cache2-24-exists.js38
-rw-r--r--netwerk/test/unit/test_cache2-25-chunk-memory-limit.js51
-rw-r--r--netwerk/test/unit/test_cache2-26-no-outputstream-open.js27
-rw-r--r--netwerk/test/unit/test_cache2-27-force-valid-for.js37
-rw-r--r--netwerk/test/unit/test_cache2-28-last-access-attrs.js39
-rw-r--r--netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js34
-rw-r--r--netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js72
-rw-r--r--netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js71
-rw-r--r--netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js91
-rw-r--r--netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js95
-rw-r--r--netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js90
-rw-r--r--netwerk/test/unit/test_cache2-30a-entry-pinning.js32
-rw-r--r--netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js38
-rw-r--r--netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js134
-rw-r--r--netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js113
-rw-r--r--netwerk/test/unit/test_cacheForOfflineUse_no-store.js93
-rw-r--r--netwerk/test/unit/test_cache_jar.js126
-rw-r--r--netwerk/test/unit/test_cacheflags.js370
-rw-r--r--netwerk/test/unit/test_channel_close.js59
-rw-r--r--netwerk/test/unit/test_chunked_responses.js175
-rw-r--r--netwerk/test/unit/test_compareURIs.js49
-rw-r--r--netwerk/test/unit/test_compressappend.js80
-rw-r--r--netwerk/test/unit/test_content_encoding_gzip.js114
-rw-r--r--netwerk/test/unit/test_content_length_underrun.js278
-rw-r--r--netwerk/test/unit/test_content_sniffer.js131
-rw-r--r--netwerk/test/unit/test_cookie_blacklist.js19
-rw-r--r--netwerk/test/unit/test_cookie_header.js100
-rw-r--r--netwerk/test/unit/test_cookiejars.js149
-rw-r--r--netwerk/test/unit/test_cookiejars_safebrowsing.js178
-rw-r--r--netwerk/test/unit/test_data_protocol.js58
-rw-r--r--netwerk/test/unit/test_dns_cancel.js83
-rw-r--r--netwerk/test/unit/test_dns_disable_ipv4.js40
-rw-r--r--netwerk/test/unit/test_dns_disable_ipv6.js41
-rw-r--r--netwerk/test/unit/test_dns_localredirect.js31
-rw-r--r--netwerk/test/unit/test_dns_offline.js74
-rw-r--r--netwerk/test/unit/test_dns_onion.js70
-rw-r--r--netwerk/test/unit/test_dns_per_interface.js79
-rw-r--r--netwerk/test/unit/test_dns_proxy_bypass.js77
-rw-r--r--netwerk/test/unit/test_dns_service.js26
-rw-r--r--netwerk/test/unit/test_doomentry.js97
-rw-r--r--netwerk/test/unit/test_duplicate_headers.js605
-rw-r--r--netwerk/test/unit/test_event_sink.js170
-rw-r--r--netwerk/test/unit/test_extract_charset_from_content_type.js163
-rw-r--r--netwerk/test/unit/test_fallback_no-cache-entry_canceled.js112
-rw-r--r--netwerk/test/unit/test_fallback_no-cache-entry_passing.js110
-rw-r--r--netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js115
-rw-r--r--netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js114
-rw-r--r--netwerk/test/unit/test_fallback_request-error_canceled.js121
-rw-r--r--netwerk/test/unit/test_fallback_request-error_passing.js119
-rw-r--r--netwerk/test/unit/test_fallback_response-error_canceled.js116
-rw-r--r--netwerk/test/unit/test_fallback_response-error_passing.js114
-rw-r--r--netwerk/test/unit/test_file_partial_inputstream.js512
-rw-r--r--netwerk/test/unit/test_file_protocol.js251
-rw-r--r--netwerk/test/unit/test_filestreams.js298
-rw-r--r--netwerk/test/unit/test_freshconnection.js30
-rw-r--r--netwerk/test/unit/test_getHost.js68
-rw-r--r--netwerk/test/unit/test_gre_resources.js31
-rw-r--r--netwerk/test/unit/test_gzipped_206.js94
-rw-r--r--netwerk/test/unit/test_head.js150
-rw-r--r--netwerk/test/unit/test_header_Accept-Language.js91
-rw-r--r--netwerk/test/unit/test_header_Accept-Language_case.js47
-rw-r--r--netwerk/test/unit/test_headers.js186
-rw-r--r--netwerk/test/unit/test_http2.js1119
-rw-r--r--netwerk/test/unit/test_httpResponseTimeout.js162
-rw-r--r--netwerk/test/unit/test_http_headers.js70
-rw-r--r--netwerk/test/unit/test_httpauth.js99
-rw-r--r--netwerk/test/unit/test_httpcancel.js114
-rw-r--r--netwerk/test/unit/test_httpsuspend.js80
-rw-r--r--netwerk/test/unit/test_idn_blacklist.js170
-rw-r--r--netwerk/test/unit/test_idn_urls.js345
-rw-r--r--netwerk/test/unit/test_idna2008.js60
-rw-r--r--netwerk/test/unit/test_idnservice.js25
-rw-r--r--netwerk/test/unit/test_immutable.js180
-rw-r--r--netwerk/test/unit/test_inhibit_caching.js76
-rw-r--r--netwerk/test/unit/test_large_port.js36
-rw-r--r--netwerk/test/unit/test_link.desktop3
-rw-r--r--netwerk/test/unit/test_link.url5
-rw-r--r--netwerk/test/unit/test_localstreams.js87
-rw-r--r--netwerk/test/unit/test_mismatch_last-modified.js154
-rw-r--r--netwerk/test/unit/test_mozTXTToHTMLConv.js199
-rw-r--r--netwerk/test/unit/test_multipart_byteranges.js113
-rw-r--r--netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js109
-rw-r--r--netwerk/test/unit/test_multipart_streamconv.js93
-rw-r--r--netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js89
-rw-r--r--netwerk/test/unit/test_nestedabout_serialize.js35
-rw-r--r--netwerk/test/unit/test_net_addr.js199
-rw-r--r--netwerk/test/unit/test_nojsredir.js62
-rw-r--r--netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js179
-rw-r--r--netwerk/test/unit/test_offline_status.js15
-rw-r--r--netwerk/test/unit/test_offlinecache_custom-directory.js151
-rw-r--r--netwerk/test/unit/test_original_sent_received_head.js220
-rw-r--r--netwerk/test/unit/test_parse_content_type.js200
-rw-r--r--netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js84
-rw-r--r--netwerk/test/unit/test_permmgr.js119
-rw-r--r--netwerk/test/unit/test_ping_aboutnetworking.js86
-rw-r--r--netwerk/test/unit/test_pinned_app_cache.js277
-rw-r--r--netwerk/test/unit/test_plaintext_sniff.js194
-rw-r--r--netwerk/test/unit/test_post.js120
-rw-r--r--netwerk/test/unit/test_predictor.js596
-rw-r--r--netwerk/test/unit/test_private_cookie_changed.js34
-rw-r--r--netwerk/test/unit/test_private_necko_channel.js53
-rw-r--r--netwerk/test/unit/test_progress.js128
-rw-r--r--netwerk/test/unit/test_protocolproxyservice.js958
-rw-r--r--netwerk/test/unit/test_proxy-failover_canceled.js53
-rw-r--r--netwerk/test/unit/test_proxy-failover_passing.js43
-rw-r--r--netwerk/test/unit/test_proxy-replace_canceled.js55
-rw-r--r--netwerk/test/unit/test_proxy-replace_passing.js43
-rw-r--r--netwerk/test/unit/test_psl.js36
-rw-r--r--netwerk/test/unit/test_range_requests.js434
-rw-r--r--netwerk/test/unit/test_readline.js60
-rw-r--r--netwerk/test/unit/test_redirect-caching_canceled.js68
-rw-r--r--netwerk/test/unit/test_redirect-caching_failure.js55
-rw-r--r--netwerk/test/unit/test_redirect-caching_passing.js59
-rw-r--r--netwerk/test/unit/test_redirect_baduri.js42
-rw-r--r--netwerk/test/unit/test_redirect_canceled.js51
-rw-r--r--netwerk/test/unit/test_redirect_different-protocol.js51
-rw-r--r--netwerk/test/unit/test_redirect_failure.js45
-rw-r--r--netwerk/test/unit/test_redirect_from_script.js258
-rw-r--r--netwerk/test/unit/test_redirect_from_script_after-open_passing.js258
-rw-r--r--netwerk/test/unit/test_redirect_history.js64
-rw-r--r--netwerk/test/unit/test_redirect_loop.js86
-rw-r--r--netwerk/test/unit/test_redirect_passing.js57
-rw-r--r--netwerk/test/unit/test_reentrancy.js105
-rw-r--r--netwerk/test/unit/test_referrer.js109
-rw-r--r--netwerk/test/unit/test_referrer_policy.js95
-rw-r--r--netwerk/test/unit/test_reopen.js141
-rw-r--r--netwerk/test/unit/test_reply_without_content_type.js91
-rw-r--r--netwerk/test/unit/test_resumable_channel.js402
-rw-r--r--netwerk/test/unit/test_resumable_truncate.js88
-rw-r--r--netwerk/test/unit/test_safeoutputstream.js66
-rw-r--r--netwerk/test/unit/test_safeoutputstream_append.js42
-rw-r--r--netwerk/test/unit/test_separate_connections.js99
-rw-r--r--netwerk/test/unit/test_signature_extraction.js206
-rw-r--r--netwerk/test/unit/test_simple.js56
-rw-r--r--netwerk/test/unit/test_sockettransportsvc_available.js8
-rw-r--r--netwerk/test/unit/test_socks.js525
-rw-r--r--netwerk/test/unit/test_speculative_connect.js333
-rw-r--r--netwerk/test/unit/test_standardurl.js455
-rw-r--r--netwerk/test/unit/test_standardurl_default_port.js51
-rw-r--r--netwerk/test/unit/test_standardurl_port.js56
-rw-r--r--netwerk/test/unit/test_streamcopier.js53
-rw-r--r--netwerk/test/unit/test_suspend_channel_before_connect.js102
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_modified.js175
-rw-r--r--netwerk/test/unit/test_synthesized_response.js243
-rw-r--r--netwerk/test/unit/test_throttlechannel.js41
-rw-r--r--netwerk/test/unit/test_throttlequeue.js23
-rw-r--r--netwerk/test/unit/test_throttling.js57
-rw-r--r--netwerk/test/unit/test_tldservice_nextsubdomain.js28
-rw-r--r--netwerk/test/unit/test_tls_server.js237
-rw-r--r--netwerk/test/unit/test_tls_server_multiple_clients.js141
-rw-r--r--netwerk/test/unit/test_traceable_channel.js150
-rw-r--r--netwerk/test/unit/test_udp_multicast.js114
-rw-r--r--netwerk/test/unit/test_udpsocket.js63
-rw-r--r--netwerk/test/unit/test_unescapestring.js31
-rw-r--r--netwerk/test/unit/test_unix_domain.js545
-rw-r--r--netwerk/test/unit/test_websocket_offline.js51
-rw-r--r--netwerk/test/unit/test_xmlhttprequest.js54
-rw-r--r--netwerk/test/unit/xpcshell.ini369
-rw-r--r--netwerk/test/unit_ipc/child_app_offline_notifications.js43
-rw-r--r--netwerk/test/unit_ipc/child_channel_id.js45
-rw-r--r--netwerk/test/unit_ipc/head_cc.js4
-rw-r--r--netwerk/test/unit_ipc/head_channels_clone.js8
-rw-r--r--netwerk/test/unit_ipc/test_XHR_redirects.js3
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_simple_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_stream_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_bug528292_wrap.js6
-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.js109
-rw-r--r--netwerk/test/unit_ipc/test_chunked_responses_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_cookie_header_wrap.js11
-rw-r--r--netwerk/test/unit_ipc/test_cookiejars_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_dns_cancel_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_dns_per_interface_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_httpsuspend_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.js36
-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.js7
-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.js7
-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_synthesized_response_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/xpcshell.ini99
-rw-r--r--netwerk/test/urlparse.dat103
-rw-r--r--netwerk/test/urlparse_mac.dat14
-rw-r--r--netwerk/test/urlparse_unx.dat34
-rw-r--r--netwerk/test/urlparse_win.dat60
-rw-r--r--netwerk/test/urltest.cpp461
-rw-r--r--netwerk/test/urltests.dat43
-rw-r--r--netwerk/wifi/moz.build65
-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.cpp95
-rw-r--r--netwerk/wifi/nsWifiAccessPoint.h86
-rw-r--r--netwerk/wifi/nsWifiMonitor.cpp265
-rw-r--r--netwerk/wifi/nsWifiMonitor.h110
-rw-r--r--netwerk/wifi/nsWifiMonitorGonk.cpp181
-rw-r--r--netwerk/wifi/nsWifiScannerDBus.cpp337
-rw-r--r--netwerk/wifi/nsWifiScannerDBus.h45
-rw-r--r--netwerk/wifi/nsWifiScannerFreeBSD.cpp171
-rw-r--r--netwerk/wifi/nsWifiScannerMac.cpp55
-rw-r--r--netwerk/wifi/nsWifiScannerSolaris.cpp149
-rw-r--r--netwerk/wifi/nsWifiScannerWin.cpp77
-rw-r--r--netwerk/wifi/osx_corewlan.mm108
-rw-r--r--netwerk/wifi/osx_wifi.h120
-rw-r--r--netwerk/wifi/tests/wifi_access_point_test.html60
-rw-r--r--netwerk/wifi/win_wifiScanner.cpp195
-rw-r--r--netwerk/wifi/win_wifiScanner.h41
-rw-r--r--netwerk/wifi/win_wlanLibrary.cpp155
-rw-r--r--netwerk/wifi/win_wlanLibrary.h61
-rw-r--r--netwerk/wifi/win_xp_wifiScanner.cpp399
-rw-r--r--netwerk/wifi/win_xp_wifiScanner.h25
1555 files changed, 457763 insertions, 0 deletions
diff --git a/netwerk/base/ADivertableParentChannel.h b/netwerk/base/ADivertableParentChannel.h
new file mode 100644
index 0000000000..2102d3d833
--- /dev/null
+++ b/netwerk/base/ADivertableParentChannel.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _adivertablechannelparent_h_
+#define _adivertablechannelparent_h_
+
+#include "nsISupports.h"
+
+class nsIStreamListener;
+
+namespace mozilla {
+namespace net {
+
+// To be implemented by a channel's parent actors, e.g. HttpChannelParent
+// and FTPChannelParent. Used by ChannelDiverterParent to divert
+// nsIStreamListener callbacks from the child process to a new
+// listener in the parent process.
+class ADivertableParentChannel : public nsISupports
+{
+public:
+ // Called by ChannelDiverterParent::DivertTo(nsIStreamListener*).
+ // The listener should now be used to received nsIStreamListener callbacks,
+ // i.e. OnStartRequest, OnDataAvailable and OnStopRequest, as if it had been
+ // passed to AsyncOpen for the channel. A reference to the listener will be
+ // added and kept until OnStopRequest has completed.
+ virtual void DivertTo(nsIStreamListener *aListener) = 0;
+
+ // Called to suspend parent channel in ChannelDiverterParent constructor.
+ virtual nsresult SuspendForDiversion() = 0;
+
+ // While messages are diverted back from the child to the parent calls to
+ // suspend/resume the channel must also suspend/resume the message diversion.
+ // These two functions will be called by nsHttpChannel and nsFtpChannel
+ // Suspend()/Resume() functions.
+ virtual nsresult SuspendMessageDiversion() = 0;
+ virtual nsresult ResumeMessageDiversion() = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h
new file mode 100644
index 0000000000..0b2726c43c
--- /dev/null
+++ b/netwerk/base/ARefBase.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ARefBase_h
+#define mozilla_net_ARefBase_h
+
+#include "nscore.h"
+
+namespace mozilla { namespace net {
+
+// This is an abstract class that can be pointed to by either
+// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic
+// objects that need to be reference counted - similiar to nsISupports
+// but it may or may not be xpcom.
+
+class ARefBase
+{
+public:
+ ARefBase() {}
+ virtual ~ARefBase() {}
+
+ NS_IMETHOD_ (MozExternalRefCountType) AddRef() = 0;
+ NS_IMETHOD_ (MozExternalRefCountType) Release() = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ArrayBufferInputStream.cpp b/netwerk/base/ArrayBufferInputStream.cpp
new file mode 100644
index 0000000000..d70c45110f
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include "ArrayBufferInputStream.h"
+#include "nsStreamUtils.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream);
+
+ArrayBufferInputStream::ArrayBufferInputStream()
+: mBufferLength(0)
+, mPos(0)
+, mClosed(false)
+{
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::SetData(JS::Handle<JS::Value> aBuffer,
+ uint32_t aByteOffset,
+ uint32_t aLength,
+ JSContext* aCx)
+{
+ if (!aBuffer.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::RootedObject arrayBuffer(aCx, &aBuffer.toObject());
+ if (!JS_IsArrayBufferObject(arrayBuffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t buflen = JS_GetArrayBufferByteLength(arrayBuffer);
+ uint32_t offset = std::min(buflen, aByteOffset);
+ mBufferLength = std::min(buflen - offset, aLength);
+
+ mArrayBuffer = mozilla::MakeUnique<char[]>(mBufferLength);
+
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ char* src = (char*) JS_GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
+ memcpy(&mArrayBuffer[0], src, mBufferLength);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Close()
+{
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Available(uint64_t* aCount)
+{
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ if (mArrayBuffer) {
+ *aCount = mBufferLength ? mBufferLength - mPos : 0;
+ } else {
+ *aCount = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, uint32_t *aReadCount)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t aCount, uint32_t *result)
+{
+ NS_ASSERTION(result, "null ptr");
+ NS_ASSERTION(mBufferLength >= mPos, "bad stream state");
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength), "stream inited incorrectly");
+
+ *result = 0;
+ while (mPos < mBufferLength) {
+ uint32_t remaining = mBufferLength - mPos;
+ MOZ_ASSERT(mArrayBuffer);
+
+ uint32_t count = std::min(aCount, remaining);
+ if (count == 0) {
+ break;
+ }
+
+ uint32_t written;
+ nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count, &written);
+ if (NS_FAILED(rv)) {
+ // InputStreams do not propagate errors to caller.
+ return NS_OK;
+ }
+
+ NS_ASSERTION(written <= count,
+ "writer should not write more than we asked it to write");
+ mPos += written;
+ *result += written;
+ aCount -= written;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = true;
+ return NS_OK;
+}
diff --git a/netwerk/base/ArrayBufferInputStream.h b/netwerk/base/ArrayBufferInputStream.h
new file mode 100644
index 0000000000..ddd3e7cefb
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ArrayBufferInputStream_h
+#define ArrayBufferInputStream_h
+
+#include "nsIArrayBufferInputStream.h"
+#include "js/Value.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+
+#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID "@mozilla.org/io/arraybuffer-input-stream;1"
+#define NS_ARRAYBUFFERINPUTSTREAM_CID \
+{ /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */ \
+ 0x3014dde6, \
+ 0xaa1c, \
+ 0x41db, \
+ {0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6} \
+}
+
+class ArrayBufferInputStream : public nsIArrayBufferInputStream {
+public:
+ ArrayBufferInputStream();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIARRAYBUFFERINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAM
+
+private:
+ virtual ~ArrayBufferInputStream() {}
+ mozilla::UniquePtr<char[]> mArrayBuffer;
+ uint32_t mBufferLength;
+ uint32_t mPos;
+ bool mClosed;
+};
+
+#endif // ArrayBufferInputStream_h
diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h
new file mode 100644
index 0000000000..43ab27133f
--- /dev/null
+++ b/netwerk/base/AutoClose.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AutoClose_h
+#define mozilla_net_AutoClose_h
+
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla { namespace net {
+
+// Like an nsAutoPtr for XPCOM streams (e.g. nsIAsyncInputStream) and other
+// refcounted classes that need to have the Close() method called explicitly
+// before they are destroyed.
+template <typename T>
+class AutoClose
+{
+public:
+ AutoClose() : mMutex("net::AutoClose.mMutex") { }
+ ~AutoClose(){
+ CloseAndRelease();
+ }
+
+ explicit operator bool()
+ {
+ MutexAutoLock lock(mMutex);
+ return mPtr;
+ }
+
+ already_AddRefed<T> forget()
+ {
+ MutexAutoLock lock(mMutex);
+ return mPtr.forget();
+ }
+
+ void takeOver(nsCOMPtr<T> & rhs)
+ {
+ already_AddRefed<T> other = rhs.forget();
+ TakeOverInternal(&other);
+ }
+
+ void CloseAndRelease()
+ {
+ TakeOverInternal(nullptr);
+ }
+
+private:
+ void TakeOverInternal(already_AddRefed<T> *aOther)
+ {
+ nsCOMPtr<T> ptr;
+ {
+ MutexAutoLock lock(mMutex);
+ ptr.swap(mPtr);
+ if (aOther) {
+ mPtr = *aOther;
+ }
+ }
+
+ if (ptr) {
+ ptr->Close();
+ }
+ }
+
+ void operator=(const AutoClose<T> &) = delete;
+ AutoClose(const AutoClose<T> &) = delete;
+
+ nsCOMPtr<T> mPtr;
+ Mutex mMutex;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AutoClose_h
diff --git a/netwerk/base/BackgroundFileSaver.cpp b/netwerk/base/BackgroundFileSaver.cpp
new file mode 100644
index 0000000000..e4bc058262
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.cpp
@@ -0,0 +1,1273 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BackgroundFileSaver.h"
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Telemetry.h"
+#include "nsCOMArray.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIFile.h"
+#include "nsIMutableArray.h"
+#include "nsIPipe.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsIX509CertList.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "pk11pub.h"
+#include "secoidt.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+#include <softpub.h>
+#include <wintrust.h>
+#endif // XP_WIN
+
+namespace mozilla {
+namespace net {
+
+// MOZ_LOG=BackgroundFileSaver:5
+static LazyLogModule prlog("BackgroundFileSaver");
+#define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+/**
+ * Buffer size for writing to the output file or reading from the input file.
+ */
+#define BUFFERED_IO_SIZE (1024 * 32)
+
+/**
+ * When this upper limit is reached, the original request is suspended.
+ */
+#define REQUEST_SUSPEND_AT (1024 * 1024 * 4)
+
+/**
+ * When this lower limit is reached, the original request is resumed.
+ */
+#define REQUEST_RESUME_AT (1024 * 1024 * 2)
+
+////////////////////////////////////////////////////////////////////////////////
+//// NotifyTargetChangeRunnable
+
+/**
+ * Runnable object used to notify the control thread that file contents will now
+ * be saved to the specified file.
+ */
+class NotifyTargetChangeRunnable final : public Runnable
+{
+public:
+ NotifyTargetChangeRunnable(BackgroundFileSaver *aSaver, nsIFile *aTarget)
+ : mSaver(aSaver)
+ , mTarget(aTarget)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ return mSaver->NotifyTargetChange(mTarget);
+ }
+
+private:
+ RefPtr<BackgroundFileSaver> mSaver;
+ nsCOMPtr<nsIFile> mTarget;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaver
+
+uint32_t BackgroundFileSaver::sThreadCount = 0;
+uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0;
+
+BackgroundFileSaver::BackgroundFileSaver()
+: mControlThread(nullptr)
+, mWorkerThread(nullptr)
+, mPipeOutputStream(nullptr)
+, mPipeInputStream(nullptr)
+, mObserver(nullptr)
+, mLock("BackgroundFileSaver.mLock")
+, mWorkerThreadAttentionRequested(false)
+, mFinishRequested(false)
+, mComplete(false)
+, mStatus(NS_OK)
+, mAppend(false)
+, mInitialTarget(nullptr)
+, mInitialTargetKeepPartial(false)
+, mRenamedTarget(nullptr)
+, mRenamedTargetKeepPartial(false)
+, mAsyncCopyContext(nullptr)
+, mSha256Enabled(false)
+, mSignatureInfoEnabled(false)
+, mActualTarget(nullptr)
+, mActualTargetKeepPartial(false)
+, mDigestContext(nullptr)
+{
+ LOG(("Created BackgroundFileSaver [this = %p]", this));
+}
+
+BackgroundFileSaver::~BackgroundFileSaver()
+{
+ LOG(("Destroying BackgroundFileSaver [this = %p]", this));
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+void
+BackgroundFileSaver::destructorSafeDestroyNSSReference()
+{
+ mDigestContext = nullptr;
+}
+
+void
+BackgroundFileSaver::virtualDestroyNSSReference()
+{
+ destructorSafeDestroyNSSReference();
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaver::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ nsresult rv;
+
+ rv = NS_NewPipe2(getter_AddRefs(mPipeInputStream),
+ getter_AddRefs(mPipeOutputStream), true, true, 0,
+ HasInfiniteBuffer() ? UINT32_MAX : 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetCurrentThread(getter_AddRefs(mControlThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewThread(getter_AddRefs(mWorkerThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sThreadCount++;
+ if (sThreadCount > sTelemetryMaxThreadCount) {
+ sTelemetryMaxThreadCount = sThreadCount;
+ }
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver **aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+ *aObserver = mObserver;
+ NS_IF_ADDREF(*aObserver);
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver *aObserver)
+{
+ mObserver = aObserver;
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::EnableAppend()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ MutexAutoLock lock(mLock);
+ mAppend = true;
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetTarget(nsIFile *aTarget, bool aKeepPartial)
+{
+ NS_ENSURE_ARG(aTarget);
+ {
+ MutexAutoLock lock(mLock);
+ if (!mInitialTarget) {
+ aTarget->Clone(getter_AddRefs(mInitialTarget));
+ mInitialTargetKeepPartial = aKeepPartial;
+ } else {
+ aTarget->Clone(getter_AddRefs(mRenamedTarget));
+ mRenamedTargetKeepPartial = aKeepPartial;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // rename or create the target file as requested, and start copying data.
+ return GetWorkerThreadAttention(true);
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::Finish(nsresult aStatus)
+{
+ nsresult rv;
+
+ // This will cause the NS_AsyncCopy operation, if it's in progress, to consume
+ // all the data that is still in the pipe, and then finish.
+ rv = mPipeOutputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure that, when we get attention from the worker thread, if no pending
+ // rename operation is waiting, the operation will complete.
+ {
+ MutexAutoLock lock(mLock);
+ mFinishRequested = true;
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // process the completion conditions, detect that completion is requested, and
+ // notify the main thread of the completion. If this function was called with
+ // a success code, we wait for the copy to finish before processing the
+ // completion conditions, otherwise we interrupt the copy immediately.
+ return GetWorkerThreadAttention(NS_FAILED(aStatus));
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSha256()
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable sha256 or initialize NSS off the main thread");
+ // Ensure Personal Security Manager is initialized. This is required for
+ // PK11_* operations to work.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSha256Enabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSha256Hash(nsACString& aHash)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread");
+ // We acquire a lock because mSha256 is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (mSha256.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aHash = mSha256;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSignatureInfo()
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable signature extraction off the main thread");
+ // Ensure Personal Security Manager is initialized.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSignatureInfoEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSignatureInfo(nsIArray** aSignatureInfo)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread");
+ // We acquire a lock because mSignatureInfo is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (!mComplete || !mSignatureInfoEnabled) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<nsIMutableArray> sigArray = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ for (int i = 0; i < mSignatureInfo.Count(); ++i) {
+ sigArray->AppendElement(mSignatureInfo[i], false);
+ }
+ *aSignatureInfo = sigArray;
+ NS_IF_ADDREF(*aSignatureInfo);
+ return NS_OK;
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaver::GetWorkerThreadAttention(bool aShouldInterruptCopy)
+{
+ nsresult rv;
+
+ MutexAutoLock lock(mLock);
+
+ // We only require attention one time. If this function is called two times
+ // before the worker thread wakes up, and the first has aShouldInterruptCopy
+ // false and the second true, we won't forcibly interrupt the copy from the
+ // control thread. However, that never happens, because calling Finish with a
+ // success code is the only case that may result in aShouldInterruptCopy being
+ // false. In that case, we won't call this function again, because consumers
+ // should not invoke other methods on the control thread after calling Finish.
+ // And in any case, Finish already closes one end of the pipe, causing the
+ // copy to finish properly on its own.
+ if (mWorkerThreadAttentionRequested) {
+ return NS_OK;
+ }
+
+ if (!mAsyncCopyContext) {
+ // Copy is not in progress, post an event to handle the change manually.
+ rv = mWorkerThread->Dispatch(NewRunnableMethod(this,
+ &BackgroundFileSaver::ProcessAttention),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (aShouldInterruptCopy) {
+ // Interrupt the copy. The copy will be resumed, if needed, by the
+ // ProcessAttention function, invoked by the AsyncCopyCallback function.
+ NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
+ }
+
+ // Indicate that attention has been requested successfully, there is no need
+ // to post another event until the worker thread processes the current one.
+ mWorkerThreadAttentionRequested = true;
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+// static
+void
+BackgroundFileSaver::AsyncCopyCallback(void *aClosure, nsresult aStatus)
+{
+ BackgroundFileSaver *self = (BackgroundFileSaver *)aClosure;
+ {
+ MutexAutoLock lock(self->mLock);
+
+ // Now that the copy was interrupted or terminated, any notification from
+ // the control thread requires an event to be posted to the worker thread.
+ self->mAsyncCopyContext = nullptr;
+
+ // When detecting failures, ignore the status code we use to interrupt.
+ if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT &&
+ NS_SUCCEEDED(self->mStatus)) {
+ self->mStatus = aStatus;
+ }
+ }
+
+ (void)self->ProcessAttention();
+
+ // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object
+ // alive even if other references disappeared. At this point, we've finished
+ // using the object and can safely release our reference.
+ NS_RELEASE(self);
+}
+
+// Called on the worker thread.
+nsresult
+BackgroundFileSaver::ProcessAttention()
+{
+ nsresult rv;
+
+ // This function is called whenever the attention of the worker thread has
+ // been requested. This may happen in these cases:
+ // * We are about to start the copy for the first time. In this case, we are
+ // called from an event posted on the worker thread from the control thread
+ // by GetWorkerThreadAttention, and mAsyncCopyContext is null.
+ // * We have interrupted the copy for some reason. In this case, we are
+ // called by AsyncCopyCallback, and mAsyncCopyContext is null.
+ // * We are currently executing ProcessStateChange, and attention is requested
+ // by the control thread, for example because SetTarget or Finish have been
+ // called. In this case, we are called from from an event posted through
+ // GetWorkerThreadAttention. While mAsyncCopyContext was always null when
+ // the event was posted, at this point mAsyncCopyContext may not be null
+ // anymore, because ProcessStateChange may have started the copy before the
+ // event that called this function was processed on the worker thread.
+ // If mAsyncCopyContext is not null, we interrupt the copy and re-enter
+ // through AsyncCopyCallback. This allows us to check if, for instance, we
+ // should rename the target file. We will then restart the copy if needed.
+ if (mAsyncCopyContext) {
+ NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
+ return NS_OK;
+ }
+ // Use the current shared state to determine the next operation to execute.
+ rv = ProcessStateChange();
+ if (NS_FAILED(rv)) {
+ // If something failed while processing, terminate the operation now.
+ {
+ MutexAutoLock lock(mLock);
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+ }
+ // Ensure we notify completion now that the operation failed.
+ CheckCompletion();
+ }
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+nsresult
+BackgroundFileSaver::ProcessStateChange()
+{
+ nsresult rv;
+
+ // We might have been notified because the operation is complete, verify.
+ if (CheckCompletion()) {
+ return NS_OK;
+ }
+
+ // Get a copy of the current shared state for the worker thread.
+ nsCOMPtr<nsIFile> initialTarget;
+ bool initialTargetKeepPartial;
+ nsCOMPtr<nsIFile> renamedTarget;
+ bool renamedTargetKeepPartial;
+ bool sha256Enabled;
+ bool append;
+ {
+ MutexAutoLock lock(mLock);
+
+ initialTarget = mInitialTarget;
+ initialTargetKeepPartial = mInitialTargetKeepPartial;
+ renamedTarget = mRenamedTarget;
+ renamedTargetKeepPartial = mRenamedTargetKeepPartial;
+ sha256Enabled = mSha256Enabled;
+ append = mAppend;
+
+ // From now on, another attention event needs to be posted if state changes.
+ mWorkerThreadAttentionRequested = false;
+ }
+
+ // The initial target can only be null if it has never been assigned. In this
+ // case, there is nothing to do since we never created any output file.
+ if (!initialTarget) {
+ return NS_OK;
+ }
+
+ // Determine if we are processing the attention request for the first time.
+ bool isContinuation = !!mActualTarget;
+ if (!isContinuation) {
+ // Assign the target file for the first time.
+ mActualTarget = initialTarget;
+ mActualTargetKeepPartial = initialTargetKeepPartial;
+ }
+
+ // Verify whether we have actually been instructed to use a different file.
+ // This may happen the first time this function is executed, if SetTarget was
+ // called two times before the worker thread processed the attention request.
+ bool equalToCurrent = false;
+ if (renamedTarget) {
+ rv = mActualTarget->Equals(renamedTarget, &equalToCurrent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!equalToCurrent)
+ {
+ // If we were asked to rename the file but the initial file did not exist,
+ // we simply create the file in the renamed location. We avoid this check
+ // if we have already started writing the output file ourselves.
+ bool exists = true;
+ if (!isContinuation) {
+ rv = mActualTarget->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (exists) {
+ // We are moving the previous target file to a different location.
+ nsCOMPtr<nsIFile> renamedTargetParentDir;
+ rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString renamedTargetName;
+ rv = renamedTarget->GetLeafName(renamedTargetName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We must delete any existing target file before moving the current
+ // one.
+ rv = renamedTarget->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ rv = renamedTarget->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Move the file. If this fails, we still reference the original file
+ // in mActualTarget, so that it is deleted if requested. If this
+ // succeeds, the nsIFile instance referenced by mActualTarget mutates
+ // and starts pointing to the new file, but we'll discard the reference.
+ rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Now we can update the actual target file name.
+ mActualTarget = renamedTarget;
+ mActualTargetKeepPartial = renamedTargetKeepPartial;
+ }
+ }
+
+ // Notify if the target file name actually changed.
+ if (!equalToCurrent) {
+ // We must clone the nsIFile instance because mActualTarget is not
+ // immutable, it may change if the target is renamed later.
+ nsCOMPtr<nsIFile> actualTargetToNotify;
+ rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<NotifyTargetChangeRunnable> event =
+ new NotifyTargetChangeRunnable(this, actualTargetToNotify);
+ NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
+
+ rv = mControlThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (isContinuation) {
+ // The pending rename operation might be the last task before finishing. We
+ // may return here only if we have already created the target file.
+ if (CheckCompletion()) {
+ return NS_OK;
+ }
+
+ // Even if the operation did not complete, the pipe input stream may be
+ // empty and may have been closed already. We detect this case using the
+ // Available property, because it never returns an error if there is more
+ // data to be consumed. If the pipe input stream is closed, we just exit
+ // and wait for more calls like SetTarget or Finish to be invoked on the
+ // control thread. However, we still truncate the file or create the
+ // initial digest context if we are expected to do that.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ // Create the digest context if requested and NSS hasn't been shut down.
+ if (sha256Enabled && !mDigestContext) {
+ nsNSSShutDownPreventionLock lock;
+ if (!isAlreadyShutDown()) {
+ mDigestContext = UniquePK11Context(
+ PK11_CreateDigestContext(SEC_OID_SHA256));
+ NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ // When we are requested to append to an existing file, we should read the
+ // existing data and ensure we include it as part of the final hash.
+ if (mDigestContext && append && !isContinuation) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ mActualTarget,
+ PR_RDONLY | nsIFile::OS_READAHEAD);
+ if (rv != NS_ERROR_FILE_NOT_FOUND) {
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char buffer[BUFFERED_IO_SIZE];
+ while (true) {
+ uint32_t count;
+ rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count == 0) {
+ // We reached the end of the file.
+ break;
+ }
+
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = MapSECStatus(
+ PK11_DigestOp(mDigestContext.get(),
+ BitwiseCast<unsigned char*, char*>(buffer),
+ count));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = inputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // We will append to the initial target file only if it was requested by the
+ // caller, but we'll always append on subsequent accesses to the target file.
+ int32_t creationIoFlags;
+ if (isContinuation) {
+ creationIoFlags = PR_APPEND;
+ } else {
+ creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE;
+ }
+
+ // Create the target file, or append to it if we already started writing it.
+ // The 0600 permissions are used while the file is being downloaded, and for
+ // interrupted downloads. Those may be located in the system temporary
+ // directory, as well as the target directory, and generally have a ".part"
+ // extension. Those part files should never be group or world-writable even
+ // if the umask allows it.
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
+ mActualTarget,
+ PR_WRONLY | creationIoFlags, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outputStream = NS_BufferOutputStream(outputStream, BUFFERED_IO_SIZE);
+ if (!outputStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Wrap the output stream so that it feeds the digest context if needed.
+ if (mDigestContext) {
+ // No need to acquire the NSS lock here, DigestOutputStream must acquire it
+ // in any case before each asynchronous write. Constructing the
+ // DigestOutputStream cannot fail. Passing mDigestContext to
+ // DigestOutputStream is safe, because BackgroundFileSaver always outlives
+ // the outputStream. BackgroundFileSaver is reference-counted before the
+ // call to AsyncCopy, and mDigestContext is never destroyed before
+ // AsyncCopyCallback.
+ outputStream = new DigestOutputStream(outputStream, mDigestContext.get());
+ }
+
+ // Start copying our input to the target file. No errors can be raised past
+ // this point if the copy starts, since they should be handled by the thread.
+ {
+ MutexAutoLock lock(mLock);
+
+ rv = NS_AsyncCopy(mPipeInputStream, outputStream, mWorkerThread,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback,
+ this, false, true, getter_AddRefs(mAsyncCopyContext),
+ GetProgressCallback());
+ if (NS_FAILED(rv)) {
+ NS_WARNING("NS_AsyncCopy failed.");
+ mAsyncCopyContext = nullptr;
+ return rv;
+ }
+ }
+
+ // If the operation succeeded, we must ensure that we keep this object alive
+ // for the entire duration of the copy, since only the raw pointer will be
+ // provided as the argument of the AsyncCopyCallback function. We can add the
+ // reference now, after NS_AsyncCopy returned, because it always starts
+ // processing asynchronously, and there is no risk that the callback is
+ // invoked before we reach this point. If the operation failed instead, then
+ // AsyncCopyCallback will never be called.
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+bool
+BackgroundFileSaver::CheckCompletion()
+{
+ nsresult rv;
+
+ MOZ_ASSERT(!mAsyncCopyContext,
+ "Should not be copying when checking completion conditions.");
+
+ bool failed = true;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mComplete) {
+ return true;
+ }
+
+ // If an error occurred, we don't need to do the checks in this code block,
+ // and the operation can be completed immediately with a failure code.
+ if (NS_SUCCEEDED(mStatus)) {
+ failed = false;
+
+ // We did not incur in an error, so we must determine if we can stop now.
+ // If the Finish method has not been called, we can just continue now.
+ if (!mFinishRequested) {
+ return false;
+ }
+
+ // We can only stop when all the operations requested by the control
+ // thread have been processed. First, we check whether we have processed
+ // the first SetTarget call, if any. Then, we check whether we have
+ // processed any rename requested by subsequent SetTarget calls.
+ if ((mInitialTarget && !mActualTarget) ||
+ (mRenamedTarget && mRenamedTarget != mActualTarget)) {
+ return false;
+ }
+
+ // If we still have data to write to the output file, allow the copy
+ // operation to resume. The Available getter may return an error if one
+ // of the pipe's streams has been already closed.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available != 0) {
+ return false;
+ }
+ }
+
+ mComplete = true;
+ }
+
+ // Ensure we notify completion now that the operation finished.
+ // Do a best-effort attempt to remove the file if required.
+ if (failed && mActualTarget && !mActualTargetKeepPartial) {
+ (void)mActualTarget->Remove(false);
+ }
+
+ // Finish computing the hash
+ if (!failed && mDigestContext) {
+ nsNSSShutDownPreventionLock lock;
+ if (!isAlreadyShutDown()) {
+ Digest d;
+ rv = d.End(SEC_OID_SHA256, mDigestContext);
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock lock(mLock);
+ mSha256 =
+ nsDependentCSubstring(BitwiseCast<char*, unsigned char*>(d.get().data),
+ d.get().len);
+ }
+ }
+ }
+
+ // Compute the signature of the binary. ExtractSignatureInfo doesn't do
+ // anything on non-Windows platforms except return an empty nsIArray.
+ if (!failed && mActualTarget) {
+ nsString filePath;
+ mActualTarget->GetTarget(filePath);
+ nsresult rv = ExtractSignatureInfo(filePath);
+ if (NS_FAILED(rv)) {
+ LOG(("Unable to extract signature information [this = %p].", this));
+ } else {
+ LOG(("Signature extraction success! [this = %p]", this));
+ }
+ }
+
+ // Post an event to notify that the operation completed.
+ if (NS_FAILED(mControlThread->Dispatch(NewRunnableMethod(this,
+ &BackgroundFileSaver::NotifySaveComplete),
+ NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Unable to post completion event to the control thread.");
+ }
+
+ return true;
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaver::NotifyTargetChange(nsIFile *aTarget)
+{
+ if (mObserver) {
+ (void)mObserver->OnTargetChange(this, aTarget);
+ }
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaver::NotifySaveComplete()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ nsresult status;
+ {
+ MutexAutoLock lock(mLock);
+ status = mStatus;
+ }
+
+ if (mObserver) {
+ (void)mObserver->OnSaveComplete(this, status);
+ }
+
+ // At this point, the worker thread will not process any more events, and we
+ // can shut it down. Shutting down a thread may re-enter the event loop on
+ // this thread. This is not a problem in this case, since this function is
+ // called by a top-level event itself, and we have already invoked the
+ // completion observer callback. Re-entering the loop can only delay the
+ // final release and destruction of this saver object, since we are keeping a
+ // reference to it through the event object.
+ mWorkerThread->Shutdown();
+
+ sThreadCount--;
+
+ // When there are no more active downloads, we consider the download session
+ // finished. We record the maximum number of concurrent downloads reached
+ // during the session in a telemetry histogram, and we reset the maximum
+ // thread counter for the next download session
+ if (sThreadCount == 0) {
+ Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT,
+ sTelemetryMaxThreadCount);
+ sTelemetryMaxThreadCount = 0;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread");
+
+ nsNSSShutDownPreventionLock nssLock;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ {
+ MutexAutoLock lock(mLock);
+ if (!mSignatureInfoEnabled) {
+ return NS_OK;
+ }
+ }
+ nsresult rv;
+ nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+#ifdef XP_WIN
+ // Setup the file to check.
+ WINTRUST_FILE_INFO fileToCheck = {0};
+ fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
+ fileToCheck.pcwszFilePath = filePath.Data();
+ fileToCheck.hFile = nullptr;
+ fileToCheck.pgKnownSubject = nullptr;
+
+ // We want to check it is signed and trusted.
+ WINTRUST_DATA trustData = {0};
+ trustData.cbStruct = sizeof(trustData);
+ trustData.pPolicyCallbackData = nullptr;
+ trustData.pSIPClientData = nullptr;
+ trustData.dwUIChoice = WTD_UI_NONE;
+ trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
+ trustData.dwUnionChoice = WTD_CHOICE_FILE;
+ trustData.dwStateAction = WTD_STATEACTION_VERIFY;
+ trustData.hWVTStateData = nullptr;
+ trustData.pwszURLReference = nullptr;
+ // Disallow revocation checks over the network
+ trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
+ // no UI
+ trustData.dwUIContext = 0;
+ trustData.pFile = &fileToCheck;
+
+ // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
+ // chains up to a trusted root CA and has appropriate permissions to sign
+ // code.
+ GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ // Check if the file is signed by something that is trusted. If the file is
+ // not signed, this is a no-op.
+ LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr;
+ // According to the Windows documentation, we should check against 0 instead
+ // of ERROR_SUCCESS, which is an HRESULT.
+ if (ret == 0) {
+ cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
+ }
+ if (cryptoProviderData) {
+ // Lock because signature information is read on the main thread.
+ MutexAutoLock lock(mLock);
+ LOG(("Downloaded trusted and signed file [this = %p].", this));
+ // A binary may have multiple signers. Each signer may have multiple certs
+ // in the chain.
+ for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) {
+ const CERT_CHAIN_CONTEXT* certChainContext =
+ cryptoProviderData->pasSigners[i].pChainContext;
+ if (!certChainContext) {
+ break;
+ }
+ for (DWORD j = 0; j < certChainContext->cChain; ++j) {
+ const CERT_SIMPLE_CHAIN* certSimpleChain =
+ certChainContext->rgpChain[j];
+ if (!certSimpleChain) {
+ break;
+ }
+ nsCOMPtr<nsIX509CertList> nssCertList =
+ do_CreateInstance(NS_X509CERTLIST_CONTRACTID);
+ if (!nssCertList) {
+ break;
+ }
+ bool extractionSuccess = true;
+ for (DWORD k = 0; k < certSimpleChain->cElement; ++k) {
+ CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k];
+ if (certChainElement->pCertContext->dwCertEncodingType !=
+ X509_ASN_ENCODING) {
+ continue;
+ }
+ nsCOMPtr<nsIX509Cert> nssCert = nullptr;
+ rv = certDB->ConstructX509(
+ reinterpret_cast<char *>(
+ certChainElement->pCertContext->pbCertEncoded),
+ certChainElement->pCertContext->cbCertEncoded,
+ getter_AddRefs(nssCert));
+ if (!nssCert) {
+ extractionSuccess = false;
+ LOG(("Couldn't create NSS cert [this = %p]", this));
+ break;
+ }
+ nssCertList->AddCert(nssCert);
+ nsString subjectName;
+ nssCert->GetSubjectName(subjectName);
+ LOG(("Adding cert %s [this = %p]",
+ NS_ConvertUTF16toUTF8(subjectName).get(), this));
+ }
+ if (extractionSuccess) {
+ mSignatureInfo.AppendObject(nssCertList);
+ }
+ }
+ }
+ // Free the provider data if cryptoProviderData is not null.
+ trustData.dwStateAction = WTD_STATEACTION_CLOSE;
+ WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ } else {
+ LOG(("Downloaded unsigned or untrusted file [this = %p].", this));
+ }
+#endif
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverOutputStream
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream,
+ nsIBackgroundFileSaver,
+ nsIOutputStream,
+ nsIAsyncOutputStream,
+ nsIOutputStreamCallback)
+
+BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream()
+: BackgroundFileSaver()
+, mAsyncWaitCallback(nullptr)
+{
+}
+
+BackgroundFileSaverOutputStream::~BackgroundFileSaverOutputStream()
+{
+}
+
+bool
+BackgroundFileSaverOutputStream::HasInfiniteBuffer()
+{
+ return false;
+}
+
+nsAsyncCopyProgressFun
+BackgroundFileSaverOutputStream::GetProgressCallback()
+{
+ return nullptr;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Close()
+{
+ return mPipeOutputStream->Close();
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Flush()
+{
+ return mPipeOutputStream->Flush();
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Write(const char *aBuf, uint32_t aCount,
+ uint32_t *_retval)
+{
+ return mPipeOutputStream->Write(aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream *aFromStream,
+ uint32_t aCount, uint32_t *_retval)
+{
+ return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void *aClosure, uint32_t aCount,
+ uint32_t *_retval)
+{
+ return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::IsNonBlocking(bool *_retval)
+{
+ return mPipeOutputStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason)
+{
+ return mPipeOutputStream->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget *aEventTarget)
+{
+ NS_ENSURE_STATE(!mAsyncWaitCallback);
+
+ mAsyncWaitCallback = aCallback;
+
+ return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::OnOutputStreamReady(
+ nsIAsyncOutputStream *aStream)
+{
+ NS_ENSURE_STATE(mAsyncWaitCallback);
+
+ nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr;
+ asyncWaitCallback.swap(mAsyncWaitCallback);
+
+ return asyncWaitCallback->OnOutputStreamReady(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener,
+ nsIBackgroundFileSaver,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+BackgroundFileSaverStreamListener::BackgroundFileSaverStreamListener()
+: BackgroundFileSaver()
+, mSuspensionLock("BackgroundFileSaverStreamListener.mSuspensionLock")
+, mReceivedTooMuchData(false)
+, mRequest(nullptr)
+, mRequestSuspended(false)
+{
+}
+
+BackgroundFileSaverStreamListener::~BackgroundFileSaverStreamListener()
+{
+}
+
+bool
+BackgroundFileSaverStreamListener::HasInfiniteBuffer()
+{
+ return true;
+}
+
+nsAsyncCopyProgressFun
+BackgroundFileSaverStreamListener::GetProgressCallback()
+{
+ return AsyncCopyProgressCallback;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ NS_ENSURE_ARG(aRequest);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ // If an error occurred, cancel the operation immediately. On success, wait
+ // until the caller has determined whether the file should be renamed.
+ if (NS_FAILED(aStatusCode)) {
+ Finish(aStatusCode);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG(aRequest);
+
+ // Read the requested data. Since the pipe has an infinite buffer, we don't
+ // expect any write error to occur here.
+ uint32_t writeCount;
+ rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If reading from the input stream fails for any reason, the pipe will return
+ // a success code, but without reading all the data. Since we should be able
+ // to read the requested data when OnDataAvailable is called, raise an error.
+ if (writeCount < aCount) {
+ NS_WARNING("Reading from the input stream should not have failed.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool stateChanged = false;
+ {
+ MutexAutoLock lock(mSuspensionLock);
+
+ if (!mReceivedTooMuchData) {
+ uint64_t available;
+ nsresult rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) {
+ mReceivedTooMuchData = true;
+ mRequest = aRequest;
+ stateChanged = true;
+ }
+ }
+ }
+
+ if (stateChanged) {
+ NotifySuspendOrResume();
+ }
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+// static
+void
+BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(void *aClosure,
+ uint32_t aCount)
+{
+ BackgroundFileSaverStreamListener *self =
+ (BackgroundFileSaverStreamListener *)aClosure;
+
+ // Wait if the control thread is in the process of suspending or resuming.
+ MutexAutoLock lock(self->mSuspensionLock);
+
+ // This function is called when some bytes are consumed by NS_AsyncCopy. Each
+ // time this happens, verify if a suspended request should be resumed, because
+ // we have now consumed enough data.
+ if (self->mReceivedTooMuchData) {
+ uint64_t available;
+ nsresult rv = self->mPipeInputStream->Available(&available);
+ if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) {
+ self->mReceivedTooMuchData = false;
+
+ // Post an event to verify if the request should be resumed.
+ if (NS_FAILED(self->mControlThread->Dispatch(NewRunnableMethod(self,
+ &BackgroundFileSaverStreamListener::NotifySuspendOrResume),
+ NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Unable to post resume event to the control thread.");
+ }
+ }
+ }
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaverStreamListener::NotifySuspendOrResume()
+{
+ // Prevent the worker thread from changing state while processing.
+ MutexAutoLock lock(mSuspensionLock);
+
+ if (mReceivedTooMuchData) {
+ if (!mRequestSuspended) {
+ // Try to suspend the request. If this fails, don't try to resume later.
+ if (NS_SUCCEEDED(mRequest->Suspend())) {
+ mRequestSuspended = true;
+ } else {
+ NS_WARNING("Unable to suspend the request.");
+ }
+ }
+ } else {
+ if (mRequestSuspended) {
+ // Resume the request only if we succeeded in suspending it.
+ if (NS_SUCCEEDED(mRequest->Resume())) {
+ mRequestSuspended = false;
+ } else {
+ NS_WARNING("Unable to resume the request.");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// DigestOutputStream
+NS_IMPL_ISUPPORTS(DigestOutputStream,
+ nsIOutputStream)
+
+DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream,
+ PK11Context* aContext) :
+ mOutputStream(aStream)
+ , mDigestContext(aContext)
+{
+ MOZ_ASSERT(mDigestContext, "Can't have null digest context");
+ MOZ_ASSERT(mOutputStream, "Can't have null output stream");
+}
+
+DigestOutputStream::~DigestOutputStream()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+NS_IMETHODIMP
+DigestOutputStream::Close()
+{
+ return mOutputStream->Close();
+}
+
+NS_IMETHODIMP
+DigestOutputStream::Flush()
+{
+ return mOutputStream->Flush();
+}
+
+NS_IMETHODIMP
+DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval)
+{
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = MapSECStatus(
+ PK11_DigestOp(mDigestContext,
+ BitwiseCast<const unsigned char*, const char*>(aBuf),
+ aCount));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mOutputStream->Write(aBuf, aCount, retval);
+}
+
+NS_IMETHODIMP
+DigestOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* retval)
+{
+ // Not supported. We could read the stream to a buf, call DigestOp on the
+ // result, seek back and pass the stream on, but it's not worth it since our
+ // application (NS_AsyncCopy) doesn't invoke this on the sink.
+ MOZ_CRASH("DigestOutputStream::WriteFrom not implemented");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void *aClosure, uint32_t aCount,
+ uint32_t *retval)
+{
+ MOZ_CRASH("DigestOutputStream::WriteSegments not implemented");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::IsNonBlocking(bool *retval)
+{
+ return mOutputStream->IsNonBlocking(retval);
+}
+
+#undef LOG_ENABLED
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/BackgroundFileSaver.h b/netwerk/base/BackgroundFileSaver.h
new file mode 100644
index 0000000000..1fa9268f8f
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.h
@@ -0,0 +1,418 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file defines two implementations of the nsIBackgroundFileSaver
+ * interface. See the "test_backgroundfilesaver.js" file for usage examples.
+ */
+
+#ifndef BackgroundFileSaver_h__
+#define BackgroundFileSaver_h__
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIBackgroundFileSaver.h"
+#include "nsIStreamListener.h"
+#include "nsNSSShutDown.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+
+class nsIAsyncInputStream;
+class nsIThread;
+class nsIX509CertList;
+
+namespace mozilla {
+namespace net {
+
+class DigestOutputStream;
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaver
+
+class BackgroundFileSaver : public nsIBackgroundFileSaver,
+ public nsNSSShutDownObject
+{
+public:
+ NS_DECL_NSIBACKGROUNDFILESAVER
+
+ BackgroundFileSaver();
+
+ /**
+ * Initializes the pipe and the worker thread on XPCOM construction.
+ *
+ * This is called automatically by the XPCOM infrastructure, and if this
+ * fails, the factory will delete this object without returning a reference.
+ */
+ nsresult Init();
+
+ /**
+ * Used by nsNSSShutDownList to manage nsNSSShutDownObjects.
+ */
+ void virtualDestroyNSSReference() override;
+
+ /**
+ * Number of worker threads that are currently running.
+ */
+ static uint32_t sThreadCount;
+
+ /**
+ * Maximum number of worker threads reached during the current download session,
+ * used for telemetry.
+ *
+ * When there are no more worker threads running, we consider the download
+ * session finished, and this counter is reset.
+ */
+ static uint32_t sTelemetryMaxThreadCount;
+
+
+protected:
+ virtual ~BackgroundFileSaver();
+
+ /**
+ * Helper function for managing NSS objects (mDigestContext).
+ */
+ void destructorSafeDestroyNSSReference();
+
+ /**
+ * Thread that constructed this object.
+ */
+ nsCOMPtr<nsIThread> mControlThread;
+
+ /**
+ * Thread to which the actual input/output is delegated.
+ */
+ nsCOMPtr<nsIThread> mWorkerThread;
+
+ /**
+ * Stream that receives data from derived classes. The received data will be
+ * available to the worker thread through mPipeInputStream. This is an
+ * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream.
+ */
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOutputStream;
+
+ /**
+ * Used during initialization, determines if the pipe is created with an
+ * infinite buffer. An infinite buffer is required if the derived class
+ * implements nsIStreamListener, because this interface requires all the
+ * provided data to be consumed synchronously.
+ */
+ virtual bool HasInfiniteBuffer() = 0;
+
+ /**
+ * Used by derived classes if they need to be called back while copying.
+ */
+ virtual nsAsyncCopyProgressFun GetProgressCallback() = 0;
+
+ /**
+ * Stream used by the worker thread to read the data to be saved.
+ */
+ nsCOMPtr<nsIAsyncInputStream> mPipeInputStream;
+
+private:
+ friend class NotifyTargetChangeRunnable;
+
+ /**
+ * Matches the nsIBackgroundFileSaver::observer property.
+ *
+ * @remarks This is a strong reference so that JavaScript callers don't need
+ * to worry about keeping another reference to the observer.
+ */
+ nsCOMPtr<nsIBackgroundFileSaverObserver> mObserver;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Shared state between control and worker threads
+
+ /**
+ * Protects the shared state between control and worker threads. This mutex
+ * is always locked for a very short time, never during input/output.
+ */
+ mozilla::Mutex mLock;
+
+ /**
+ * True if the worker thread is already waiting to process a change in state.
+ */
+ bool mWorkerThreadAttentionRequested;
+
+ /**
+ * True if the operation should finish as soon as possibile.
+ */
+ bool mFinishRequested;
+
+ /**
+ * True if the operation completed, with either success or failure.
+ */
+ bool mComplete;
+
+ /**
+ * Holds the current file saver status. This is a success status while the
+ * object is working correctly, and remains such if the operation completes
+ * successfully. This becomes an error status when an error occurs on the
+ * worker thread, or when the operation is canceled.
+ */
+ nsresult mStatus;
+
+ /**
+ * True if we should append data to the initial target file, instead of
+ * overwriting it.
+ */
+ bool mAppend;
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and contains
+ * the target file name that will be used by the worker thread, as soon as it
+ * is possible to update mActualTarget and open the file. This is null if no
+ * target was ever assigned to this object.
+ */
+ nsCOMPtr<nsIFile> mInitialTarget;
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and
+ * indicates whether mInitialTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mInitialTargetKeepPartial;
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * contains the new target file name to which the worker thread will move the
+ * target file, as soon as it can be done. This is null if SetTarget was
+ * called only once, or no target was ever assigned to this object.
+ *
+ * The target file can be renamed multiple times, though only the most recent
+ * rename is guaranteed to be processed by the worker thread.
+ */
+ nsCOMPtr<nsIFile> mRenamedTarget;
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * indicates whether mRenamedTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mRenamedTargetKeepPartial;
+
+ /**
+ * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise.
+ * This is read by both threads but only written by the worker thread.
+ */
+ nsCOMPtr<nsISupports> mAsyncCopyContext;
+
+ /**
+ * The SHA 256 hash in raw bytes of the downloaded file. This is written
+ * by the worker thread but can be read on the main thread.
+ */
+ nsCString mSha256;
+
+ /**
+ * Whether or not to compute the hash. Must be set on the main thread before
+ * setTarget is called.
+ */
+ bool mSha256Enabled;
+
+ /**
+ * Store the signature info.
+ */
+ nsCOMArray<nsIX509CertList> mSignatureInfo;
+
+ /**
+ * Whether or not to extract the signature. Must be set on the main thread
+ * before setTarget is called.
+ */
+ bool mSignatureInfoEnabled;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// State handled exclusively by the worker thread
+
+ /**
+ * Current target file associated to the input and output streams.
+ */
+ nsCOMPtr<nsIFile> mActualTarget;
+
+ /**
+ * Indicates whether mActualTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mActualTargetKeepPartial;
+
+ /**
+ * Used to calculate the file hash. This keeps state across file renames and
+ * is lazily initialized in ProcessStateChange.
+ */
+ UniquePK11Context mDigestContext;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Private methods
+
+ /**
+ * Called when NS_AsyncCopy completes.
+ *
+ * @param aClosure
+ * Populated with a raw pointer to the BackgroundFileSaver object.
+ * @param aStatus
+ * Success or failure status specified when the copy was interrupted.
+ */
+ static void AsyncCopyCallback(void *aClosure, nsresult aStatus);
+
+ /**
+ * Called on the control thread after state changes, to ensure that the worker
+ * thread will process the state change appropriately.
+ *
+ * @param aShouldInterruptCopy
+ * If true, the current NS_AsyncCopy, if any, is canceled.
+ */
+ nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy);
+
+ /**
+ * Event called on the worker thread to begin processing a state change.
+ */
+ nsresult ProcessAttention();
+
+ /**
+ * Called by ProcessAttention to execute the operations corresponding to the
+ * state change. If this results in an error, ProcessAttention will force the
+ * entire operation to be aborted.
+ */
+ nsresult ProcessStateChange();
+
+ /**
+ * Returns true if completion conditions are met on the worker thread. The
+ * first time this happens, posts the completion event to the control thread.
+ */
+ bool CheckCompletion();
+
+ /**
+ * Event called on the control thread to indicate that file contents will now
+ * be saved to the specified file.
+ */
+ nsresult NotifyTargetChange(nsIFile *aTarget);
+
+ /**
+ * Event called on the control thread to send the final notification.
+ */
+ nsresult NotifySaveComplete();
+
+ /**
+ * Verifies the signature of the binary at the specified file path and stores
+ * the signature data in mSignatureInfo. We extract only X.509 certificates,
+ * since that is what Google's Safebrowsing protocol specifies.
+ */
+ nsresult ExtractSignatureInfo(const nsAString& filePath);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverOutputStream
+
+class BackgroundFileSaverOutputStream : public BackgroundFileSaver
+ , public nsIAsyncOutputStream
+ , public nsIOutputStreamCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ BackgroundFileSaverOutputStream();
+
+protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+private:
+ ~BackgroundFileSaverOutputStream();
+
+ /**
+ * Original callback provided to our AsyncWait wrapper.
+ */
+ nsCOMPtr<nsIOutputStreamCallback> mAsyncWaitCallback;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener. This class is instantiated by
+// nsExternalHelperAppService, DownloadCore.jsm, and possibly others.
+
+class BackgroundFileSaverStreamListener final : public BackgroundFileSaver
+ , public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ BackgroundFileSaverStreamListener();
+
+protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+private:
+ ~BackgroundFileSaverStreamListener();
+
+ /**
+ * Protects the state related to whether the request should be suspended.
+ */
+ mozilla::Mutex mSuspensionLock;
+
+ /**
+ * Whether we should suspend the request because we received too much data.
+ */
+ bool mReceivedTooMuchData;
+
+ /**
+ * Request for which we received too much data. This is populated when
+ * mReceivedTooMuchData becomes true for the first time.
+ */
+ nsCOMPtr<nsIRequest> mRequest;
+
+ /**
+ * Whether mRequest is currently suspended.
+ */
+ bool mRequestSuspended;
+
+ /**
+ * Called while NS_AsyncCopy is copying data.
+ */
+ static void AsyncCopyProgressCallback(void *aClosure, uint32_t aCount);
+
+ /**
+ * Called on the control thread to suspend or resume the request.
+ */
+ nsresult NotifySuspendOrResume();
+};
+
+// A wrapper around nsIOutputStream, so that we can compute hashes on the
+// stream without copying and without polluting pristine NSS code with XPCOM
+// interfaces.
+class DigestOutputStream : public nsNSSShutDownObject,
+ public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ // Constructor. Neither parameter may be null. The caller owns both.
+ DigestOutputStream(nsIOutputStream* outputStream, PK11Context* aContext);
+
+ // We don't own any NSS objects here, so no need to clean up
+ void virtualDestroyNSSReference() override { }
+
+private:
+ ~DigestOutputStream();
+
+ // Calls to write are passed to this stream.
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ // Digest context used to compute the hash, owned by the caller.
+ PK11Context* mDigestContext;
+
+ // Don't accidentally copy construct.
+ DigestOutputStream(const DigestOutputStream& d);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp
new file mode 100644
index 0000000000..f97fb41d7e
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.cpp
@@ -0,0 +1,366 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/CaptivePortalService.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+
+static const char16_t kInterfaceName[] = u"captive-portal-inteface";
+
+static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
+static const char kAbortCaptivePortalLoginEvent[] = "captive-portal-login-abort";
+static const char kCaptivePortalLoginSuccessEvent[] = "captive-portal-login-success";
+
+static const uint32_t kDefaultInterval = 60*1000; // check every 60 seconds
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gCaptivePortalLog("CaptivePortalService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver,
+ nsISupportsWeakReference, nsITimerCallback,
+ nsICaptivePortalCallback)
+
+CaptivePortalService::CaptivePortalService()
+ : mState(UNKNOWN)
+ , mStarted(false)
+ , mInitialized(false)
+ , mRequestInProgress(false)
+ , mEverBeenCaptive(false)
+ , mDelay(kDefaultInterval)
+ , mSlackCount(0)
+ , mMinInterval(kDefaultInterval)
+ , mMaxInterval(25*kDefaultInterval)
+ , mBackoffFactor(5.0)
+{
+ mLastChecked = TimeStamp::Now();
+}
+
+CaptivePortalService::~CaptivePortalService()
+{
+ LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
+ XRE_GetProcessType() == GeckoProcessType_Default));
+}
+
+nsresult
+CaptivePortalService::PerformCheck()
+{
+ LOG(("CaptivePortalService::PerformCheck mRequestInProgress:%d mInitialized:%d mStarted:%d\n",
+ mRequestInProgress, mInitialized, mStarted));
+ // Don't issue another request if last one didn't complete
+ if (mRequestInProgress || !mInitialized || !mStarted) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ nsresult rv;
+ if (!mCaptivePortalDetector) {
+ mCaptivePortalDetector =
+ do_GetService("@mozilla.org/toolkit/captive-detector;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("Unable to get a captive portal detector\n"));
+ return rv;
+ }
+ }
+
+ LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n"));
+ mRequestInProgress = true;
+ mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this);
+ return NS_OK;
+}
+
+nsresult
+CaptivePortalService::RearmTimer()
+{
+ LOG(("CaptivePortalService::RearmTimer\n"));
+ // Start a timer to recheck
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+
+ // If we have successfully determined the state, and we have never detected
+ // a captive portal, we don't need to keep polling, but will rely on events
+ // to trigger detection.
+ if (mState == NOT_CAPTIVE) {
+ return NS_OK;
+ }
+
+ if (!mTimer) {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ if (mTimer && mDelay > 0) {
+ LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay));
+ return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CaptivePortalService::Initialize()
+{
+ if (mInitialized) {
+ return NS_OK;
+ }
+ mInitialized = true;
+
+ // Only the main process service should actually do anything. The service in
+ // the content process only mirrors the CP state in the main process.
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true);
+ observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true);
+ observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true);
+ }
+
+ LOG(("Initialized CaptivePortalService\n"));
+ return NS_OK;
+}
+
+nsresult
+CaptivePortalService::Start()
+{
+ if (!mInitialized) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ if (mStarted) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN");
+ mStarted = true;
+ mEverBeenCaptive = false;
+
+ // Get the delay prefs
+ Preferences::GetUint("network.captive-portal-service.minInterval", &mMinInterval);
+ Preferences::GetUint("network.captive-portal-service.maxInterval", &mMaxInterval);
+ Preferences::GetFloat("network.captive-portal-service.backoffFactor", &mBackoffFactor);
+
+ LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n",
+ mMinInterval, mMaxInterval, mBackoffFactor));
+
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ // When Start is called, perform a check immediately
+ PerformCheck();
+ RearmTimer();
+ return NS_OK;
+}
+
+nsresult
+CaptivePortalService::Stop()
+{
+ LOG(("CaptivePortalService::Stop\n"));
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything when called in the content process.
+ return NS_OK;
+ }
+
+ if (!mStarted) {
+ return NS_OK;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mRequestInProgress = false;
+ mStarted = false;
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->Abort(kInterfaceName);
+ }
+ mCaptivePortalDetector = nullptr;
+
+ // Clear the state in case anyone queries the state while detection is off.
+ mState = UNKNOWN;
+ return NS_OK;
+}
+
+void
+CaptivePortalService::SetStateInChild(int32_t aState)
+{
+ // This should only be called in the content process, from ContentChild.cpp
+ // in order to mirror the captive portal state set in the chrome process.
+ MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
+
+ mState = aState;
+ mLastChecked = TimeStamp::Now();
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsICaptivePortalService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+CaptivePortalService::GetState(int32_t *aState)
+{
+ *aState = mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::RecheckCaptivePortal()
+{
+ LOG(("CaptivePortalService::RecheckCaptivePortal\n"));
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ // This is called for user activity. We need to reset the slack count,
+ // so the checks continue to be quite frequent.
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ PerformCheck();
+ RearmTimer();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::GetLastChecked(uint64_t *aLastChecked)
+{
+ double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds();
+ *aLastChecked = static_cast<uint64_t>(duration);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsITimer
+// This callback gets called every mDelay miliseconds
+// It issues a checkCaptivePortal operation if one isn't already in progress
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Notify(nsITimer *aTimer)
+{
+ LOG(("CaptivePortalService::Notify\n"));
+ MOZ_ASSERT(aTimer == mTimer);
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ PerformCheck();
+
+ // This is needed because we don't want to always make requests very often.
+ // Every 10 checks, we the delay is increased mBackoffFactor times
+ // to a maximum delay of mMaxInterval
+ mSlackCount++;
+ if (mSlackCount % 10 == 0) {
+ mDelay = mDelay * mBackoffFactor;
+ }
+ if (mDelay > mMaxInterval) {
+ mDelay = mMaxInterval;
+ }
+
+ // Note - if mDelay is 0, the timer will not be rearmed.
+ RearmTimer();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsIObserver
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Observe(nsISupports *aSubject,
+ const char * aTopic,
+ const char16_t * aData)
+{
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic));
+ if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
+ // A redirect or altered content has been detected.
+ // The user needs to log in. We are in a captive portal.
+ mState = LOCKED_PORTAL;
+ mLastChecked = TimeStamp::Now();
+ mEverBeenCaptive = true;
+ } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) {
+ // The user has successfully logged in. We have connectivity.
+ mState = UNLOCKED_PORTAL;
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ RearmTimer();
+ } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) {
+ // The login has been aborted
+ mState = UNKNOWN;
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ }
+
+ // Send notification so that the captive portal state is mirrored in the
+ // content process.
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE, nullptr);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsICaptivePortalCallback
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Prepare()
+{
+ LOG(("CaptivePortalService::Prepare\n"));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ // XXX: Finish preparation shouldn't be called until dns and routing is available.
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->FinishPreparation(kInterfaceName);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::Complete(bool success)
+{
+ LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success, mState));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ mLastChecked = TimeStamp::Now();
+
+ // Note: this callback gets called when:
+ // 1. the request is completed, and content is valid (success == true)
+ // 2. when the request is aborted or times out (success == false)
+
+ if (success) {
+ if (mEverBeenCaptive) {
+ mState = UNLOCKED_PORTAL;
+ } else {
+ mState = NOT_CAPTIVE;
+ }
+ }
+
+ mRequestInProgress = false;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h
new file mode 100644
index 0000000000..dd15450dc9
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.h
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CaptivePortalService_h_
+#define CaptivePortalService_h_
+
+#include "nsICaptivePortalService.h"
+#include "nsICaptivePortalDetector.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsCOMArray.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace net {
+
+class CaptivePortalService
+ : public nsICaptivePortalService
+ , public nsIObserver
+ , public nsSupportsWeakReference
+ , public nsITimerCallback
+ , public nsICaptivePortalCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICAPTIVEPORTALSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSICAPTIVEPORTALCALLBACK
+
+ CaptivePortalService();
+ nsresult Initialize();
+ nsresult Start();
+ nsresult Stop();
+
+ // This method is only called in the content process, in order to mirror
+ // the captive portal state in the parent process.
+ void SetStateInChild(int32_t aState);
+private:
+ virtual ~CaptivePortalService();
+ nsresult PerformCheck();
+ nsresult RearmTimer();
+
+ nsCOMPtr<nsICaptivePortalDetector> mCaptivePortalDetector;
+ int32_t mState;
+
+ nsCOMPtr<nsITimer> mTimer;
+ bool mStarted;
+ bool mInitialized;
+ bool mRequestInProgress;
+ bool mEverBeenCaptive;
+
+ uint32_t mDelay;
+ int32_t mSlackCount;
+
+ uint32_t mMinInterval;
+ uint32_t mMaxInterval;
+ float mBackoffFactor;
+
+ // This holds a timestamp when the last time when the captive portal check
+ // has changed state.
+ mozilla::TimeStamp mLastChecked;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // CaptivePortalService_h_
diff --git a/netwerk/base/ChannelDiverterChild.cpp b/netwerk/base/ChannelDiverterChild.cpp
new file mode 100644
index 0000000000..d275783f84
--- /dev/null
+++ b/netwerk/base/ChannelDiverterChild.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/ChannelDiverterChild.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+#include "mozilla/net/PHttpChannelChild.h"
+#include "mozilla/net/PFTPChannelChild.h"
+#include "nsIDivertableChannel.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelDiverterChild::ChannelDiverterChild()
+{
+}
+
+ChannelDiverterChild::~ChannelDiverterChild()
+{
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ChannelDiverterChild.h b/netwerk/base/ChannelDiverterChild.h
new file mode 100644
index 0000000000..a92de2f110
--- /dev/null
+++ b/netwerk/base/ChannelDiverterChild.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _channeldiverterchild_h_
+#define _channeldiverterchild_h_
+
+#include "mozilla/net/PChannelDiverterChild.h"
+
+namespace mozilla {
+namespace net {
+
+class ChannelDiverterChild :
+ public PChannelDiverterChild
+{
+public:
+ ChannelDiverterChild();
+ virtual ~ChannelDiverterChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* _channeldiverterchild_h_ */
diff --git a/netwerk/base/ChannelDiverterParent.cpp b/netwerk/base/ChannelDiverterParent.cpp
new file mode 100644
index 0000000000..3b66644abc
--- /dev/null
+++ b/netwerk/base/ChannelDiverterParent.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/ChannelDiverterParent.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/FTPChannelParent.h"
+#include "mozilla/net/PHttpChannelParent.h"
+#include "mozilla/net/PFTPChannelParent.h"
+#include "ADivertableParentChannel.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelDiverterParent::ChannelDiverterParent()
+{
+}
+
+ChannelDiverterParent::~ChannelDiverterParent()
+{
+}
+
+bool
+ChannelDiverterParent::Init(const ChannelDiverterArgs& aArgs)
+{
+ switch (aArgs.type()) {
+ case ChannelDiverterArgs::THttpChannelDiverterArgs:
+ {
+ auto httpParent = static_cast<HttpChannelParent*>(
+ aArgs.get_HttpChannelDiverterArgs().mChannelParent());
+ httpParent->SetApplyConversion(aArgs.get_HttpChannelDiverterArgs().mApplyConversion());
+
+ mDivertableChannelParent =
+ static_cast<ADivertableParentChannel*>(httpParent);
+ break;
+ }
+ case ChannelDiverterArgs::TPFTPChannelParent:
+ {
+ mDivertableChannelParent = static_cast<ADivertableParentChannel*>(
+ static_cast<FTPChannelParent*>(aArgs.get_PFTPChannelParent()));
+ break;
+ }
+ default:
+ NS_NOTREACHED("unknown ChannelDiverterArgs type");
+ return false;
+ }
+ MOZ_ASSERT(mDivertableChannelParent);
+
+ nsresult rv = mDivertableChannelParent->SuspendForDiversion();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ return true;
+}
+
+void
+ChannelDiverterParent::DivertTo(nsIStreamListener* newListener)
+{
+ MOZ_ASSERT(newListener);
+ MOZ_ASSERT(mDivertableChannelParent);
+
+ mDivertableChannelParent->DivertTo(newListener);
+}
+
+void
+ChannelDiverterParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ // Implement me! Bug 1005179
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ChannelDiverterParent.h b/netwerk/base/ChannelDiverterParent.h
new file mode 100644
index 0000000000..047e1c68a9
--- /dev/null
+++ b/netwerk/base/ChannelDiverterParent.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _channeldiverterparent_h_
+#define _channeldiverterparent_h_
+
+#include "mozilla/net/PChannelDiverterParent.h"
+
+class nsIStreamListener;
+
+namespace mozilla {
+namespace net {
+
+class ChannelDiverterArgs;
+class ADivertableParentChannel;
+
+class ChannelDiverterParent :
+ public PChannelDiverterParent
+{
+public:
+ ChannelDiverterParent();
+ virtual ~ChannelDiverterParent();
+
+ bool Init(const ChannelDiverterArgs& aArgs);
+
+ void DivertTo(nsIStreamListener* newListener);
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+ RefPtr<ADivertableParentChannel> mDivertableChannelParent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* _channeldiverterparent_h_ */
diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp
new file mode 100644
index 0000000000..f5d0880aed
--- /dev/null
+++ b/netwerk/base/Dashboard.cpp
@@ -0,0 +1,921 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/NetDashboardBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/net/Dashboard.h"
+#include "mozilla/net/HttpInfo.h"
+#include "nsHttp.h"
+#include "nsICancelable.h"
+#include "nsIDNSService.h"
+#include "nsIDNSRecord.h"
+#include "nsIInputStream.h"
+#include "nsISocketTransport.h"
+#include "nsIThread.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "mozilla/Logging.h"
+
+using mozilla::AutoSafeJSContext;
+using mozilla::dom::Sequence;
+using mozilla::dom::ToJSValue;
+
+namespace mozilla {
+namespace net {
+
+class SocketData
+ : public nsISupports
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ SocketData()
+ {
+ mTotalSent = 0;
+ mTotalRecv = 0;
+ mThread = nullptr;
+ }
+
+ uint64_t mTotalSent;
+ uint64_t mTotalRecv;
+ nsTArray<SocketInfo> mData;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+
+private:
+ virtual ~SocketData()
+ {
+ }
+};
+
+static void GetErrorString(nsresult rv, nsAString& errorString);
+
+NS_IMPL_ISUPPORTS0(SocketData)
+
+
+class HttpData
+ : public nsISupports
+{
+ virtual ~HttpData()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ HttpData()
+ {
+ mThread = nullptr;
+ }
+
+ nsTArray<HttpRetParams> mData;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+};
+
+NS_IMPL_ISUPPORTS0(HttpData)
+
+
+class WebSocketRequest
+ : public nsISupports
+{
+ virtual ~WebSocketRequest()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ WebSocketRequest()
+ {
+ mThread = nullptr;
+ }
+
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+};
+
+NS_IMPL_ISUPPORTS0(WebSocketRequest)
+
+
+class DnsData
+ : public nsISupports
+{
+ virtual ~DnsData()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DnsData()
+ {
+ mThread = nullptr;
+ }
+
+ nsTArray<DNSCacheEntries> mData;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+};
+
+NS_IMPL_ISUPPORTS0(DnsData)
+
+
+class ConnectionData
+ : public nsITransportEventSink
+ , public nsITimerCallback
+{
+ virtual ~ConnectionData()
+ {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSITIMERCALLBACK
+
+ void StartTimer(uint32_t aTimeout);
+ void StopTimer();
+
+ explicit ConnectionData(Dashboard *target)
+ {
+ mThread = nullptr;
+ mDashboard = target;
+ }
+
+ nsCOMPtr<nsISocketTransport> mSocket;
+ nsCOMPtr<nsIInputStream> mStreamIn;
+ nsCOMPtr<nsITimer> mTimer;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+ Dashboard *mDashboard;
+
+ nsCString mHost;
+ uint32_t mPort;
+ const char *mProtocol;
+ uint32_t mTimeout;
+
+ nsString mStatus;
+};
+
+NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback)
+
+NS_IMETHODIMP
+ConnectionData::OnTransportStatus(nsITransport *aTransport, nsresult aStatus,
+ int64_t aProgress, int64_t aProgressMax)
+{
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
+ StopTimer();
+ }
+
+ GetErrorString(aStatus, mStatus);
+ mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>
+ (mDashboard, &Dashboard::GetConnectionStatus, this),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ConnectionData::Notify(nsITimer *aTimer)
+{
+ MOZ_ASSERT(aTimer == mTimer);
+
+ if (mSocket) {
+ mSocket->Close(NS_ERROR_ABORT);
+ mSocket = nullptr;
+ mStreamIn = nullptr;
+ }
+
+ mTimer = nullptr;
+
+ mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT");
+ mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>
+ (mDashboard, &Dashboard::GetConnectionStatus, this),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+void
+ConnectionData::StartTimer(uint32_t aTimeout)
+{
+ if (!mTimer) {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ mTimer->InitWithCallback(this, aTimeout * 1000,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+ConnectionData::StopTimer()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+
+class LookupHelper;
+
+class LookupArgument
+ : public nsISupports
+{
+ virtual ~LookupArgument()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ LookupArgument(nsIDNSRecord *aRecord, LookupHelper *aHelper)
+ {
+ mRecord = aRecord;
+ mHelper = aHelper;
+ }
+
+ nsCOMPtr<nsIDNSRecord> mRecord;
+ RefPtr<LookupHelper> mHelper;
+};
+
+NS_IMPL_ISUPPORTS0(LookupArgument)
+
+
+class LookupHelper
+ : public nsIDNSListener
+{
+ virtual ~LookupHelper()
+ {
+ if (mCancel) {
+ mCancel->Cancel(NS_ERROR_ABORT);
+ }
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ LookupHelper() {
+ }
+
+ nsresult ConstructAnswer(LookupArgument *aArgument);
+public:
+ nsCOMPtr<nsICancelable> mCancel;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+ nsresult mStatus;
+};
+
+NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener)
+
+NS_IMETHODIMP
+LookupHelper::OnLookupComplete(nsICancelable *aRequest,
+ nsIDNSRecord *aRecord, nsresult aStatus)
+{
+ MOZ_ASSERT(aRequest == mCancel);
+ mCancel = nullptr;
+ mStatus = aStatus;
+
+ RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this);
+ mThread->Dispatch(NewRunnableMethod<RefPtr<LookupArgument>>
+ (this, &LookupHelper::ConstructAnswer, arg),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+nsresult
+LookupHelper::ConstructAnswer(LookupArgument *aArgument)
+{
+ nsIDNSRecord *aRecord = aArgument->mRecord;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::DNSLookupDict dict;
+ dict.mAddress.Construct();
+
+ Sequence<nsString> &addresses = dict.mAddress.Value();
+
+ if (NS_SUCCEEDED(mStatus)) {
+ dict.mAnswer = true;
+ bool hasMore;
+ aRecord->HasMore(&hasMore);
+ while (hasMore) {
+ nsString* nextAddress = addresses.AppendElement(fallible);
+ if (!nextAddress) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCString nextAddressASCII;
+ aRecord->GetNextAddrAsString(nextAddressASCII);
+ CopyASCIItoUTF16(nextAddressASCII, *nextAddress);
+ aRecord->HasMore(&hasMore);
+ }
+ } else {
+ dict.mAnswer = false;
+ GetErrorString(mStatus, dict.mError);
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ this->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier)
+
+Dashboard::Dashboard()
+{
+ mEnableLogging = false;
+}
+
+Dashboard::~Dashboard()
+{
+}
+
+NS_IMETHODIMP
+Dashboard::RequestSockets(NetDashboardCallback *aCallback)
+{
+ RefPtr<SocketData> socketData = new SocketData();
+ socketData->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ socketData->mThread = NS_GetCurrentThread();
+ gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<SocketData>>
+ (this, &Dashboard::GetSocketsDispatch, socketData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetSocketsDispatch(SocketData *aSocketData)
+{
+ RefPtr<SocketData> socketData = aSocketData;
+ if (gSocketTransportService) {
+ gSocketTransportService->GetSocketConnections(&socketData->mData);
+ socketData->mTotalSent = gSocketTransportService->GetSentBytes();
+ socketData->mTotalRecv = gSocketTransportService->GetReceivedBytes();
+ }
+ socketData->mThread->Dispatch(NewRunnableMethod<RefPtr<SocketData>>
+ (this, &Dashboard::GetSockets, socketData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetSockets(SocketData *aSocketData)
+{
+ RefPtr<SocketData> socketData = aSocketData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::SocketsDict dict;
+ dict.mSockets.Construct();
+ dict.mSent = 0;
+ dict.mReceived = 0;
+
+ Sequence<mozilla::dom::SocketElement> &sockets = dict.mSockets.Value();
+
+ uint32_t length = socketData->mData.Length();
+ if (!sockets.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < socketData->mData.Length(); i++) {
+ dom::SocketElement &mSocket = *sockets.AppendElement(fallible);
+ CopyASCIItoUTF16(socketData->mData[i].host, mSocket.mHost);
+ mSocket.mPort = socketData->mData[i].port;
+ mSocket.mActive = socketData->mData[i].active;
+ mSocket.mTcp = socketData->mData[i].tcp;
+ mSocket.mSent = (double) socketData->mData[i].sent;
+ mSocket.mReceived = (double) socketData->mData[i].received;
+ dict.mSent += socketData->mData[i].sent;
+ dict.mReceived += socketData->mData[i].received;
+ }
+
+ dict.mSent += socketData->mTotalSent;
+ dict.mReceived += socketData->mTotalRecv;
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val))
+ return NS_ERROR_FAILURE;
+ socketData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestHttpConnections(NetDashboardCallback *aCallback)
+{
+ RefPtr<HttpData> httpData = new HttpData();
+ httpData->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ httpData->mThread = NS_GetCurrentThread();
+
+ gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<HttpData>>
+ (this, &Dashboard::GetHttpDispatch, httpData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetHttpDispatch(HttpData *aHttpData)
+{
+ RefPtr<HttpData> httpData = aHttpData;
+ HttpInfo::GetHttpConnectionData(&httpData->mData);
+ httpData->mThread->Dispatch(NewRunnableMethod<RefPtr<HttpData>>
+ (this, &Dashboard::GetHttpConnections, httpData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+
+nsresult
+Dashboard::GetHttpConnections(HttpData *aHttpData)
+{
+ RefPtr<HttpData> httpData = aHttpData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::HttpConnDict dict;
+ dict.mConnections.Construct();
+
+ using mozilla::dom::HalfOpenInfoDict;
+ using mozilla::dom::HttpConnectionElement;
+ using mozilla::dom::HttpConnInfo;
+ Sequence<HttpConnectionElement> &connections = dict.mConnections.Value();
+
+ uint32_t length = httpData->mData.Length();
+ if (!connections.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < httpData->mData.Length(); i++) {
+ HttpConnectionElement &connection = *connections.AppendElement(fallible);
+
+ CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost);
+ connection.mPort = httpData->mData[i].port;
+ connection.mSpdy = httpData->mData[i].spdy;
+ connection.mSsl = httpData->mData[i].ssl;
+
+ connection.mActive.Construct();
+ connection.mIdle.Construct();
+ connection.mHalfOpens.Construct();
+
+ Sequence<HttpConnInfo> &active = connection.mActive.Value();
+ Sequence<HttpConnInfo> &idle = connection.mIdle.Value();
+ Sequence<HalfOpenInfoDict> &halfOpens = connection.mHalfOpens.Value();
+
+ if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) ||
+ !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) ||
+ !halfOpens.SetCapacity(httpData->mData[i].halfOpens.Length(),
+ fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) {
+ HttpConnInfo &info = *active.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].active[j].rtt;
+ info.mTtl = httpData->mData[i].active[j].ttl;
+ info.mProtocolVersion =
+ httpData->mData[i].active[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) {
+ HttpConnInfo &info = *idle.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].idle[j].rtt;
+ info.mTtl = httpData->mData[i].idle[j].ttl;
+ info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].halfOpens.Length(); j++) {
+ HalfOpenInfoDict &info = *halfOpens.AppendElement(fallible);
+ info.mSpeculative = httpData->mData[i].halfOpens[j].speculative;
+ }
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ httpData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::GetEnableLogging(bool *value)
+{
+ *value = mEnableLogging;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::SetEnableLogging(const bool value)
+{
+ mEnableLogging = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted)
+{
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ LogData mData(nsCString(aHost), aSerial, aEncrypted);
+ if (mWs.data.Contains(mData)) {
+ return NS_OK;
+ }
+ if (!mWs.data.AppendElement(mData)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial)
+{
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1)
+ return NS_ERROR_FAILURE;
+ mWs.data.RemoveElementAt(index);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial, uint32_t aLength)
+{
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1)
+ return NS_ERROR_FAILURE;
+ mWs.data[index].mMsgSent++;
+ mWs.data[index].mSizeSent += aLength;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial, uint32_t aLength)
+{
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1)
+ return NS_ERROR_FAILURE;
+ mWs.data[index].mMsgReceived++;
+ mWs.data[index].mSizeReceived += aLength;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestWebsocketConnections(NetDashboardCallback *aCallback)
+{
+ RefPtr<WebSocketRequest> wsRequest = new WebSocketRequest();
+ wsRequest->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ wsRequest->mThread = NS_GetCurrentThread();
+
+ wsRequest->mThread->Dispatch(NewRunnableMethod<RefPtr<WebSocketRequest>>
+ (this, &Dashboard::GetWebSocketConnections, wsRequest),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetWebSocketConnections(WebSocketRequest *aWsRequest)
+{
+ RefPtr<WebSocketRequest> wsRequest = aWsRequest;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::WebSocketDict dict;
+ dict.mWebsockets.Construct();
+ Sequence<mozilla::dom::WebSocketElement> &websockets =
+ dict.mWebsockets.Value();
+
+ mozilla::MutexAutoLock lock(mWs.lock);
+ uint32_t length = mWs.data.Length();
+ if (!websockets.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < mWs.data.Length(); i++) {
+ dom::WebSocketElement &websocket = *websockets.AppendElement(fallible);
+ CopyASCIItoUTF16(mWs.data[i].mHost, websocket.mHostport);
+ websocket.mMsgsent = mWs.data[i].mMsgSent;
+ websocket.mMsgreceived = mWs.data[i].mMsgReceived;
+ websocket.mSentsize = mWs.data[i].mSizeSent;
+ websocket.mReceivedsize = mWs.data[i].mSizeReceived;
+ websocket.mEncrypted = mWs.data[i].mEncrypted;
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ wsRequest->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSInfo(NetDashboardCallback *aCallback)
+{
+ RefPtr<DnsData> dnsData = new DnsData();
+ dnsData->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+
+ nsresult rv;
+ dnsData->mData.Clear();
+ dnsData->mThread = NS_GetCurrentThread();
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<DnsData>>
+ (this, &Dashboard::GetDnsInfoDispatch, dnsData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetDnsInfoDispatch(DnsData *aDnsData)
+{
+ RefPtr<DnsData> dnsData = aDnsData;
+ if (mDnsService) {
+ mDnsService->GetDNSCacheEntries(&dnsData->mData);
+ }
+ dnsData->mThread->Dispatch(NewRunnableMethod<RefPtr<DnsData>>
+ (this, &Dashboard::GetDNSCacheEntries, dnsData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetDNSCacheEntries(DnsData *dnsData)
+{
+ AutoSafeJSContext cx;
+
+ mozilla::dom::DNSCacheDict dict;
+ dict.mEntries.Construct();
+ Sequence<mozilla::dom::DnsCacheEntry> &entries = dict.mEntries.Value();
+
+ uint32_t length = dnsData->mData.Length();
+ if (!entries.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < dnsData->mData.Length(); i++) {
+ dom::DnsCacheEntry &entry = *entries.AppendElement(fallible);
+ entry.mHostaddr.Construct();
+
+ Sequence<nsString> &addrs = entry.mHostaddr.Value();
+ if (!addrs.SetCapacity(dnsData->mData[i].hostaddr.Length(), fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CopyASCIItoUTF16(dnsData->mData[i].hostname, entry.mHostname);
+ entry.mExpiration = dnsData->mData[i].expiration;
+
+ for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) {
+ nsString* addr = addrs.AppendElement(fallible);
+ if (!addr) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr);
+ }
+
+ if (dnsData->mData[i].family == PR_AF_INET6) {
+ CopyASCIItoUTF16("ipv6", entry.mFamily);
+ } else {
+ CopyASCIItoUTF16("ipv4", entry.mFamily);
+ }
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ dnsData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSLookup(const nsACString &aHost,
+ NetDashboardCallback *aCallback)
+{
+ nsresult rv;
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ RefPtr<LookupHelper> helper = new LookupHelper();
+ helper->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ helper->mThread = NS_GetCurrentThread();
+ rv = mDnsService->AsyncResolve(aHost, 0, helper.get(),
+ NS_GetCurrentThread(),
+ getter_AddRefs(helper->mCancel));
+ return rv;
+}
+
+void
+HttpConnInfo::SetHTTP1ProtocolVersion(uint8_t pv)
+{
+ switch (pv) {
+ case NS_HTTP_VERSION_0_9:
+ protocolVersion.AssignLiteral(u"http/0.9");
+ break;
+ case NS_HTTP_VERSION_1_0:
+ protocolVersion.AssignLiteral(u"http/1.0");
+ break;
+ case NS_HTTP_VERSION_1_1:
+ protocolVersion.AssignLiteral(u"http/1.1");
+ break;
+ case NS_HTTP_VERSION_2_0:
+ protocolVersion.AssignLiteral(u"http/2.0");
+ break;
+ default:
+ protocolVersion.AssignLiteral(u"unknown protocol version");
+ }
+}
+
+void
+HttpConnInfo::SetHTTP2ProtocolVersion(uint8_t pv)
+{
+ MOZ_ASSERT (pv == HTTP_VERSION_2);
+ protocolVersion.Assign(u"h2");
+}
+
+NS_IMETHODIMP
+Dashboard::GetLogPath(nsACString &aLogPath)
+{
+ aLogPath.SetCapacity(2048);
+ uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048);
+ aLogPath.SetLength(len);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort,
+ const char *aProtocol, uint32_t aTimeout,
+ NetDashboardCallback *aCallback)
+{
+ nsresult rv;
+ RefPtr<ConnectionData> connectionData = new ConnectionData(this);
+ connectionData->mHost = aHost;
+ connectionData->mPort = aPort;
+ connectionData->mProtocol = aProtocol;
+ connectionData->mTimeout = aTimeout;
+
+ connectionData->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ connectionData->mThread = NS_GetCurrentThread();
+
+ rv = TestNewConnection(connectionData);
+ if (NS_FAILED(rv)) {
+ mozilla::net::GetErrorString(rv, connectionData->mStatus);
+ connectionData->mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>
+ (this, &Dashboard::GetConnectionStatus, connectionData),
+ NS_DISPATCH_NORMAL);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetConnectionStatus(ConnectionData *aConnectionData)
+{
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::ConnStatusDict dict;
+ dict.mStatus = connectionData->mStatus;
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val))
+ return NS_ERROR_FAILURE;
+
+ connectionData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+nsresult
+Dashboard::TestNewConnection(ConnectionData *aConnectionData)
+{
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+
+ nsresult rv;
+ if (!connectionData->mHost.Length() ||
+ !net_IsValidHostName(connectionData->mHost)) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (connectionData->mProtocol &&
+ NS_LITERAL_STRING("ssl").EqualsASCII(connectionData->mProtocol)) {
+ rv = gSocketTransportService->CreateTransport(
+ &connectionData->mProtocol, 1, connectionData->mHost,
+ connectionData->mPort, nullptr,
+ getter_AddRefs(connectionData->mSocket));
+ } else {
+ rv = gSocketTransportService->CreateTransport(
+ nullptr, 0, connectionData->mHost,
+ connectionData->mPort, nullptr,
+ getter_AddRefs(connectionData->mSocket));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->SetEventSink(connectionData,
+ NS_GetCurrentThread());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->OpenInputStream(
+ nsITransport::OPEN_BLOCKING, 0, 0,
+ getter_AddRefs(connectionData->mStreamIn));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ connectionData->StartTimer(connectionData->mTimeout);
+
+ return rv;
+}
+
+typedef struct
+{
+ nsresult key;
+ const char *error;
+} ErrorEntry;
+
+#undef ERROR
+#define ERROR(key, val) {key, #key}
+
+ErrorEntry socketTransportStatuses[] = {
+ ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)),
+ ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)),
+ ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)),
+ ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)),
+ ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)),
+ ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)),
+ ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)),
+};
+#undef ERROR
+
+
+static void
+GetErrorString(nsresult rv, nsAString& errorString)
+{
+ for (size_t i = 0; i < ArrayLength(socketTransportStatuses); ++i) {
+ if (socketTransportStatuses[i].key == rv) {
+ errorString.AssignASCII(socketTransportStatuses[i].error);
+ return;
+ }
+ }
+ nsAutoCString errorCString;
+ mozilla::GetErrorName(rv, errorCString);
+ CopyUTF8toUTF16(errorCString, errorString);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/Dashboard.h b/netwerk/base/Dashboard.h
new file mode 100644
index 0000000000..4e893c15d4
--- /dev/null
+++ b/netwerk/base/Dashboard.h
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDashboard_h__
+#define nsDashboard_h__
+
+#include "mozilla/Mutex.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsIDashboard.h"
+#include "nsIDashboardEventNotifier.h"
+#include "nsIDNSListener.h"
+#include "nsIServiceManager.h"
+#include "nsITimer.h"
+#include "nsITransport.h"
+
+class nsIDNSService;
+
+namespace mozilla {
+namespace net {
+
+class SocketData;
+class HttpData;
+class DnsData;
+class WebSocketRequest;
+class ConnectionData;
+
+class Dashboard final
+ : public nsIDashboard
+ , public nsIDashboardEventNotifier
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDASHBOARD
+ NS_DECL_NSIDASHBOARDEVENTNOTIFIER
+
+ Dashboard();
+ static const char *GetErrorString(nsresult rv);
+ nsresult GetConnectionStatus(ConnectionData *aConnectionData);
+
+private:
+
+ struct LogData
+ {
+ LogData(nsCString host, uint32_t serial, bool encryption):
+ mHost(host),
+ mSerial(serial),
+ mMsgSent(0),
+ mMsgReceived(0),
+ mSizeSent(0),
+ mSizeReceived(0),
+ mEncrypted(encryption)
+ { }
+ nsCString mHost;
+ uint32_t mSerial;
+ uint32_t mMsgSent;
+ uint32_t mMsgReceived;
+ uint64_t mSizeSent;
+ uint64_t mSizeReceived;
+ bool mEncrypted;
+ bool operator==(const LogData& a) const
+ {
+ return mHost.Equals(a.mHost) && (mSerial == a.mSerial);
+ }
+ };
+
+ struct WebSocketData
+ {
+ WebSocketData():lock("Dashboard.webSocketData")
+ {
+ }
+ uint32_t IndexOf(nsCString hostname, uint32_t mSerial)
+ {
+ LogData temp(hostname, mSerial, false);
+ return data.IndexOf(temp);
+ }
+ nsTArray<LogData> data;
+ mozilla::Mutex lock;
+ };
+
+
+ bool mEnableLogging;
+ WebSocketData mWs;
+
+private:
+ virtual ~Dashboard();
+
+ nsresult GetSocketsDispatch(SocketData *);
+ nsresult GetHttpDispatch(HttpData *);
+ nsresult GetDnsInfoDispatch(DnsData *);
+ nsresult TestNewConnection(ConnectionData *);
+
+ /* Helper methods that pass the JSON to the callback function. */
+ nsresult GetSockets(SocketData *);
+ nsresult GetHttpConnections(HttpData *);
+ nsresult GetDNSCacheEntries(DnsData *);
+ nsresult GetWebSocketConnections(WebSocketRequest *);
+
+ nsCOMPtr<nsIDNSService> mDnsService;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsDashboard_h__
diff --git a/netwerk/base/DashboardTypes.h b/netwerk/base/DashboardTypes.h
new file mode 100644
index 0000000000..3f1f30d16b
--- /dev/null
+++ b/netwerk/base/DashboardTypes.h
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_DashboardTypes_h_
+#define mozilla_net_DashboardTypes_h_
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+struct SocketInfo
+{
+ nsCString host;
+ uint64_t sent;
+ uint64_t received;
+ uint16_t port;
+ bool active;
+ bool tcp;
+};
+
+struct HalfOpenSockets
+{
+ bool speculative;
+};
+
+struct DNSCacheEntries
+{
+ nsCString hostname;
+ nsTArray<nsCString> hostaddr;
+ uint16_t family;
+ int64_t expiration;
+ nsCString netInterface;
+};
+
+struct HttpConnInfo
+{
+ uint32_t ttl;
+ uint32_t rtt;
+ nsString protocolVersion;
+
+ void SetHTTP1ProtocolVersion(uint8_t pv);
+ void SetHTTP2ProtocolVersion(uint8_t pv);
+};
+
+struct HttpRetParams
+{
+ nsCString host;
+ nsTArray<HttpConnInfo> active;
+ nsTArray<HttpConnInfo> idle;
+ nsTArray<HalfOpenSockets> halfOpens;
+ uint32_t counter;
+ uint16_t port;
+ bool spdy;
+ bool ssl;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DashboardTypes_h_
diff --git a/netwerk/base/EventTokenBucket.cpp b/netwerk/base/EventTokenBucket.cpp
new file mode 100644
index 0000000000..e12624ea2a
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.cpp
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EventTokenBucket.h"
+
+#include "nsICancelable.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#ifdef DEBUG
+#include "MainThreadUtils.h"
+#endif
+
+#ifdef XP_WIN
+#include <windows.h>
+#include <mmsystem.h>
+#endif
+
+namespace mozilla {
+namespace net {
+
+////////////////////////////////////////////
+// EventTokenBucketCancelable
+////////////////////////////////////////////
+
+class TokenBucketCancelable : public nsICancelable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ explicit TokenBucketCancelable(class ATokenBucketEvent *event);
+ void Fire();
+
+private:
+ virtual ~TokenBucketCancelable() {}
+
+ friend class EventTokenBucket;
+ ATokenBucketEvent *mEvent;
+};
+
+NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable)
+
+TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent *event)
+ : mEvent(event)
+{
+}
+
+NS_IMETHODIMP
+TokenBucketCancelable::Cancel(nsresult reason)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mEvent = nullptr;
+ return NS_OK;
+}
+
+void
+TokenBucketCancelable::Fire()
+{
+ if (!mEvent)
+ return;
+
+ ATokenBucketEvent *event = mEvent;
+ mEvent = nullptr;
+ event->OnTokenBucketAdmitted();
+}
+
+////////////////////////////////////////////
+// EventTokenBucket
+////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback)
+
+// by default 1hz with no burst
+EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond,
+ uint32_t burstSize)
+ : mUnitCost(kUsecPerSec)
+ , mMaxCredit(kUsecPerSec)
+ , mCredit(kUsecPerSec)
+ , mPaused(false)
+ , mStopped(false)
+ , mTimerArmed(false)
+#ifdef XP_WIN
+ , mFineGrainTimerInUse(false)
+ , mFineGrainResetTimerArmed(false)
+#endif
+{
+ mLastUpdate = TimeStamp::Now();
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv))
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mTimer)
+ mTimer->SetTarget(sts);
+ SetRate(eventsPerSecond, burstSize);
+}
+
+EventTokenBucket::~EventTokenBucket()
+{
+ SOCKET_LOG(("EventTokenBucket::dtor %p events=%d\n",
+ this, mEvents.GetSize()));
+
+ CleanupTimers();
+
+ // Complete any queued events to prevent hangs
+ while (mEvents.GetSize()) {
+ RefPtr<TokenBucketCancelable> cancelable =
+ dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront()));
+ cancelable->Fire();
+ }
+}
+
+void
+EventTokenBucket::CleanupTimers()
+{
+ if (mTimer && mTimerArmed) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mTimerArmed = false;
+
+#ifdef XP_WIN
+ NormalTimers();
+ if (mFineGrainResetTimer && mFineGrainResetTimerArmed) {
+ mFineGrainResetTimer->Cancel();
+ }
+ mFineGrainResetTimer = nullptr;
+ mFineGrainResetTimerArmed = false;
+#endif
+}
+
+void
+EventTokenBucket::SetRate(uint32_t eventsPerSecond,
+ uint32_t burstSize)
+{
+ SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n",
+ this, eventsPerSecond, burstSize));
+
+ if (eventsPerSecond > kMaxHz) {
+ eventsPerSecond = kMaxHz;
+ SOCKET_LOG((" eventsPerSecond out of range\n"));
+ }
+
+ if (!eventsPerSecond) {
+ eventsPerSecond = 1;
+ SOCKET_LOG((" eventsPerSecond out of range\n"));
+ }
+
+ mUnitCost = kUsecPerSec / eventsPerSecond;
+ mMaxCredit = mUnitCost * burstSize;
+ if (mMaxCredit > kUsecPerSec * 60 * 15) {
+ SOCKET_LOG((" burstSize out of range\n"));
+ mMaxCredit = kUsecPerSec * 60 * 15;
+ }
+ mCredit = mMaxCredit;
+ mLastUpdate = TimeStamp::Now();
+}
+
+void
+EventTokenBucket::ClearCredits()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this));
+ mCredit = 0;
+}
+
+uint32_t
+EventTokenBucket::BurstEventsAvailable()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return static_cast<uint32_t>(mCredit / mUnitCost);
+}
+
+uint32_t
+EventTokenBucket::QueuedEvents()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mEvents.GetSize();
+}
+
+void
+EventTokenBucket::Pause()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::Pause %p\n", this));
+ if (mPaused || mStopped)
+ return;
+
+ mPaused = true;
+ if (mTimerArmed) {
+ mTimer->Cancel();
+ mTimerArmed = false;
+ }
+}
+
+void
+EventTokenBucket::UnPause()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this));
+ if (!mPaused || mStopped)
+ return;
+
+ mPaused = false;
+ DispatchEvents();
+ UpdateTimer();
+}
+
+void
+EventTokenBucket::Stop()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::Stop %p armed=%d\n", this, mTimerArmed));
+ mStopped = true;
+ CleanupTimers();
+
+ // Complete any queued events to prevent hangs
+ while (mEvents.GetSize()) {
+ RefPtr<TokenBucketCancelable> cancelable =
+ dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront()));
+ cancelable->Fire();
+ }
+}
+
+nsresult
+EventTokenBucket::SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this));
+
+ if (mStopped || !mTimer)
+ return NS_ERROR_FAILURE;
+
+ UpdateCredits();
+
+ RefPtr<TokenBucketCancelable> cancelEvent = new TokenBucketCancelable(event);
+ // When this function exits the cancelEvent needs 2 references, one for the
+ // mEvents queue and one for the caller of SubmitEvent()
+
+ NS_ADDREF(*cancelable = cancelEvent.get());
+
+ if (mPaused || !TryImmediateDispatch(cancelEvent.get())) {
+ // queue it
+ SOCKET_LOG((" queued\n"));
+ mEvents.Push(cancelEvent.forget().take());
+ UpdateTimer();
+ }
+ else {
+ SOCKET_LOG((" dispatched synchronously\n"));
+ }
+
+ return NS_OK;
+}
+
+bool
+EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable *cancelable)
+{
+ if (mCredit < mUnitCost)
+ return false;
+
+ mCredit -= mUnitCost;
+ cancelable->Fire();
+ return true;
+}
+
+void
+EventTokenBucket::DispatchEvents()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused));
+ if (mPaused || mStopped)
+ return;
+
+ while (mEvents.GetSize() && mUnitCost <= mCredit) {
+ RefPtr<TokenBucketCancelable> cancelable =
+ dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront()));
+ if (cancelable->mEvent) {
+ SOCKET_LOG(("EventTokenBucket::DispachEvents [%p] "
+ "Dispatching queue token bucket event cost=%lu credit=%lu\n",
+ this, mUnitCost, mCredit));
+ mCredit -= mUnitCost;
+ cancelable->Fire();
+ }
+ }
+
+#ifdef XP_WIN
+ if (!mEvents.GetSize())
+ WantNormalTimers();
+#endif
+}
+
+void
+EventTokenBucket::UpdateTimer()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer)
+ return;
+
+ if (mCredit >= mUnitCost)
+ return;
+
+ // determine the time needed to wait to accumulate enough credits to admit
+ // one more event and set the timer for that point. Always round it
+ // up because firing early doesn't help.
+ //
+ uint64_t deficit = mUnitCost - mCredit;
+ uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec;
+
+ if (msecWait < 4) // minimum wait
+ msecWait = 4;
+ else if (msecWait > 60000) // maximum wait
+ msecWait = 60000;
+
+#ifdef XP_WIN
+ FineGrainTimers();
+#endif
+
+ SOCKET_LOG(("EventTokenBucket::UpdateTimer %p for %dms\n",
+ this, msecWait));
+ nsresult rv = mTimer->InitWithCallback(this, static_cast<uint32_t>(msecWait),
+ nsITimer::TYPE_ONE_SHOT);
+ mTimerArmed = NS_SUCCEEDED(rv);
+}
+
+NS_IMETHODIMP
+EventTokenBucket::Notify(nsITimer *timer)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+#ifdef XP_WIN
+ if (timer == mFineGrainResetTimer) {
+ FineGrainResetTimerNotify();
+ return NS_OK;
+ }
+#endif
+
+ SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this));
+ mTimerArmed = false;
+ if (mStopped)
+ return NS_OK;
+
+ UpdateCredits();
+ DispatchEvents();
+ UpdateTimer();
+
+ return NS_OK;
+}
+
+void
+EventTokenBucket::UpdateCredits()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration elapsed = now - mLastUpdate;
+ mLastUpdate = now;
+
+ mCredit += static_cast<uint64_t>(elapsed.ToMicroseconds());
+ if (mCredit > mMaxCredit)
+ mCredit = mMaxCredit;
+ SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %lu (%lu each.. %3.2f)\n",
+ this, mCredit, mUnitCost, (double)mCredit / mUnitCost));
+}
+
+#ifdef XP_WIN
+void
+EventTokenBucket::FineGrainTimers()
+{
+ SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n",
+ this, mFineGrainTimerInUse));
+
+ mLastFineGrainTimerUse = TimeStamp::Now();
+
+ if (mFineGrainTimerInUse)
+ return;
+
+ if (mUnitCost > kCostFineGrainThreshold)
+ return;
+
+ SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n",
+ this));
+
+ mFineGrainTimerInUse = true;
+ timeBeginPeriod(1);
+}
+
+void
+EventTokenBucket::NormalTimers()
+{
+ if (!mFineGrainTimerInUse)
+ return;
+ mFineGrainTimerInUse = false;
+
+ SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this));
+ timeEndPeriod(1);
+}
+
+void
+EventTokenBucket::WantNormalTimers()
+{
+ if (!mFineGrainTimerInUse)
+ return;
+ if (mFineGrainResetTimerArmed)
+ return;
+
+ TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse);
+ static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5);
+
+ if (elapsed >= fiveSeconds) {
+ NormalTimers();
+ return;
+ }
+
+ if (!mFineGrainResetTimer)
+ mFineGrainResetTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+ // if we can't delay the reset, just do it now
+ if (!mFineGrainResetTimer) {
+ NormalTimers();
+ return;
+ }
+
+ // pad the callback out 100ms to avoid having to round trip this again if the
+ // timer calls back just a tad early.
+ SOCKET_LOG(("EventTokenBucket::WantNormalTimers %p "
+ "Will reset timer granularity after delay", this));
+
+ mFineGrainResetTimer->InitWithCallback(
+ this,
+ static_cast<uint32_t>((fiveSeconds - elapsed).ToMilliseconds()) + 100,
+ nsITimer::TYPE_ONE_SHOT);
+ mFineGrainResetTimerArmed = true;
+}
+
+void
+EventTokenBucket::FineGrainResetTimerNotify()
+{
+ SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify() events = %d\n",
+ this, mEvents.GetSize()));
+ mFineGrainResetTimerArmed = false;
+
+ // If we are currently processing events then wait for the queue to drain
+ // before trying to reset back to normal timers again
+ if (!mEvents.GetSize())
+ WantNormalTimers();
+}
+
+#endif
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/EventTokenBucket.h b/netwerk/base/EventTokenBucket.h
new file mode 100644
index 0000000000..b187ca7b06
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetEventTokenBucket_h__
+#define NetEventTokenBucket_h__
+
+#include "ARefBase.h"
+#include "nsCOMPtr.h"
+#include "nsDeque.h"
+#include "nsITimer.h"
+
+#include "mozilla/TimeStamp.h"
+
+class nsICancelable;
+
+namespace mozilla {
+namespace net {
+
+/* A token bucket is used to govern the maximum rate a series of events
+ can be executed at. For instance if your event was "eat a piece of cake"
+ then a token bucket configured to allow "1 piece per day" would spread
+ the eating of a 8 piece cake over 8 days even if you tried to eat the
+ whole thing up front. In a practical sense it 'costs' 1 token to execute
+ an event and tokens are 'earned' at a particular rate as time goes by.
+
+ The token bucket can be perfectly smooth or allow a configurable amount of
+ burstiness. A bursty token bucket allows you to save up unused credits, while
+ a perfectly smooth one would not. A smooth "1 per day" cake token bucket
+ would require 9 days to eat that cake if you skipped a slice on day 4
+ (use the token or lose it), while a token bucket configured with a burst
+ of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day
+ 5) and finish the cake in the usual 8 days.
+
+ EventTokenBucket(hz=20, burst=5) creates a token bucket with the following properties:
+
+ + events from an infinite stream will be admitted 20 times per second (i.e.
+ hz=20 means 1 event per 50 ms). Timers will be used to space things evenly down to
+ 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than 200hz will admit
+ multiple events with 5ms gaps between them. 10000hz is the maximum rate and 1hz is
+ the minimum rate.
+
+ + The burst size controls the limit of 'credits' that a token bucket can accumulate
+ when idle. For our (20,5) example each event requires 50ms of credit (again, 20hz = 50ms
+ per event). a burst size of 5 means that the token bucket can accumulate a
+ maximum of 250ms (5 * 50ms) for this bucket. If no events have been admitted for the
+ last full second the bucket can still only accumulate 250ms of credit - but that credit
+ means that 5 events can be admitted without delay. A burst size of 1 is the minimum.
+ The EventTokenBucket is created with maximum credits already applied, but they
+ can be cleared with the ClearCredits() method. The maximum burst size is
+ 15 minutes worth of events.
+
+ + An event is submitted to the token bucket asynchronously through SubmitEvent().
+ The OnTokenBucketAdmitted() method of the submitted event is used as a callback
+ when the event is ready to run. A cancelable event is returned to the SubmitEvent() caller
+ for use in the case they do not wish to wait for the callback.
+*/
+
+class EventTokenBucket;
+
+class ATokenBucketEvent
+{
+public:
+ virtual void OnTokenBucketAdmitted() = 0;
+};
+
+class TokenBucketCancelable;
+
+class EventTokenBucket : public nsITimerCallback, public ARefBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ // This should be constructed on the main thread
+ EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ // These public methods are all meant to be called from the socket thread
+ void ClearCredits();
+ uint32_t BurstEventsAvailable();
+ uint32_t QueuedEvents();
+
+ // a paused token bucket will not process any events, but it will accumulate
+ // credits. ClearCredits can be used before unpausing if desired.
+ void Pause();
+ void UnPause();
+ void Stop();
+
+ // The returned cancelable event can only be canceled from the socket thread
+ nsresult SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable);
+
+private:
+ virtual ~EventTokenBucket();
+ void CleanupTimers();
+
+ friend class RunNotifyEvent;
+ friend class SetTimerEvent;
+
+ bool TryImmediateDispatch(TokenBucketCancelable *event);
+ void SetRate(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ void DispatchEvents();
+ void UpdateTimer();
+ void UpdateCredits();
+
+ const static uint64_t kUsecPerSec = 1000000;
+ const static uint64_t kUsecPerMsec = 1000;
+ const static uint64_t kMaxHz = 10000;
+
+ uint64_t mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond)
+ uint64_t mMaxCredit; // usec mCredit limit (from busrtSize)
+ uint64_t mCredit; // usec of accumulated credit.
+
+ bool mPaused;
+ bool mStopped;
+ nsDeque mEvents;
+ bool mTimerArmed;
+ TimeStamp mLastUpdate;
+
+ // The timer is created on the main thread, but is armed and executes Notify()
+ // callbacks on the socket thread in order to maintain low latency of event
+ // delivery.
+ nsCOMPtr<nsITimer> mTimer;
+
+#ifdef XP_WIN
+ // Windows timers are 15ms granularity by default. When we have active events
+ // that need to be dispatched at 50ms or less granularity we change the OS
+ // granularity to 1ms. 90 seconds after that need has elapsed we will change it
+ // back
+ const static uint64_t kCostFineGrainThreshold = 50 * kUsecPerMsec;
+
+ void FineGrainTimers(); // get 1ms granularity
+ void NormalTimers(); // reset to default granularity
+ void WantNormalTimers(); // reset after 90 seconds if not needed in interim
+ void FineGrainResetTimerNotify(); // delayed callback to reset
+
+ TimeStamp mLastFineGrainTimerUse;
+ bool mFineGrainTimerInUse;
+ bool mFineGrainResetTimerArmed;
+ nsCOMPtr<nsITimer> mFineGrainResetTimer;
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp
new file mode 100644
index 0000000000..61b9394f99
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.cpp
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LoadContextInfo.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::dom;
+namespace mozilla {
+namespace net {
+
+// LoadContextInfo
+
+NS_IMPL_ISUPPORTS(LoadContextInfo, nsILoadContextInfo)
+
+LoadContextInfo::LoadContextInfo(bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes)
+ : mIsAnonymous(aIsAnonymous)
+ , mOriginAttributes(aOriginAttributes)
+{
+}
+
+LoadContextInfo::~LoadContextInfo()
+{
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool *aIsPrivate)
+{
+ *aIsPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool *aIsAnonymous)
+{
+ *aIsAnonymous = mIsAnonymous;
+ return NS_OK;
+}
+
+NeckoOriginAttributes const* LoadContextInfo::OriginAttributesPtr()
+{
+ return &mOriginAttributes;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetOriginAttributes(JSContext *aCx,
+ JS::MutableHandle<JS::Value> aVal)
+{
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// LoadContextInfoFactory
+
+NS_IMPL_ISUPPORTS(LoadContextInfoFactory, nsILoadContextInfoFactory)
+
+NS_IMETHODIMP LoadContextInfoFactory::GetDefault(nsILoadContextInfo * *aDefault)
+{
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(false, NeckoOriginAttributes());
+ info.forget(aDefault);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::GetPrivate(nsILoadContextInfo * *aPrivate)
+{
+ NeckoOriginAttributes attrs;
+ attrs.SyncAttributesWithPrivateBrowsing(true);
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(false, attrs);
+ info.forget(aPrivate);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::GetAnonymous(nsILoadContextInfo * *aAnonymous)
+{
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(true, NeckoOriginAttributes());
+ info.forget(aAnonymous);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::Custom(bool aAnonymous,
+ JS::HandleValue aOriginAttributes, JSContext *cx,
+ nsILoadContextInfo * *_retval)
+{
+ NeckoOriginAttributes attrs;
+ bool status = attrs.Init(cx, aOriginAttributes);
+ NS_ENSURE_TRUE(status, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aAnonymous, attrs);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::FromLoadContext(nsILoadContext *aLoadContext, bool aAnonymous,
+ nsILoadContextInfo * *_retval)
+{
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aLoadContext, aAnonymous);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::FromWindow(nsIDOMWindow *aWindow, bool aAnonymous,
+ nsILoadContextInfo * *_retval)
+{
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aWindow, aAnonymous);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+// Helper functions
+
+LoadContextInfo *
+GetLoadContextInfo(nsIChannel * aChannel)
+{
+ nsresult rv;
+
+ DebugOnly<bool> pb = NS_UsePrivateBrowsing(aChannel);
+
+ bool anon = false;
+ nsLoadFlags loadFlags;
+ rv = aChannel->GetLoadFlags(&loadFlags);
+ if (NS_SUCCEEDED(rv)) {
+ anon = !!(loadFlags & nsIChannel::LOAD_ANONYMOUS);
+ }
+
+ NeckoOriginAttributes oa;
+ NS_GetOriginAttributes(aChannel, oa);
+ MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0));
+
+ return new LoadContextInfo(anon, oa);
+}
+
+LoadContextInfo *
+GetLoadContextInfo(nsILoadContext *aLoadContext, bool aIsAnonymous)
+{
+ if (!aLoadContext) {
+ return new LoadContextInfo(aIsAnonymous,
+ NeckoOriginAttributes(nsILoadContextInfo::NO_APP_ID, false));
+ }
+
+ DebugOnly<bool> pb = aLoadContext->UsePrivateBrowsing();
+ DocShellOriginAttributes doa;
+ aLoadContext->GetOriginAttributes(doa);
+ MOZ_ASSERT(pb == (doa.mPrivateBrowsingId > 0));
+
+ NeckoOriginAttributes noa;
+ noa.InheritFromDocShellToNecko(doa);
+
+ return new LoadContextInfo(aIsAnonymous, noa);
+}
+
+LoadContextInfo*
+GetLoadContextInfo(nsIDOMWindow *aWindow,
+ bool aIsAnonymous)
+{
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
+
+ return GetLoadContextInfo(loadContext, aIsAnonymous);
+}
+
+LoadContextInfo *
+GetLoadContextInfo(nsILoadContextInfo *aInfo)
+{
+ return new LoadContextInfo(aInfo->IsAnonymous(),
+ *aInfo->OriginAttributesPtr());
+}
+
+LoadContextInfo *
+GetLoadContextInfo(bool const aIsAnonymous,
+ NeckoOriginAttributes const &aOriginAttributes)
+{
+ return new LoadContextInfo(aIsAnonymous,
+ aOriginAttributes);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/LoadContextInfo.h b/netwerk/base/LoadContextInfo.h
new file mode 100644
index 0000000000..8477dfd1cc
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.h
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLoadContextInfo_h__
+#define nsLoadContextInfo_h__
+
+#include "nsILoadContextInfo.h"
+
+class nsIChannel;
+class nsILoadContext;
+
+namespace mozilla {
+namespace net {
+
+class LoadContextInfo : public nsILoadContextInfo
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILOADCONTEXTINFO
+
+ LoadContextInfo(bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes);
+
+private:
+ virtual ~LoadContextInfo();
+
+protected:
+ bool mIsAnonymous : 1;
+ NeckoOriginAttributes mOriginAttributes;
+};
+
+class LoadContextInfoFactory : public nsILoadContextInfoFactory
+{
+ virtual ~LoadContextInfoFactory() {}
+public:
+ NS_DECL_ISUPPORTS // deliberately not thread-safe
+ NS_DECL_NSILOADCONTEXTINFOFACTORY
+};
+
+LoadContextInfo*
+GetLoadContextInfo(nsIChannel *aChannel);
+
+LoadContextInfo*
+GetLoadContextInfo(nsILoadContext *aLoadContext,
+ bool aIsAnonymous);
+
+LoadContextInfo*
+GetLoadContextInfo(nsIDOMWindow *aLoadContext,
+ bool aIsAnonymous);
+
+LoadContextInfo*
+GetLoadContextInfo(nsILoadContextInfo *aInfo);
+
+LoadContextInfo*
+GetLoadContextInfo(bool const aIsAnonymous,
+ NeckoOriginAttributes const &aOriginAttributes);
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
new file mode 100644
index 0000000000..216cf559cd
--- /dev/null
+++ b/netwerk/base/LoadInfo.cpp
@@ -0,0 +1,925 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/LoadInfo.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsFrameLoader.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIFrameLoader.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsUtils.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsGlobalWindow.h"
+#include "nsNullPrincipal.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+static void
+InheritOriginAttributes(nsIPrincipal* aLoadingPrincipal, NeckoOriginAttributes& aAttrs)
+{
+ const PrincipalOriginAttributes attrs =
+ BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef();
+ aAttrs.InheritFromDocToNecko(attrs);
+}
+
+LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsINode* aLoadingContext,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType)
+ : mLoadingPrincipal(aLoadingContext ?
+ aLoadingContext->NodePrincipal() : aLoadingPrincipal)
+ , mTriggeringPrincipal(aTriggeringPrincipal ?
+ aTriggeringPrincipal : mLoadingPrincipal.get())
+ , mPrincipalToInherit(nullptr)
+ , mLoadingContext(do_GetWeakReference(aLoadingContext))
+ , mSecurityFlags(aSecurityFlags)
+ , mInternalContentPolicyType(aContentPolicyType)
+ , mTainting(LoadTainting::Basic)
+ , mUpgradeInsecureRequests(false)
+ , mVerifySignedContent(false)
+ , mEnforceSRI(false)
+ , mForceInheritPrincipalDropped(false)
+ , mInnerWindowID(0)
+ , mOuterWindowID(0)
+ , mParentOuterWindowID(0)
+ , mFrameOuterWindowID(0)
+ , mEnforceSecurity(false)
+ , mInitialSecurityCheckDone(false)
+ , mIsThirdPartyContext(false)
+ , mForcePreflight(false)
+ , mIsPreflight(false)
+ , mForceHSTSPriming(false)
+ , mMixedContentWouldBlock(false)
+{
+ MOZ_ASSERT(mLoadingPrincipal);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+#ifdef DEBUG
+ // TYPE_DOCUMENT loads initiated by javascript tests will go through
+ // nsIOService and use the wrong constructor. Don't enforce the
+ // !TYPE_DOCUMENT check in those cases
+ bool skipContentTypeCheck = false;
+ skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion");
+#endif
+
+ // This constructor shouldn't be used for TYPE_DOCUMENT loads that don't
+ // have a loadingPrincipal
+ MOZ_ASSERT(skipContentTypeCheck ||
+ mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT);
+
+ // TODO(bug 1259873): Above, we initialize mIsThirdPartyContext to false meaning
+ // that consumers of LoadInfo that don't pass a context or pass a context from
+ // which we can't find a window will default to assuming that they're 1st
+ // party. It would be nice if we could default "safe" and assume that we are
+ // 3rd party until proven otherwise.
+
+ // if consumers pass both, aLoadingContext and aLoadingPrincipal
+ // then the loadingPrincipal must be the same as the node's principal
+ MOZ_ASSERT(!aLoadingContext || !aLoadingPrincipal ||
+ aLoadingContext->NodePrincipal() == aLoadingPrincipal);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
+ mSecurityFlags ^= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ mForceInheritPrincipalDropped = true;
+ }
+
+ if (aLoadingContext) {
+ nsCOMPtr<nsPIDOMWindowOuter> contextOuter = aLoadingContext->OwnerDoc()->GetWindow();
+ if (contextOuter) {
+ ComputeIsThirdPartyContext(contextOuter);
+ mOuterWindowID = contextOuter->WindowID();
+ nsCOMPtr<nsPIDOMWindowOuter> parent = contextOuter->GetScriptableParent();
+ mParentOuterWindowID = parent ? parent->WindowID() : mOuterWindowID;
+ }
+
+ mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID();
+
+ // When the element being loaded is a frame, we choose the frame's window
+ // for the window ID and the frame element's window as the parent
+ // window. This is the behavior that Chrome exposes to add-ons.
+ // NB: If the frameLoaderOwner doesn't have a frame loader, then the load
+ // must be coming from an object (such as a plugin) that's loaded into it
+ // instead of a document being loaded. In that case, treat this object like
+ // any other non-document-loading element.
+ nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner =
+ do_QueryInterface(aLoadingContext);
+ nsCOMPtr<nsIFrameLoader> fl = frameLoaderOwner ?
+ frameLoaderOwner->GetFrameLoader() : nullptr;
+ if (fl) {
+ nsCOMPtr<nsIDocShell> docShell;
+ if (NS_SUCCEEDED(fl->GetDocShell(getter_AddRefs(docShell))) && docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell);
+ if (outerWindow) {
+ mFrameOuterWindowID = outerWindow->WindowID();
+ }
+ }
+ }
+
+ // if the document forces all requests to be upgraded from http to https, then
+ // we should do that for all requests. If it only forces preloads to be upgraded
+ // then we should enforce upgrade insecure requests only for preloads.
+ mUpgradeInsecureRequests =
+ aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(false) ||
+ (nsContentUtils::IsPreloadType(mInternalContentPolicyType) &&
+ aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(true));
+
+ // if owner doc has content signature, we enforce SRI
+ nsCOMPtr<nsIChannel> channel = aLoadingContext->OwnerDoc()->GetChannel();
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ if (loadInfo) {
+ mEnforceSRI = loadInfo->GetVerifySignedContent();
+ }
+ }
+ }
+
+ // If CSP requires SRI (require-sri-for), then store that information
+ // in the loadInfo so we can enforce SRI before loading the subresource.
+ if (!mEnforceSRI) {
+ // do not look into the CSP if already true:
+ // a CSP saying that SRI isn't needed should not
+ // overrule GetVerifySignedContent
+ if (aLoadingPrincipal) {
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ aLoadingPrincipal->GetCsp(getter_AddRefs(csp));
+ uint32_t externalType =
+ nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType);
+ // csp could be null if loading principal is system principal
+ if (csp) {
+ csp->RequireSRIForType(externalType, &mEnforceSRI);
+ }
+ // if CSP is delivered via a meta tag, it's speculatively available
+ // as 'preloadCSP'. If we are preloading a script or style, we have
+ // to apply that speculative 'preloadCSP' for such loads.
+ if (!mEnforceSRI && nsContentUtils::IsPreloadType(aContentPolicyType)) {
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCSP;
+ aLoadingPrincipal->GetPreloadCsp(getter_AddRefs(preloadCSP));
+ if (preloadCSP) {
+ preloadCSP->RequireSRIForType(externalType, &mEnforceSRI);
+ }
+ }
+ }
+ }
+
+ InheritOriginAttributes(mLoadingPrincipal, mOriginAttributes);
+
+ // We need to do this after inheriting the document's origin attributes
+ // above, in case the loading principal ends up being the system principal.
+ if (aLoadingContext) {
+ nsCOMPtr<nsILoadContext> loadContext =
+ aLoadingContext->OwnerDoc()->GetLoadContext();
+ nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell();
+ if (loadContext && docShell &&
+ docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
+ bool usePrivateBrowsing;
+ nsresult rv = loadContext->GetUsePrivateBrowsing(&usePrivateBrowsing);
+ if (NS_SUCCEEDED(rv)) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(usePrivateBrowsing);
+ }
+ }
+ }
+
+ // For chrome docshell, the mPrivateBrowsingId remains 0 even its
+ // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in
+ // origin attributes if the type of the docshell is content.
+ if (aLoadingContext) {
+ nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell();
+ if (docShell) {
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+ }
+ }
+}
+
+/* Constructor takes an outer window, but no loadingNode or loadingPrincipal.
+ * This constructor should only be used for TYPE_DOCUMENT loads, since they
+ * have a null loadingNode and loadingPrincipal.
+*/
+LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags)
+ : mLoadingPrincipal(nullptr)
+ , mTriggeringPrincipal(aTriggeringPrincipal)
+ , mPrincipalToInherit(nullptr)
+ , mSecurityFlags(aSecurityFlags)
+ , mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT)
+ , mTainting(LoadTainting::Basic)
+ , mUpgradeInsecureRequests(false)
+ , mVerifySignedContent(false)
+ , mEnforceSRI(false)
+ , mForceInheritPrincipalDropped(false)
+ , mInnerWindowID(0)
+ , mOuterWindowID(0)
+ , mParentOuterWindowID(0)
+ , mFrameOuterWindowID(0)
+ , mEnforceSecurity(false)
+ , mInitialSecurityCheckDone(false)
+ , mIsThirdPartyContext(false) // NB: TYPE_DOCUMENT implies not third-party.
+ , mForcePreflight(false)
+ , mIsPreflight(false)
+ , mForceHSTSPriming(false)
+ , mMixedContentWouldBlock(false)
+{
+ // Top-level loads are never third-party
+ // Grab the information we can out of the window.
+ MOZ_ASSERT(aOuterWindow);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
+ mSecurityFlags ^= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ mForceInheritPrincipalDropped = true;
+ }
+
+ // NB: Ignore the current inner window since we're navigating away from it.
+ mOuterWindowID = aOuterWindow->WindowID();
+
+ // TODO We can have a parent without a frame element in some cases dealing
+ // with the hidden window.
+ nsCOMPtr<nsPIDOMWindowOuter> parent = aOuterWindow->GetScriptableParent();
+ mParentOuterWindowID = parent ? parent->WindowID() : 0;
+
+ // get the docshell from the outerwindow, and then get the originattributes
+ nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell();
+ MOZ_ASSERT(docShell);
+ const DocShellOriginAttributes attrs =
+ nsDocShell::Cast(docShell)->GetOriginAttributes();
+
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ MOZ_ASSERT(attrs.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+
+ mOriginAttributes.InheritFromDocShellToNecko(attrs);
+}
+
+LoadInfo::LoadInfo(const LoadInfo& rhs)
+ : mLoadingPrincipal(rhs.mLoadingPrincipal)
+ , mTriggeringPrincipal(rhs.mTriggeringPrincipal)
+ , mPrincipalToInherit(rhs.mPrincipalToInherit)
+ , mLoadingContext(rhs.mLoadingContext)
+ , mSecurityFlags(rhs.mSecurityFlags)
+ , mInternalContentPolicyType(rhs.mInternalContentPolicyType)
+ , mTainting(rhs.mTainting)
+ , mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests)
+ , mVerifySignedContent(rhs.mVerifySignedContent)
+ , mEnforceSRI(rhs.mEnforceSRI)
+ , mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped)
+ , mInnerWindowID(rhs.mInnerWindowID)
+ , mOuterWindowID(rhs.mOuterWindowID)
+ , mParentOuterWindowID(rhs.mParentOuterWindowID)
+ , mFrameOuterWindowID(rhs.mFrameOuterWindowID)
+ , mEnforceSecurity(rhs.mEnforceSecurity)
+ , mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone)
+ , mIsThirdPartyContext(rhs.mIsThirdPartyContext)
+ , mOriginAttributes(rhs.mOriginAttributes)
+ , mRedirectChainIncludingInternalRedirects(
+ rhs.mRedirectChainIncludingInternalRedirects)
+ , mRedirectChain(rhs.mRedirectChain)
+ , mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders)
+ , mForcePreflight(rhs.mForcePreflight)
+ , mIsPreflight(rhs.mIsPreflight)
+ , mForceHSTSPriming(rhs.mForceHSTSPriming)
+ , mMixedContentWouldBlock(rhs.mMixedContentWouldBlock)
+{
+}
+
+LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting,
+ bool aUpgradeInsecureRequests,
+ bool aVerifySignedContent,
+ bool aEnforceSRI,
+ bool aForceInheritPrincipalDropped,
+ uint64_t aInnerWindowID,
+ uint64_t aOuterWindowID,
+ uint64_t aParentOuterWindowID,
+ uint64_t aFrameOuterWindowID,
+ bool aEnforceSecurity,
+ bool aInitialSecurityCheckDone,
+ bool aIsThirdPartyContext,
+ const NeckoOriginAttributes& aOriginAttributes,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChainIncludingInternalRedirects,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChain,
+ const nsTArray<nsCString>& aCorsUnsafeHeaders,
+ bool aForcePreflight,
+ bool aIsPreflight,
+ bool aForceHSTSPriming,
+ bool aMixedContentWouldBlock)
+ : mLoadingPrincipal(aLoadingPrincipal)
+ , mTriggeringPrincipal(aTriggeringPrincipal)
+ , mPrincipalToInherit(aPrincipalToInherit)
+ , mSecurityFlags(aSecurityFlags)
+ , mInternalContentPolicyType(aContentPolicyType)
+ , mTainting(aTainting)
+ , mUpgradeInsecureRequests(aUpgradeInsecureRequests)
+ , mVerifySignedContent(aVerifySignedContent)
+ , mEnforceSRI(aEnforceSRI)
+ , mForceInheritPrincipalDropped(aForceInheritPrincipalDropped)
+ , mInnerWindowID(aInnerWindowID)
+ , mOuterWindowID(aOuterWindowID)
+ , mParentOuterWindowID(aParentOuterWindowID)
+ , mFrameOuterWindowID(aFrameOuterWindowID)
+ , mEnforceSecurity(aEnforceSecurity)
+ , mInitialSecurityCheckDone(aInitialSecurityCheckDone)
+ , mIsThirdPartyContext(aIsThirdPartyContext)
+ , mOriginAttributes(aOriginAttributes)
+ , mCorsUnsafeHeaders(aCorsUnsafeHeaders)
+ , mForcePreflight(aForcePreflight)
+ , mIsPreflight(aIsPreflight)
+ , mForceHSTSPriming (aForceHSTSPriming)
+ , mMixedContentWouldBlock(aMixedContentWouldBlock)
+{
+ // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
+ MOZ_ASSERT(mLoadingPrincipal || aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+ mRedirectChainIncludingInternalRedirects.SwapElements(
+ aRedirectChainIncludingInternalRedirects);
+
+ mRedirectChain.SwapElements(aRedirectChain);
+}
+
+LoadInfo::~LoadInfo()
+{
+}
+
+void
+LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow)
+{
+ nsContentPolicyType type =
+ nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType);
+ if (type == nsIContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party.
+ mIsThirdPartyContext = false;
+ return;
+ }
+
+ nsCOMPtr<mozIThirdPartyUtil> util(do_GetService(THIRDPARTYUTIL_CONTRACTID));
+ if (NS_WARN_IF(!util)) {
+ return;
+ }
+
+ util->IsThirdPartyWindow(aOuterWindow, nullptr, &mIsThirdPartyContext);
+}
+
+NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo)
+
+already_AddRefed<nsILoadInfo>
+LoadInfo::Clone() const
+{
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ return copy.forget();
+}
+
+already_AddRefed<nsILoadInfo>
+LoadInfo::CloneWithNewSecFlags(nsSecurityFlags aSecurityFlags) const
+{
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ copy->mSecurityFlags = aSecurityFlags;
+ return copy.forget();
+}
+
+already_AddRefed<nsILoadInfo>
+LoadInfo::CloneForNewRequest() const
+{
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ copy->mEnforceSecurity = false;
+ copy->mInitialSecurityCheckDone = false;
+ copy->mRedirectChainIncludingInternalRedirects.Clear();
+ copy->mRedirectChain.Clear();
+ return copy.forget();
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal)
+{
+ NS_IF_ADDREF(*aLoadingPrincipal = mLoadingPrincipal);
+ return NS_OK;
+}
+
+nsIPrincipal*
+LoadInfo::LoadingPrincipal()
+{
+ return mLoadingPrincipal;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal)
+{
+ NS_ADDREF(*aTriggeringPrincipal = mTriggeringPrincipal);
+ return NS_OK;
+}
+
+nsIPrincipal*
+LoadInfo::TriggeringPrincipal()
+{
+ return mTriggeringPrincipal;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit)
+{
+ NS_IF_ADDREF(*aPrincipalToInherit = mPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit)
+{
+ MOZ_ASSERT(aPrincipalToInherit, "must be a valid principal to inherit");
+ mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+nsIPrincipal*
+LoadInfo::PrincipalToInherit()
+{
+ return mPrincipalToInherit;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingDocument(nsIDOMDocument** aResult)
+{
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ if (node) {
+ nsCOMPtr<nsIDOMDocument> context = do_QueryInterface(node->OwnerDoc());
+ context.forget(aResult);
+ }
+ return NS_OK;
+}
+
+nsINode*
+LoadInfo::LoadingNode()
+{
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ return node;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSecurityFlags(nsSecurityFlags* aResult)
+{
+ *aResult = mSecurityFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSecurityMode(uint32_t* aFlags)
+{
+ *aFlags = (mSecurityFlags &
+ (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED |
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL |
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext)
+{
+ *aIsInThirdPartyContext = mIsThirdPartyContext;
+ return NS_OK;
+}
+
+static const uint32_t sCookiePolicyMask =
+ nsILoadInfo::SEC_COOKIES_DEFAULT |
+ nsILoadInfo::SEC_COOKIES_INCLUDE |
+ nsILoadInfo::SEC_COOKIES_SAME_ORIGIN |
+ nsILoadInfo::SEC_COOKIES_OMIT;
+
+NS_IMETHODIMP
+LoadInfo::GetCookiePolicy(uint32_t *aResult)
+{
+ uint32_t policy = mSecurityFlags & sCookiePolicyMask;
+ if (policy == nsILoadInfo::SEC_COOKIES_DEFAULT) {
+ policy = (mSecurityFlags & SEC_REQUIRE_CORS_DATA_INHERITS) ?
+ nsILoadInfo::SEC_COOKIES_SAME_ORIGIN : nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+
+ *aResult = policy;
+ return NS_OK;
+}
+
+void
+LoadInfo::SetIncludeCookiesSecFlag()
+{
+ MOZ_ASSERT(!mEnforceSecurity,
+ "Request should not have been opened yet");
+ MOZ_ASSERT((mSecurityFlags & sCookiePolicyMask) ==
+ nsILoadInfo::SEC_COOKIES_DEFAULT);
+ mSecurityFlags = (mSecurityFlags & ~sCookiePolicyMask) |
+ nsILoadInfo::SEC_COOKIES_INCLUDE;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal)
+{
+ *aInheritPrincipal =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal)
+{
+ *aInheritPrincipal =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed)
+{
+ *aLoadingSandboxed = (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAboutBlankInherits(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_ABOUT_BLANK_INHERITS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowChrome(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_ALLOW_CHROME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetDisallowScript(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_DISALLOW_SCRIPT);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+LoadInfo::GetDontFollowRedirects(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadErrorPage(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_LOAD_ERROR_PAGE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult)
+{
+ *aResult = nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType);
+ return NS_OK;
+}
+
+nsContentPolicyType
+LoadInfo::InternalContentPolicyType()
+{
+ return mInternalContentPolicyType;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetUpgradeInsecureRequests(bool* aResult)
+{
+ *aResult = mUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetVerifySignedContent(bool aVerifySignedContent)
+{
+ MOZ_ASSERT(mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "can only verify content for TYPE_DOCUMENT");
+ mVerifySignedContent = aVerifySignedContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetVerifySignedContent(bool* aResult)
+{
+ *aResult = mVerifySignedContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetEnforceSRI(bool aEnforceSRI)
+{
+ mEnforceSRI = aEnforceSRI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetEnforceSRI(bool* aResult)
+{
+ *aResult = mEnforceSRI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipalDropped(bool* aResult)
+{
+ *aResult = mForceInheritPrincipalDropped;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInnerWindowID(uint64_t* aResult)
+{
+ *aResult = mInnerWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetOuterWindowID(uint64_t* aResult)
+{
+ *aResult = mOuterWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetParentOuterWindowID(uint64_t* aResult)
+{
+ *aResult = mParentOuterWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetFrameOuterWindowID(uint64_t* aResult)
+{
+ *aResult = mFrameOuterWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetScriptableOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOriginAttributes)
+{
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::ResetPrincipalsToNullPrincipal()
+{
+ // take the originAttributes from the LoadInfo and create
+ // a new NullPrincipal using those origin attributes.
+ PrincipalOriginAttributes pAttrs;
+ pAttrs.InheritFromNecko(mOriginAttributes);
+ nsCOMPtr<nsIPrincipal> newNullPrincipal = nsNullPrincipal::Create(pAttrs);
+
+ MOZ_ASSERT(mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT ||
+ !mLoadingPrincipal,
+ "LoadingPrincipal should be null for toplevel loads");
+
+ // the loadingPrincipal for toplevel loads is always a nullptr;
+ if (mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT) {
+ mLoadingPrincipal = newNullPrincipal;
+ }
+ mTriggeringPrincipal = newNullPrincipal;
+ mPrincipalToInherit = newNullPrincipal;
+
+ // setting SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER will overrule
+ // any non null owner set on the channel and will return the principal
+ // form the loadinfo instead.
+ mSecurityFlags |= SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetScriptableOriginAttributes(JSContext* aCx,
+ JS::Handle<JS::Value> aOriginAttributes)
+{
+ NeckoOriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOriginAttributes = attrs;
+ return NS_OK;
+}
+
+nsresult
+LoadInfo::GetOriginAttributes(mozilla::NeckoOriginAttributes* aOriginAttributes)
+{
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult
+LoadInfo::SetOriginAttributes(const mozilla::NeckoOriginAttributes& aOriginAttributes)
+{
+ mOriginAttributes = aOriginAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetEnforceSecurity(bool aEnforceSecurity)
+{
+ // Indicates whether the channel was openend using AsyncOpen2. Once set
+ // to true, it must remain true throughout the lifetime of the channel.
+ // Setting it to anything else than true will be discarded.
+ MOZ_ASSERT(aEnforceSecurity, "aEnforceSecurity must be true");
+ mEnforceSecurity = mEnforceSecurity || aEnforceSecurity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetEnforceSecurity(bool* aResult)
+{
+ *aResult = mEnforceSecurity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone)
+{
+ // Indicates whether the channel was ever evaluated by the
+ // ContentSecurityManager. Once set to true, this flag must
+ // remain true throughout the lifetime of the channel.
+ // Setting it to anything else than true will be discarded.
+ MOZ_ASSERT(aInitialSecurityCheckDone, "aInitialSecurityCheckDone must be true");
+ mInitialSecurityCheckDone = mInitialSecurityCheckDone || aInitialSecurityCheckDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInitialSecurityCheckDone(bool* aResult)
+{
+ *aResult = mInitialSecurityCheckDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::AppendRedirectedPrincipal(nsIPrincipal* aPrincipal, bool aIsInternalRedirect)
+{
+ NS_ENSURE_ARG(aPrincipal);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mRedirectChainIncludingInternalRedirects.AppendElement(aPrincipal);
+ if (!aIsInternalRedirect) {
+ mRedirectChain.AppendElement(aPrincipal);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirectChainIncludingInternalRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aChain)
+{
+ if (!ToJSValue(aCx, mRedirectChainIncludingInternalRedirects, aChain)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>&
+LoadInfo::RedirectChainIncludingInternalRedirects()
+{
+ return mRedirectChainIncludingInternalRedirects;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirectChain(JSContext* aCx, JS::MutableHandle<JS::Value> aChain)
+{
+ if (!ToJSValue(aCx, mRedirectChain, aChain)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>&
+LoadInfo::RedirectChain()
+{
+ return mRedirectChain;
+}
+
+void
+LoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders,
+ bool aForcePreflight)
+{
+ MOZ_ASSERT(GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS);
+ MOZ_ASSERT(!mInitialSecurityCheckDone);
+ mCorsUnsafeHeaders = aHeaders;
+ mForcePreflight = aForcePreflight;
+}
+
+const nsTArray<nsCString>&
+LoadInfo::CorsUnsafeHeaders()
+{
+ return mCorsUnsafeHeaders;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForcePreflight(bool* aForcePreflight)
+{
+ *aForcePreflight = mForcePreflight;
+ return NS_OK;
+}
+
+void
+LoadInfo::SetIsPreflight()
+{
+ MOZ_ASSERT(GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS);
+ MOZ_ASSERT(!mInitialSecurityCheckDone);
+ mIsPreflight = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsPreflight(bool* aIsPreflight)
+{
+ *aIsPreflight = mIsPreflight;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceHSTSPriming(bool* aForceHSTSPriming)
+{
+ *aForceHSTSPriming = mForceHSTSPriming;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetMixedContentWouldBlock(bool *aMixedContentWouldBlock)
+{
+ *aMixedContentWouldBlock = mMixedContentWouldBlock;
+ return NS_OK;
+}
+
+void
+LoadInfo::SetHSTSPriming(bool aMixedContentWouldBlock)
+{
+ mForceHSTSPriming = true;
+ mMixedContentWouldBlock = aMixedContentWouldBlock;
+}
+
+void
+LoadInfo::ClearHSTSPriming()
+{
+ mForceHSTSPriming = false;
+ mMixedContentWouldBlock = false;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTainting(uint32_t* aTaintingOut)
+{
+ MOZ_ASSERT(aTaintingOut);
+ *aTaintingOut = static_cast<uint32_t>(mTainting);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::MaybeIncreaseTainting(uint32_t aTainting)
+{
+ NS_ENSURE_ARG(aTainting <= TAINTING_OPAQUE);
+ LoadTainting tainting = static_cast<LoadTainting>(aTainting);
+ if (tainting > mTainting) {
+ mTainting = tainting;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsTopLevelLoad(bool *aResult)
+{
+ *aResult = mFrameOuterWindowID ? mFrameOuterWindowID == mOuterWindowID
+ : mParentOuterWindowID == mOuterWindowID;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
new file mode 100644
index 0000000000..261f85349d
--- /dev/null
+++ b/netwerk/base/LoadInfo.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadInfo_h
+#define mozilla_LoadInfo_h
+
+#include "nsIContentPolicy.h"
+#include "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+#include "nsIURI.h"
+#include "nsTArray.h"
+
+#include "mozilla/BasePrincipal.h"
+
+class nsINode;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+
+namespace dom {
+class XMLHttpRequestMainThread;
+}
+
+namespace net {
+class OptionalLoadInfoArgs;
+} // namespace net
+
+namespace ipc {
+// we have to forward declare that function so we can use it as a friend.
+nsresult
+LoadInfoArgsToLoadInfo(const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs,
+ nsILoadInfo** outLoadInfo);
+} // namespace ipc
+
+namespace net {
+
+/**
+ * Class that provides an nsILoadInfo implementation.
+ *
+ * Note that there is no reason why this class should be MOZ_EXPORT, but
+ * Thunderbird relies on some insane hacks which require this, so we'll leave it
+ * as is for now, but hopefully we'll be able to remove the MOZ_EXPORT keyword
+ * from this class at some point. See bug 1149127 for the discussion.
+ */
+class MOZ_EXPORT LoadInfo final : public nsILoadInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSILOADINFO
+
+ // aLoadingPrincipal MUST NOT BE NULL.
+ LoadInfo(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsINode* aLoadingContext,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType);
+
+ // Constructor used for TYPE_DOCUMENT loads which have no reasonable
+ // loadingNode or loadingPrincipal
+ LoadInfo(nsPIDOMWindowOuter* aOuterWindow,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags);
+
+ // create an exact copy of the loadinfo
+ already_AddRefed<nsILoadInfo> Clone() const;
+ // hands off!!! don't use CloneWithNewSecFlags unless you know
+ // exactly what you are doing - it should only be used within
+ // nsBaseChannel::Redirect()
+ already_AddRefed<nsILoadInfo>
+ CloneWithNewSecFlags(nsSecurityFlags aSecurityFlags) const;
+ // creates a copy of the loadinfo which is appropriate to use for a
+ // separate request. I.e. not for a redirect or an inner channel, but
+ // when a separate request is made with the same security properties.
+ already_AddRefed<nsILoadInfo> CloneForNewRequest() const;
+
+ void SetIsPreflight();
+
+private:
+ // private constructor that is only allowed to be called from within
+ // HttpChannelParent and FTPChannelParent declared as friends undeneath.
+ // In e10s we can not serialize nsINode, hence we store the innerWindowID.
+ // Please note that aRedirectChain uses swapElements.
+ LoadInfo(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting,
+ bool aUpgradeInsecureRequests,
+ bool aVerifySignedContent,
+ bool aEnforceSRI,
+ bool aForceInheritPrincipalDropped,
+ uint64_t aInnerWindowID,
+ uint64_t aOuterWindowID,
+ uint64_t aParentOuterWindowID,
+ uint64_t aFrameOuterWindowID,
+ bool aEnforceSecurity,
+ bool aInitialSecurityCheckDone,
+ bool aIsThirdPartyRequest,
+ const NeckoOriginAttributes& aOriginAttributes,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChainIncludingInternalRedirects,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChain,
+ const nsTArray<nsCString>& aUnsafeHeaders,
+ bool aForcePreflight,
+ bool aIsPreflight,
+ bool aForceHSTSPriming,
+ bool aMixedContentWouldBlock);
+ LoadInfo(const LoadInfo& rhs);
+
+ friend nsresult
+ mozilla::ipc::LoadInfoArgsToLoadInfo(
+ const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs,
+ nsILoadInfo** outLoadInfo);
+
+ ~LoadInfo();
+
+ void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow);
+
+ // This function is the *only* function which can change the securityflags
+ // of a loadinfo. It only exists because of the XHR code. Don't call it
+ // from anywhere else!
+ void SetIncludeCookiesSecFlag();
+ friend class mozilla::dom::XMLHttpRequestMainThread;
+
+ // if you add a member, please also update the copy constructor
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+ nsWeakPtr mLoadingContext;
+ nsSecurityFlags mSecurityFlags;
+ nsContentPolicyType mInternalContentPolicyType;
+ LoadTainting mTainting;
+ bool mUpgradeInsecureRequests;
+ bool mVerifySignedContent;
+ bool mEnforceSRI;
+ bool mForceInheritPrincipalDropped;
+ uint64_t mInnerWindowID;
+ uint64_t mOuterWindowID;
+ uint64_t mParentOuterWindowID;
+ uint64_t mFrameOuterWindowID;
+ bool mEnforceSecurity;
+ bool mInitialSecurityCheckDone;
+ bool mIsThirdPartyContext;
+ NeckoOriginAttributes mOriginAttributes;
+ nsTArray<nsCOMPtr<nsIPrincipal>> mRedirectChainIncludingInternalRedirects;
+ nsTArray<nsCOMPtr<nsIPrincipal>> mRedirectChain;
+ nsTArray<nsCString> mCorsUnsafeHeaders;
+ bool mForcePreflight;
+ bool mIsPreflight;
+
+ bool mForceHSTSPriming : 1;
+ bool mMixedContentWouldBlock : 1;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_LoadInfo_h
+
diff --git a/netwerk/base/LoadTainting.h b/netwerk/base/LoadTainting.h
new file mode 100644
index 0000000000..e2633969f7
--- /dev/null
+++ b/netwerk/base/LoadTainting.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadTainting_h
+#define mozilla_LoadTainting_h
+
+namespace mozilla {
+
+// Define an enumeration to reflect the concept of response tainting from the
+// the fetch spec:
+//
+// https://fetch.spec.whatwg.org/#concept-request-response-tainting
+//
+// Roughly the tainting means:
+//
+// * Basic: the request resulted in a same-origin or non-http load
+// * CORS: the request resulted in a cross-origin load with CORS headers
+// * Opaque: the request resulted in a cross-origin load without CORS headers
+//
+// The enumeration is purposefully designed such that more restrictive tainting
+// corresponds to a higher integral value.
+//
+// NOTE: Checking the tainting is not currently adequate. You *must* still
+// check the final URL and CORS mode on the channel.
+//
+// These values are currently only set on the channel LoadInfo when the request
+// was initiated through fetch() or when a service worker interception occurs.
+// In the future we should set the tainting value within necko so that it is
+// consistently applied. Once that is done consumers can replace checks against
+// the final URL and CORS mode with checks against tainting.
+enum class LoadTainting : uint8_t
+{
+ Basic = 0,
+ CORS = 1,
+ Opaque = 2
+};
+
+} // namespace mozilla
+
+#endif // mozilla_LoadTainting_h
diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp
new file mode 100644
index 0000000000..f8add31aac
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MemoryDownloader.h"
+
+#include "mozilla/Assertions.h"
+#include "nsIInputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(MemoryDownloader,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+MemoryDownloader::MemoryDownloader(IObserver* aObserver)
+: mObserver(aObserver)
+{
+}
+
+MemoryDownloader::~MemoryDownloader()
+{
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt)
+{
+ MOZ_ASSERT(!mData);
+ mData.reset(new FallibleTArray<uint8_t>());
+ mStatus = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ nsresult aStatus)
+{
+ MOZ_ASSERT_IF(NS_FAILED(mStatus), NS_FAILED(aStatus));
+ MOZ_ASSERT(!mData == NS_FAILED(mStatus));
+ Data data;
+ data.swap(mData);
+ RefPtr<IObserver> observer;
+ observer.swap(mObserver);
+ observer->OnDownloadComplete(this, aRequest, aCtxt, aStatus,
+ mozilla::Move(data));
+ return NS_OK;
+}
+
+nsresult
+MemoryDownloader::ConsumeData(nsIInputStream* aIn,
+ void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ MemoryDownloader* self = static_cast<MemoryDownloader*>(aClosure);
+ if (!self->mData->AppendElements(aFromRawSegment, aCount, fallible)) {
+ // The error returned by ConsumeData isn't propagated to the
+ // return of ReadSegments, so it has to be passed as state.
+ self->mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ nsIInputStream* aInStr,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ uint32_t n;
+ MOZ_ASSERT(mData);
+ nsresult rv = aInStr->ReadSegments(ConsumeData, this, aCount, &n);
+ if (NS_SUCCEEDED(mStatus) && NS_FAILED(rv)) {
+ mStatus = rv;
+ }
+ if (NS_WARN_IF(NS_FAILED(mStatus))) {
+ mData.reset(nullptr);
+ return mStatus;
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/MemoryDownloader.h b/netwerk/base/MemoryDownloader.h
new file mode 100644
index 0000000000..32fcff66ca
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_MemoryDownloader_h__
+#define mozilla_net_MemoryDownloader_h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsTArray.h"
+
+/**
+ * mozilla::net::MemoryDownloader
+ *
+ * This class is similar to nsIDownloader, but stores the downloaded
+ * stream in memory instead of a file. Ownership of the temporary
+ * memory is transferred to the observer when download is complete;
+ * there is no need to retain a reference to the downloader.
+ */
+
+namespace mozilla {
+namespace net {
+
+class MemoryDownloader final : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ typedef mozilla::UniquePtr<FallibleTArray<uint8_t>> Data;
+
+ class IObserver : public nsISupports {
+ public:
+ // Note: aData may be null if (and only if) aStatus indicates failure.
+ virtual void OnDownloadComplete(MemoryDownloader* aDownloader,
+ nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ nsresult aStatus,
+ Data aData) = 0;
+ };
+
+ explicit MemoryDownloader(IObserver* aObserver);
+
+private:
+ virtual ~MemoryDownloader();
+
+ static nsresult ConsumeData(nsIInputStream *in,
+ void *closure,
+ const char *fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount);
+
+ RefPtr<IObserver> mObserver;
+ Data mData;
+ nsresult mStatus;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_MemoryDownloader_h__
diff --git a/netwerk/base/NetStatistics.h b/netwerk/base/NetStatistics.h
new file mode 100644
index 0000000000..2613550836
--- /dev/null
+++ b/netwerk/base/NetStatistics.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetStatistics_h__
+#define NetStatistics_h__
+
+#include "mozilla/Assertions.h"
+
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsINetworkInterface.h"
+#include "nsINetworkManager.h"
+#include "nsINetworkStatsServiceProxy.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+// The following members are used for network per-app metering.
+const static uint64_t NETWORK_STATS_THRESHOLD = 65536;
+const static char NETWORK_STATS_NO_SERVICE_TYPE[] = "";
+
+inline nsresult
+GetActiveNetworkInfo(nsCOMPtr<nsINetworkInfo> &aNetworkInfo)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkManager> networkManager =
+ do_GetService("@mozilla.org/network/manager;1", &rv);
+
+ if (NS_FAILED(rv) || !networkManager) {
+ aNetworkInfo = nullptr;
+ return rv;
+ }
+
+ networkManager->GetActiveNetworkInfo(getter_AddRefs(aNetworkInfo));
+
+ return NS_OK;
+}
+
+class SaveNetworkStatsEvent : public Runnable {
+public:
+ SaveNetworkStatsEvent(uint32_t aAppId,
+ bool aIsInIsolatedMozBrowser,
+ nsMainThreadPtrHandle<nsINetworkInfo> &aActiveNetworkInfo,
+ uint64_t aCountRecv,
+ uint64_t aCountSent,
+ bool aIsAccumulative)
+ : mAppId(aAppId),
+ mIsInIsolatedMozBrowser(aIsInIsolatedMozBrowser),
+ mActiveNetworkInfo(aActiveNetworkInfo),
+ mCountRecv(aCountRecv),
+ mCountSent(aCountSent),
+ mIsAccumulative(aIsAccumulative)
+ {
+ MOZ_ASSERT(mAppId != NECKO_NO_APP_ID);
+ MOZ_ASSERT(mActiveNetworkInfo);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkStatsServiceProxy> mNetworkStatsServiceProxy =
+ do_GetService("@mozilla.org/networkstatsServiceProxy;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // save the network stats through NetworkStatsServiceProxy
+ mNetworkStatsServiceProxy->SaveAppStats(mAppId,
+ mIsInIsolatedMozBrowser,
+ mActiveNetworkInfo,
+ PR_Now() / 1000,
+ mCountRecv,
+ mCountSent,
+ mIsAccumulative,
+ nullptr);
+
+ return NS_OK;
+ }
+private:
+ uint32_t mAppId;
+ bool mIsInIsolatedMozBrowser;
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+ uint64_t mCountRecv;
+ uint64_t mCountSent;
+ bool mIsAccumulative;
+};
+
+} // namespace mozilla:net
+} // namespace mozilla
+
+#endif // !NetStatistics_h__
diff --git a/netwerk/base/NetUtil.jsm b/netwerk/base/NetUtil.jsm
new file mode 100644
index 0000000000..e970c8ad81
--- /dev/null
+++ b/netwerk/base/NetUtil.jsm
@@ -0,0 +1,461 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
+ * vim: sw=4 ts=4 sts=4 et filetype=javascript
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [
+ "NetUtil",
+];
+
+/**
+ * Necko utilities
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+const PR_UINT32_MAX = 0xffffffff;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// NetUtil Object
+
+this.NetUtil = {
+ /**
+ * Function to perform simple async copying from aSource (an input stream)
+ * to aSink (an output stream). The copy will happen on some background
+ * thread. Both streams will be closed when the copy completes.
+ *
+ * @param aSource
+ * The input stream to read from
+ * @param aSink
+ * The output stream to write to
+ * @param aCallback [optional]
+ * A function that will be called at copy completion with a single
+ * argument: the nsresult status code for the copy operation.
+ *
+ * @return An nsIRequest representing the copy operation (for example, this
+ * can be used to cancel the copying). The consumer can ignore the
+ * return value if desired.
+ */
+ asyncCopy: function NetUtil_asyncCopy(aSource, aSink,
+ aCallback = null)
+ {
+ if (!aSource || !aSink) {
+ let exception = new Components.Exception(
+ "Must have a source and a sink",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // make a stream copier
+ var copier = Cc["@mozilla.org/network/async-stream-copier;1"].
+ createInstance(Ci.nsIAsyncStreamCopier2);
+ copier.init(aSource, aSink,
+ null /* Default event target */,
+ 0 /* Default length */,
+ true, true /* Auto-close */);
+
+ var observer;
+ if (aCallback) {
+ observer = {
+ onStartRequest: function(aRequest, aContext) {},
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ aCallback(aStatusCode);
+ }
+ }
+ } else {
+ observer = null;
+ }
+
+ // start the copying
+ copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
+ return copier;
+ },
+
+ /**
+ * Asynchronously opens a source and fetches the response. While the fetch
+ * is asynchronous, I/O may happen on the main thread. When reading from
+ * a local file, prefer using "OS.File" methods instead.
+ *
+ * @param aSource
+ * This argument can be one of the following:
+ * - An options object that will be passed to NetUtil.newChannel.
+ * - An existing nsIChannel.
+ * - An existing nsIInputStream.
+ * Using an nsIURI, nsIFile, or string spec directly is deprecated.
+ * @param aCallback
+ * The callback function that will be notified upon completion. It
+ * will get these arguments:
+ * 1) An nsIInputStream containing the data from aSource, if any.
+ * 2) The status code from opening the source.
+ * 3) Reference to the nsIRequest.
+ */
+ asyncFetch: function NetUtil_asyncFetch(aSource, aCallback)
+ {
+ if (!aSource || !aCallback) {
+ let exception = new Components.Exception(
+ "Must have a source and a callback",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // Create a pipe that will create our output stream that we can use once
+ // we have gotten all the data.
+ let pipe = Cc["@mozilla.org/pipe;1"].
+ createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, PR_UINT32_MAX, null);
+
+ // Create a listener that will give data to the pipe's output stream.
+ let listener = Cc["@mozilla.org/network/simple-stream-listener;1"].
+ createInstance(Ci.nsISimpleStreamListener);
+ listener.init(pipe.outputStream, {
+ onStartRequest: function(aRequest, aContext) {},
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ pipe.outputStream.close();
+ aCallback(pipe.inputStream, aStatusCode, aRequest);
+ }
+ });
+
+ // Input streams are handled slightly differently from everything else.
+ if (aSource instanceof Ci.nsIInputStream) {
+ let pump = Cc["@mozilla.org/network/input-stream-pump;1"].
+ createInstance(Ci.nsIInputStreamPump);
+ pump.init(aSource, -1, -1, 0, 0, true);
+ pump.asyncRead(listener, null);
+ return;
+ }
+
+ let channel = aSource;
+ if (!(channel instanceof Ci.nsIChannel)) {
+ channel = this.newChannel(aSource);
+ }
+
+ try {
+ // Open the channel using asyncOpen2() if the loadinfo contains one
+ // of the security mode flags, otherwise fall back to use asyncOpen().
+ if (channel.loadInfo &&
+ channel.loadInfo.securityMode != 0) {
+ channel.asyncOpen2(listener);
+ }
+ else {
+ // Log deprecation warning to console to make sure all channels
+ // are created providing the correct security flags in the loadinfo.
+ // See nsILoadInfo for all available security flags and also the API
+ // of NetUtil.newChannel() for details above.
+ Cu.reportError("NetUtil.jsm: asyncFetch() requires the channel to have " +
+ "one of the security flags set in the loadinfo (see nsILoadInfo). " +
+ "Please create channel using NetUtil.newChannel()");
+ channel.asyncOpen(listener, null);
+ }
+ }
+ catch (e) {
+ let exception = new Components.Exception(
+ "Failed to open input source '" + channel.originalURI.spec + "'",
+ e.result,
+ Components.stack.caller,
+ aSource,
+ e
+ );
+ throw exception;
+ }
+ },
+
+ /**
+ * Constructs a new URI for the given spec, character set, and base URI, or
+ * an nsIFile.
+ *
+ * @param aTarget
+ * The string spec for the desired URI or an nsIFile.
+ * @param aOriginCharset [optional]
+ * The character set for the URI. Only used if aTarget is not an
+ * nsIFile.
+ * @param aBaseURI [optional]
+ * The base URI for the spec. Only used if aTarget is not an
+ * nsIFile.
+ *
+ * @return an nsIURI object.
+ */
+ newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI)
+ {
+ if (!aTarget) {
+ let exception = new Components.Exception(
+ "Must have a non-null string spec or nsIFile object",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (aTarget instanceof Ci.nsIFile) {
+ return this.ioService.newFileURI(aTarget);
+ }
+
+ return this.ioService.newURI(aTarget, aOriginCharset, aBaseURI);
+ },
+
+ /**
+ * Constructs a new channel for the given source.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ *
+ * @param aWhatToLoad
+ * This argument used to be a string spec for the desired URI, an
+ * nsIURI, or an nsIFile. Now it should be an options object with
+ * the following properties:
+ * {
+ * uri:
+ * The full URI spec string, nsIURI or nsIFile to create the
+ * channel for.
+ * Note that this cannot be an nsIFile if you have to specify a
+ * non-default charset or base URI. Call NetUtil.newURI first if
+ * you need to construct an URI using those options.
+ * loadingNode:
+ * loadingPrincipal:
+ * triggeringPrincipal:
+ * securityFlags:
+ * contentPolicyType:
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * loadUsingSystemPrincipal:
+ * Set this to true to use the system principal as
+ * loadingPrincipal. This must be omitted if loadingPrincipal or
+ * loadingNode are present.
+ * This should be used with care as it skips security checks.
+ * }
+ * @param aOriginCharset [deprecated]
+ * The character set for the URI. Only used if aWhatToLoad is a
+ * string, which is a deprecated API. Must be undefined otherwise.
+ * Use NetUtil.newURI if you need to use this option.
+ * @param aBaseURI [deprecated]
+ * The base URI for the spec. Only used if aWhatToLoad is a string,
+ * which is a deprecated API. Must be undefined otherwise. Use
+ * NetUtil.newURI if you need to use this option.
+ * @return an nsIChannel object.
+ */
+ newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, aBaseURI)
+ {
+ // Check for the deprecated API first.
+ if (typeof aWhatToLoad == "string" ||
+ (aWhatToLoad instanceof Ci.nsIFile) ||
+ (aWhatToLoad instanceof Ci.nsIURI)) {
+
+ let uri = (aWhatToLoad instanceof Ci.nsIURI)
+ ? aWhatToLoad
+ : this.newURI(aWhatToLoad, aOriginCharset, aBaseURI);
+
+ // log deprecation warning for developers.
+ Services.console.logStringMessage(
+ "Warning: NetUtil.newChannel(uri) deprecated, please provide argument 'aWhatToLoad'");
+
+ // Provide default loadinfo arguments and call the new API.
+ let systemPrincipal =
+ Services.scriptSecurityManager.getSystemPrincipal();
+
+ return this.ioService.newChannelFromURI2(
+ uri,
+ null, // loadingNode
+ systemPrincipal, // loadingPrincipal
+ null, // triggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ }
+
+ // We are using the updated API, that requires only the options object.
+ if (typeof aWhatToLoad != "object" ||
+ aOriginCharset !== undefined ||
+ aBaseURI !== undefined) {
+ throw new Components.Exception(
+ "newChannel requires a single object argument",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ let { uri,
+ loadingNode,
+ loadingPrincipal,
+ loadUsingSystemPrincipal,
+ triggeringPrincipal,
+ securityFlags,
+ contentPolicyType } = aWhatToLoad;
+
+ if (!uri) {
+ throw new Components.Exception(
+ "newChannel requires the 'uri' property on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
+ uri = this.newURI(uri);
+ }
+
+ if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires at least one of the 'loadingNode'," +
+ " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
+ " properties on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (loadUsingSystemPrincipal === true) {
+ if (loadingNode || loadingPrincipal) {
+ throw new Components.Exception(
+ "newChannel does not accept 'loadUsingSystemPrincipal'" +
+ " if the 'loadingNode' or 'loadingPrincipal' properties" +
+ " are present on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ loadingPrincipal = Services.scriptSecurityManager
+ .getSystemPrincipal();
+ } else if (loadUsingSystemPrincipal !== undefined) {
+ throw new Components.Exception(
+ "newChannel requires the 'loadUsingSystemPrincipal'" +
+ " property on the options object to be 'true' or 'undefined'.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (securityFlags === undefined) {
+ securityFlags = loadUsingSystemPrincipal
+ ? Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
+ : Ci.nsILoadInfo.SEC_NORMAL;
+ }
+
+ if (contentPolicyType === undefined) {
+ if (!loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires the 'contentPolicyType' property on" +
+ " the options object unless loading from system principal.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
+ }
+
+ return this.ioService.newChannelFromURI2(uri,
+ loadingNode || null,
+ loadingPrincipal || null,
+ triggeringPrincipal || null,
+ securityFlags,
+ contentPolicyType);
+ },
+
+ /**
+ * Reads aCount bytes from aInputStream into a string.
+ *
+ * @param aInputStream
+ * The input stream to read from.
+ * @param aCount
+ * The number of bytes to read from the stream.
+ * @param aOptions [optional]
+ * charset
+ * The character encoding of stream data.
+ * replacement
+ * The character to replace unknown byte sequences.
+ * If unset, it causes an exceptions to be thrown.
+ *
+ * @return the bytes from the input stream in string form.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
+ * block the calling thread (non-blocking mode only).
+ * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
+ * aCount amount of data.
+ * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
+ */
+ readInputStreamToString: function NetUtil_readInputStreamToString(aInputStream,
+ aCount,
+ aOptions)
+ {
+ if (!(aInputStream instanceof Ci.nsIInputStream)) {
+ let exception = new Components.Exception(
+ "First argument should be an nsIInputStream",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (!aCount) {
+ let exception = new Components.Exception(
+ "Non-zero amount of bytes must be specified",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (aOptions && "charset" in aOptions) {
+ let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ try {
+ // When replacement is set, the character that is unknown sequence
+ // replaces with aOptions.replacement character.
+ if (!("replacement" in aOptions)) {
+ // aOptions.replacement isn't set.
+ // If input stream has unknown sequences for aOptions.charset,
+ // throw NS_ERROR_ILLEGAL_INPUT.
+ aOptions.replacement = 0;
+ }
+
+ cis.init(aInputStream, aOptions.charset, aCount,
+ aOptions.replacement);
+ let str = {};
+ cis.readString(-1, str);
+ cis.close();
+ return str.value;
+ }
+ catch (e) {
+ // Adjust the stack so it throws at the caller's location.
+ throw new Components.Exception(e.message, e.result,
+ Components.stack.caller, e.data);
+ }
+ }
+
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(aInputStream);
+ try {
+ return sis.readBytes(aCount);
+ }
+ catch (e) {
+ // Adjust the stack so it throws at the caller's location.
+ throw new Components.Exception(e.message, e.result,
+ Components.stack.caller, e.data);
+ }
+ },
+
+ /**
+ * Returns a reference to nsIIOService.
+ *
+ * @return a reference to nsIIOService.
+ */
+ get ioService()
+ {
+ delete this.ioService;
+ return this.ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ },
+};
diff --git a/netwerk/base/NetworkActivityMonitor.cpp b/netwerk/base/NetworkActivityMonitor.cpp
new file mode 100644
index 0000000000..887878977f
--- /dev/null
+++ b/netwerk/base/NetworkActivityMonitor.cpp
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NetworkActivityMonitor.h"
+#include "prmem.h"
+#include "nsIObserverService.h"
+#include "nsPISocketTransportService.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+#include "prerror.h"
+
+using namespace mozilla::net;
+
+static PRStatus
+nsNetMon_Connect(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
+{
+ PRStatus ret;
+ PRErrorCode code;
+ ret = fd->lower->methods->connect(fd->lower, addr, timeout);
+ if (ret == PR_SUCCESS || (code = PR_GetError()) == PR_WOULD_BLOCK_ERROR ||
+ code == PR_IN_PROGRESS_ERROR)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Read(PRFileDesc *fd, void *buf, int32_t len)
+{
+ int32_t ret;
+ ret = fd->lower->methods->read(fd->lower, buf, len);
+ if (ret >= 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Write(PRFileDesc *fd, const void *buf, int32_t len)
+{
+ int32_t ret;
+ ret = fd->lower->methods->write(fd->lower, buf, len);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Writev(PRFileDesc *fd,
+ const PRIOVec *iov,
+ int32_t size,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->writev(fd->lower, iov, size, timeout);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Recv(PRFileDesc *fd,
+ void *buf,
+ int32_t amount,
+ int flags,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout);
+ if (ret >= 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Send(PRFileDesc *fd,
+ const void *buf,
+ int32_t amount,
+ int flags,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->send(fd->lower, buf, amount, flags, timeout);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_RecvFrom(PRFileDesc *fd,
+ void *buf,
+ int32_t amount,
+ int flags,
+ PRNetAddr *addr,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->recvfrom(fd->lower,
+ buf,
+ amount,
+ flags,
+ addr,
+ timeout);
+ if (ret >= 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_SendTo(PRFileDesc *fd,
+ const void *buf,
+ int32_t amount,
+ int flags,
+ const PRNetAddr *addr,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->sendto(fd->lower,
+ buf,
+ amount,
+ flags,
+ addr,
+ timeout);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_AcceptRead(PRFileDesc *listenSock,
+ PRFileDesc **acceptedSock,
+ PRNetAddr **peerAddr,
+ void *buf,
+ int32_t amount,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = listenSock->lower->methods->acceptread(listenSock->lower,
+ acceptedSock,
+ peerAddr,
+ buf,
+ amount,
+ timeout);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+ return ret;
+}
+
+
+class NotifyNetworkActivity : public mozilla::Runnable {
+public:
+ explicit NotifyNetworkActivity(NetworkActivityMonitor::Direction aDirection)
+ : mDirection(aDirection)
+ {}
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs)
+ return NS_ERROR_FAILURE;
+
+ obs->NotifyObservers(nullptr,
+ mDirection == NetworkActivityMonitor::kUpload
+ ? NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC
+ : NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC,
+ nullptr);
+ return NS_OK;
+ }
+private:
+ NetworkActivityMonitor::Direction mDirection;
+};
+
+NetworkActivityMonitor * NetworkActivityMonitor::gInstance = nullptr;
+static PRDescIdentity sNetActivityMonitorLayerIdentity;
+static PRIOMethods sNetActivityMonitorLayerMethods;
+static PRIOMethods *sNetActivityMonitorLayerMethodsPtr = nullptr;
+
+NetworkActivityMonitor::NetworkActivityMonitor()
+ : mBlipInterval(PR_INTERVAL_NO_TIMEOUT)
+{
+ MOZ_COUNT_CTOR(NetworkActivityMonitor);
+
+ NS_ASSERTION(gInstance==nullptr,
+ "multiple NetworkActivityMonitor instances!");
+}
+
+NetworkActivityMonitor::~NetworkActivityMonitor()
+{
+ MOZ_COUNT_DTOR(NetworkActivityMonitor);
+ gInstance = nullptr;
+}
+
+nsresult
+NetworkActivityMonitor::Init(int32_t blipInterval)
+{
+ nsresult rv;
+
+ if (gInstance)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ NetworkActivityMonitor * mon = new NetworkActivityMonitor();
+ rv = mon->Init_Internal(blipInterval);
+ if (NS_FAILED(rv)) {
+ delete mon;
+ return rv;
+ }
+
+ gInstance = mon;
+ return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::Shutdown()
+{
+ if (!gInstance)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ delete gInstance;
+ return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::Init_Internal(int32_t blipInterval)
+{
+ if (!sNetActivityMonitorLayerMethodsPtr) {
+ sNetActivityMonitorLayerIdentity =
+ PR_GetUniqueIdentity("network activity monitor layer");
+ sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods();
+ sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect;
+ sNetActivityMonitorLayerMethods.read = nsNetMon_Read;
+ sNetActivityMonitorLayerMethods.write = nsNetMon_Write;
+ sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev;
+ sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv;
+ sNetActivityMonitorLayerMethods.send = nsNetMon_Send;
+ sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom;
+ sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo;
+ sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead;
+ sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods;
+ }
+
+ mBlipInterval = PR_MillisecondsToInterval(blipInterval);
+ // Set the last notification times to time that has just expired, so any
+ // activity even right now will trigger notification.
+ mLastNotificationTime[kUpload] = PR_IntervalNow() - mBlipInterval;
+ mLastNotificationTime[kDownload] = mLastNotificationTime[kUpload];
+
+ return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::AttachIOLayer(PRFileDesc *fd)
+{
+ if (!gInstance)
+ return NS_OK;
+
+ PRFileDesc * layer;
+ PRStatus status;
+
+ layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
+ sNetActivityMonitorLayerMethodsPtr);
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_DELETE(layer);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::DataInOut(Direction direction)
+{
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (gInstance) {
+ PRIntervalTime now = PR_IntervalNow();
+ if ((now - gInstance->mLastNotificationTime[direction]) >
+ gInstance->mBlipInterval) {
+ gInstance->mLastNotificationTime[direction] = now;
+ gInstance->PostNotification(direction);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+NetworkActivityMonitor::PostNotification(Direction direction)
+{
+ nsCOMPtr<nsIRunnable> ev = new NotifyNetworkActivity(direction);
+ NS_DispatchToMainThread(ev);
+}
diff --git a/netwerk/base/NetworkActivityMonitor.h b/netwerk/base/NetworkActivityMonitor.h
new file mode 100644
index 0000000000..28c9a911e3
--- /dev/null
+++ b/netwerk/base/NetworkActivityMonitor.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkActivityMonitor_h___
+#define NetworkActivityMonitor_h___
+
+#include <stdint.h>
+#include "nscore.h"
+#include "prio.h"
+#include "prinrval.h"
+
+namespace mozilla { namespace net {
+
+class NetworkActivityMonitor
+{
+public:
+ enum Direction {
+ kUpload = 0,
+ kDownload = 1
+ };
+
+ NetworkActivityMonitor();
+ ~NetworkActivityMonitor();
+
+ static nsresult Init(int32_t blipInterval);
+ static nsresult Shutdown();
+
+ static nsresult AttachIOLayer(PRFileDesc *fd);
+ static nsresult DataInOut(Direction direction);
+
+private:
+ nsresult Init_Internal(int32_t blipInterval);
+ void PostNotification(Direction direction);
+
+ static NetworkActivityMonitor * gInstance;
+ PRIntervalTime mBlipInterval;
+ PRIntervalTime mLastNotificationTime[2];
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NetworkActivityMonitor_h___ */
diff --git a/netwerk/base/NetworkInfoServiceCocoa.cpp b/netwerk/base/NetworkInfoServiceCocoa.cpp
new file mode 100644
index 0000000000..937c726583
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceCocoa.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <netdb.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aIface, AddrMapType& aAddrMap);
+
+nsresult
+DoListAddresses(AddrMapType& aAddrMap)
+{
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto autoCloseSocket = MakeScopeExit([&] {
+ close(fd);
+ });
+
+ struct ifconf ifconf;
+ /* 16k of space should be enough to list all interfaces. Worst case, if it's
+ * not then we will error out and fail to list addresses. This should only
+ * happen on pathological machines with way too many interfaces.
+ */
+ char buf[16384];
+
+ ifconf.ifc_len = sizeof(buf);
+ ifconf.ifc_buf = buf;
+ if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct ifreq* ifreq = ifconf.ifc_req;
+ int i = 0;
+ while (i < ifconf.ifc_len) {
+ size_t len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
+
+ DebugOnly<nsresult> rv =
+ ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed");
+
+ ifreq = (struct ifreq*) ((char*)ifreq + len);
+ i += len;
+ }
+
+ autoCloseSocket.release();
+ return NS_OK;
+}
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aInterface, AddrMapType& aAddrMap)
+{
+ struct ifreq ifreq;
+ memset(&ifreq, 0, sizeof(struct ifreq));
+ strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1);
+ if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char host[128];
+ int family;
+ switch(family=ifreq.ifr_addr.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), 0, 0, NI_NUMERICHOST);
+ break;
+ case AF_UNSPEC:
+ return NS_OK;
+ default:
+ // Unknown family.
+ return NS_OK;
+ }
+
+ nsCString ifaceStr;
+ ifaceStr.AssignASCII(aInterface);
+
+ nsCString addrStr;
+ addrStr.AssignASCII(host);
+
+ aAddrMap.Put(ifaceStr, addrStr);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkInfoServiceImpl.h b/netwerk/base/NetworkInfoServiceImpl.h
new file mode 100644
index 0000000000..6f92c335f6
--- /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..96627cfecd
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceLinux.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <netdb.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aIface, AddrMapType& aAddrMap);
+
+nsresult
+DoListAddresses(AddrMapType& aAddrMap)
+{
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto autoCloseSocket = MakeScopeExit([&] {
+ close(fd);
+ });
+
+ struct ifconf ifconf;
+ /* 16k of space should be enough to list all interfaces. Worst case, if it's
+ * not then we will error out and fail to list addresses. This should only
+ * happen on pathological machines with way too many interfaces.
+ */
+ char buf[16384];
+
+ ifconf.ifc_len = sizeof(buf);
+ ifconf.ifc_buf = buf;
+ if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct ifreq* ifreq = ifconf.ifc_req;
+ int i = 0;
+ while (i < ifconf.ifc_len) {
+ size_t len = sizeof(struct ifreq);
+
+ DebugOnly<nsresult> rv =
+ ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed");
+
+ ifreq = (struct ifreq*) ((char*)ifreq + len);
+ i += len;
+ }
+
+ autoCloseSocket.release();
+ return NS_OK;
+}
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aInterface, AddrMapType& aAddrMap)
+{
+ struct ifreq ifreq;
+ memset(&ifreq, 0, sizeof(struct ifreq));
+ strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1);
+ if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char host[128];
+ int family;
+ switch(family=ifreq.ifr_addr.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), 0, 0, NI_NUMERICHOST);
+ break;
+ case AF_UNSPEC:
+ return NS_OK;
+ default:
+ // Unknown family.
+ return NS_OK;
+ }
+
+ nsCString ifaceStr;
+ ifaceStr.AssignASCII(aInterface);
+
+ nsCString addrStr;
+ addrStr.AssignASCII(host);
+
+ aAddrMap.Put(ifaceStr, addrStr);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkInfoServiceWindows.cpp b/netwerk/base/NetworkInfoServiceWindows.cpp
new file mode 100644
index 0000000000..2a9448e351
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceWindows.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <winsock2.h>
+#include <ws2ipdef.h>
+#include <iphlpapi.h>
+
+#include "mozilla/UniquePtr.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+nsresult
+DoListAddresses(AddrMapType& aAddrMap)
+{
+ UniquePtr<MIB_IPADDRTABLE> ipAddrTable;
+ DWORD size = sizeof(MIB_IPADDRTABLE);
+
+ ipAddrTable.reset((MIB_IPADDRTABLE*) malloc(size));
+ if (!ipAddrTable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD retVal = GetIpAddrTable(ipAddrTable.get(), &size, 0);
+ if (retVal == ERROR_INSUFFICIENT_BUFFER) {
+ ipAddrTable.reset((MIB_IPADDRTABLE*) malloc(size));
+ if (!ipAddrTable) {
+ return NS_ERROR_FAILURE;
+ }
+ retVal = GetIpAddrTable(ipAddrTable.get(), &size, 0);
+ }
+ if (retVal != NO_ERROR) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (DWORD i = 0; i < ipAddrTable->dwNumEntries; i++) {
+ int index = ipAddrTable->table[i].dwIndex;
+ uint32_t addrVal = (uint32_t) ipAddrTable->table[i].dwAddr;
+
+ nsCString indexString;
+ indexString.AppendInt(index, 10);
+
+ nsCString addrString;
+ addrString.AppendPrintf("%d.%d.%d.%d",
+ (addrVal >> 0) & 0xff, (addrVal >> 8) & 0xff,
+ (addrVal >> 16) & 0xff, (addrVal >> 24) & 0xff);
+
+ aAddrMap.Put(indexString, addrString);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp
new file mode 100644
index 0000000000..9cb45efded
--- /dev/null
+++ b/netwerk/base/PollableEvent.cpp
@@ -0,0 +1,347 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+#include "PollableEvent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "prerror.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include "prnetdb.h"
+
+#ifdef XP_WIN
+#include "ShutdownLayer.h"
+#else
+#include <fcntl.h>
+#define USEPIPE 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifndef USEPIPE
+static PRDescIdentity sPollableEventLayerIdentity;
+static PRIOMethods sPollableEventLayerMethods;
+static PRIOMethods *sPollableEventLayerMethodsPtr = nullptr;
+
+static void LazyInitSocket()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (sPollableEventLayerMethodsPtr) {
+ return;
+ }
+ sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer");
+ sPollableEventLayerMethods = *PR_GetDefaultIOMethods();
+ sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods;
+}
+
+static bool NewTCPSocketPair(PRFileDesc *fd[], bool aSetRecvBuff)
+{
+ // this is a replacement for PR_NewTCPSocketPair that manually
+ // sets the recv buffer to 64K. A windows bug (1248358)
+ // can result in using an incompatible rwin and window
+ // scale option on localhost pipes if not set before connect.
+
+ SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n", aSetRecvBuff ? "with" : "without"));
+
+ PRFileDesc *listener = nullptr;
+ PRFileDesc *writer = nullptr;
+ PRFileDesc *reader = nullptr;
+ PRSocketOptionData recvBufferOpt;
+ recvBufferOpt.option = PR_SockOpt_RecvBufferSize;
+ recvBufferOpt.value.recv_buffer_size = 65535;
+
+ PRSocketOptionData nodelayOpt;
+ nodelayOpt.option = PR_SockOpt_NoDelay;
+ nodelayOpt.value.no_delay = true;
+
+ PRSocketOptionData noblockOpt;
+ noblockOpt.option = PR_SockOpt_Nonblocking;
+ noblockOpt.value.non_blocking = true;
+
+ listener = PR_OpenTCPSocket(PR_AF_INET);
+ if (!listener) {
+ goto failed;
+ }
+
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(listener, &recvBufferOpt);
+ }
+ PR_SetSocketOption(listener, &nodelayOpt);
+
+ PRNetAddr listenAddr;
+ memset(&listenAddr, 0, sizeof(listenAddr));
+ if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) ||
+ (PR_Bind(listener, &listenAddr) == PR_FAILURE) ||
+ (PR_GetSockName(listener, &listenAddr) == PR_FAILURE) || // learn the dynamic port
+ (PR_Listen(listener, 5) == PR_FAILURE)) {
+ goto failed;
+ }
+
+ writer = PR_OpenTCPSocket(PR_AF_INET);
+ if (!writer) {
+ goto failed;
+ }
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(writer, &recvBufferOpt);
+ }
+ PR_SetSocketOption(writer, &nodelayOpt);
+ PR_SetSocketOption(writer, &noblockOpt);
+ PRNetAddr writerAddr;
+ if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port), &writerAddr) == PR_FAILURE) {
+ goto failed;
+ }
+
+ if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) {
+ if ((PR_GetError() != PR_IN_PROGRESS_ERROR) ||
+ (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) {
+ goto failed;
+ }
+ }
+
+ reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200));
+ if (!reader) {
+ goto failed;
+ }
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(reader, &recvBufferOpt);
+ }
+ PR_SetSocketOption(reader, &nodelayOpt);
+ PR_SetSocketOption(reader, &noblockOpt);
+ PR_Close(listener);
+
+ fd[0] = reader;
+ fd[1] = writer;
+ return true;
+
+failed:
+ if (listener) {
+ PR_Close(listener);
+ }
+ if (reader) {
+ PR_Close(reader);
+ }
+ if (writer) {
+ PR_Close(writer);
+ }
+ return false;
+}
+
+#endif
+
+PollableEvent::PollableEvent()
+ : mWriteFD(nullptr)
+ , mReadFD(nullptr)
+ , mSignaled(false)
+{
+ MOZ_COUNT_CTOR(PollableEvent);
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ // create pair of prfiledesc that can be used as a poll()ble
+ // signal. on windows use a localhost socket pair, and on
+ // unix use a pipe.
+#ifdef USEPIPE
+ SOCKET_LOG(("PollableEvent() using pipe\n"));
+ if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) {
+ // make the pipe non blocking. NSPR asserts at
+ // trying to use SockOpt here
+ PROsfd fd = PR_FileDesc2NativeHandle(mReadFD);
+ int flags = fcntl(fd, F_GETFL, 0);
+ (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ fd = PR_FileDesc2NativeHandle(mWriteFD);
+ flags = fcntl(fd, F_GETFL, 0);
+ (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ } else {
+ mReadFD = nullptr;
+ mWriteFD = nullptr;
+ SOCKET_LOG(("PollableEvent() pipe failed\n"));
+ }
+#else
+ SOCKET_LOG(("PollableEvent() using socket pair\n"));
+ PRFileDesc *fd[2];
+ LazyInitSocket();
+
+ // Try with a increased recv buffer first (bug 1248358).
+ if (NewTCPSocketPair(fd, true)) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+ // If the previous fails try without recv buffer increase (bug 1305436).
+ } else if (NewTCPSocketPair(fd, false)) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+ // If both fail, try the old version.
+ } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+
+ PRSocketOptionData socket_opt;
+ DebugOnly<PRStatus> status;
+ socket_opt.option = PR_SockOpt_NoDelay;
+ socket_opt.value.no_delay = true;
+ PR_SetSocketOption(mWriteFD, &socket_opt);
+ PR_SetSocketOption(mReadFD, &socket_opt);
+ socket_opt.option = PR_SockOpt_Nonblocking;
+ socket_opt.value.non_blocking = true;
+ status = PR_SetSocketOption(mWriteFD, &socket_opt);
+ MOZ_ASSERT(status == PR_SUCCESS);
+ status = PR_SetSocketOption(mReadFD, &socket_opt);
+ MOZ_ASSERT(status == PR_SUCCESS);
+ }
+
+ if (mReadFD && mWriteFD) {
+ // compatibility with LSPs such as McAfee that assume a NSPR
+ // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a nop.
+ PRFileDesc *topLayer =
+ PR_CreateIOLayerStub(sPollableEventLayerIdentity,
+ sPollableEventLayerMethodsPtr);
+ if (topLayer) {
+ if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) {
+ topLayer->dtor(topLayer);
+ } else {
+ SOCKET_LOG(("PollableEvent() nspr layer ok\n"));
+ mReadFD = topLayer;
+ }
+ }
+
+ } else {
+ SOCKET_LOG(("PollableEvent() socketpair failed\n"));
+ }
+#endif
+
+ if (mReadFD && mWriteFD) {
+ // prime the system to deal with races invovled in [dc]tor cycle
+ SOCKET_LOG(("PollableEvent() ctor ok\n"));
+ mSignaled = true;
+ PR_Write(mWriteFD, "I", 1);
+ }
+}
+
+PollableEvent::~PollableEvent()
+{
+ MOZ_COUNT_DTOR(PollableEvent);
+ if (mWriteFD) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(mWriteFD);
+#endif
+ PR_Close(mWriteFD);
+ }
+ if (mReadFD) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(mReadFD);
+#endif
+ PR_Close(mReadFD);
+ }
+}
+
+// we do not record signals on the socket thread
+// because the socket thread can reliably look at its
+// own runnable queue before selecting a poll time
+// this is the "service the network without blocking" comment in
+// nsSocketTransportService2.cpp
+bool
+PollableEvent::Signal()
+{
+ SOCKET_LOG(("PollableEvent::Signal\n"));
+
+ if (!mWriteFD) {
+ SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
+ return false;
+ }
+#ifndef XP_WIN
+ // On windows poll can hang and this became worse when we introduced the
+ // patch for bug 698882 (see also bug 1292181), therefore we reverted the
+ // behavior on windows to be as before bug 698882, e.g. write to the socket
+ // also if an event dispatch is on the socket thread and writing to the
+ // socket for each event. See bug 1292181.
+ if (PR_GetCurrentThread() == gSocketThread) {
+ SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n"));
+ return true;
+ }
+#endif
+
+#ifndef XP_WIN
+ // To wake up the poll writing once is enough, but for Windows that can cause
+ // hangs so we will write for every event.
+ // For non-Windows systems it is enough to write just once.
+ if (mSignaled) {
+ return true;
+ }
+#endif
+
+ mSignaled = true;
+ int32_t status = PR_Write(mWriteFD, "M", 1);
+ SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status));
+ if (status != 1) {
+ NS_WARNING("PollableEvent::Signal Failed\n");
+ SOCKET_LOG(("PollableEvent::Signal Failed\n"));
+ mSignaled = false;
+ }
+ return (status == 1);
+}
+
+bool
+PollableEvent::Clear()
+{
+ // necessary because of the "dont signal on socket thread" optimization
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ SOCKET_LOG(("PollableEvent::Clear\n"));
+ mSignaled = false;
+ if (!mReadFD) {
+ SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
+ return false;
+ }
+ char buf[2048];
+ int32_t status;
+#ifdef XP_WIN
+ // On Windows we are writing to the socket for each event, to be sure that we
+ // do not have any deadlock read from the socket as much as we can.
+ while (true) {
+ status = PR_Read(mReadFD, buf, 2048);
+ SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status));
+ if (status == 0) {
+ SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+ return false;
+ }
+ if (status < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ return true;
+ } else {
+ SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+ return false;
+ }
+ }
+ }
+#else
+ status = PR_Read(mReadFD, buf, 2048);
+ SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status));
+
+ if (status == 1) {
+ return true;
+ }
+ if (status == 0) {
+ SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+ return false;
+ }
+ if (status > 1) {
+ MOZ_ASSERT(false);
+ SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
+ Clear();
+ return true;
+ }
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ return true;
+ }
+ SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+ return false;
+#endif //XP_WIN
+
+}
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PollableEvent.h b/netwerk/base/PollableEvent.h
new file mode 100644
index 0000000000..8000799320
--- /dev/null
+++ b/netwerk/base/PollableEvent.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PollableEvent_h__
+#define PollableEvent_h__
+
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+// class must be called locked
+class PollableEvent
+{
+public:
+ PollableEvent();
+ ~PollableEvent();
+
+ // Signal/Clear return false only if they fail
+ bool Signal();
+ bool Clear();
+ bool Valid() { return mWriteFD && mReadFD; }
+
+ PRFileDesc *PollableFD() { return mReadFD; }
+
+private:
+ PRFileDesc *mWriteFD;
+ PRFileDesc *mReadFD;
+ bool mSignaled;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp
new file mode 100644
index 0000000000..e97b11d162
--- /dev/null
+++ b/netwerk/base/Predictor.cpp
@@ -0,0 +1,2550 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "Predictor.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsICacheStorage.h"
+#include "nsICacheStorageService.h"
+#include "nsICachingChannel.h"
+#include "nsICancelable.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsIDNSService.h"
+#include "nsIDocument.h"
+#include "nsIFile.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsIIOService.h"
+#include "nsILoadContext.h"
+#include "nsILoadContextInfo.h"
+#include "nsILoadGroup.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsISpeculativeConnect.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+
+#include "LoadContextInfo.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/net/NeckoChild.h"
+
+#include "mozilla/dom/ContentParent.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace net {
+
+Predictor *Predictor::sSelf = nullptr;
+
+static LazyLogModule gPredictorLog("NetworkPredictor");
+
+#define PREDICTOR_LOG(args) MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
+
+#define RETURN_IF_FAILED(_rv) \
+ do { \
+ if (NS_FAILED(_rv)) { \
+ return; \
+ } \
+ } while (0)
+
+#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
+
+
+static const char PREDICTOR_ENABLED_PREF[] = "network.predictor.enabled";
+static const char PREDICTOR_SSL_HOVER_PREF[] = "network.predictor.enable-hover-on-ssl";
+static const char PREDICTOR_PREFETCH_PREF[] = "network.predictor.enable-prefetch";
+
+static const char PREDICTOR_PAGE_DELTA_DAY_PREF[] =
+ "network.predictor.page-degradation.day";
+static const int32_t PREDICTOR_PAGE_DELTA_DAY_DEFAULT = 0;
+static const char PREDICTOR_PAGE_DELTA_WEEK_PREF[] =
+ "network.predictor.page-degradation.week";
+static const int32_t PREDICTOR_PAGE_DELTA_WEEK_DEFAULT = 5;
+static const char PREDICTOR_PAGE_DELTA_MONTH_PREF[] =
+ "network.predictor.page-degradation.month";
+static const int32_t PREDICTOR_PAGE_DELTA_MONTH_DEFAULT = 10;
+static const char PREDICTOR_PAGE_DELTA_YEAR_PREF[] =
+ "network.predictor.page-degradation.year";
+static const int32_t PREDICTOR_PAGE_DELTA_YEAR_DEFAULT = 25;
+static const char PREDICTOR_PAGE_DELTA_MAX_PREF[] =
+ "network.predictor.page-degradation.max";
+static const int32_t PREDICTOR_PAGE_DELTA_MAX_DEFAULT = 50;
+static const char PREDICTOR_SUB_DELTA_DAY_PREF[] =
+ "network.predictor.subresource-degradation.day";
+static const int32_t PREDICTOR_SUB_DELTA_DAY_DEFAULT = 1;
+static const char PREDICTOR_SUB_DELTA_WEEK_PREF[] =
+ "network.predictor.subresource-degradation.week";
+static const int32_t PREDICTOR_SUB_DELTA_WEEK_DEFAULT = 10;
+static const char PREDICTOR_SUB_DELTA_MONTH_PREF[] =
+ "network.predictor.subresource-degradation.month";
+static const int32_t PREDICTOR_SUB_DELTA_MONTH_DEFAULT = 25;
+static const char PREDICTOR_SUB_DELTA_YEAR_PREF[] =
+ "network.predictor.subresource-degradation.year";
+static const int32_t PREDICTOR_SUB_DELTA_YEAR_DEFAULT = 50;
+static const char PREDICTOR_SUB_DELTA_MAX_PREF[] =
+ "network.predictor.subresource-degradation.max";
+static const int32_t PREDICTOR_SUB_DELTA_MAX_DEFAULT = 100;
+
+static const char PREDICTOR_PREFETCH_ROLLING_LOAD_PREF[] =
+ "network.predictor.prefetch-rolling-load-count";
+static const int32_t PREFETCH_ROLLING_LOAD_DEFAULT = 10;
+static const char PREDICTOR_PREFETCH_MIN_PREF[] =
+ "network.predictor.prefetch-min-confidence";
+static const int32_t PREFETCH_MIN_DEFAULT = 100;
+static const char PREDICTOR_PRECONNECT_MIN_PREF[] =
+ "network.predictor.preconnect-min-confidence";
+static const int32_t PRECONNECT_MIN_DEFAULT = 90;
+static const char PREDICTOR_PRERESOLVE_MIN_PREF[] =
+ "network.predictor.preresolve-min-confidence";
+static const int32_t PRERESOLVE_MIN_DEFAULT = 60;
+static const char PREDICTOR_REDIRECT_LIKELY_PREF[] =
+ "network.predictor.redirect-likely-confidence";
+static const int32_t REDIRECT_LIKELY_DEFAULT = 75;
+
+static const char PREDICTOR_PREFETCH_FORCE_VALID_PREF[] =
+ "network.predictor.prefetch-force-valid-for";
+static const int32_t PREFETCH_FORCE_VALID_DEFAULT = 10;
+
+static const char PREDICTOR_MAX_RESOURCES_PREF[] =
+ "network.predictor.max-resources-per-entry";
+static const uint32_t PREDICTOR_MAX_RESOURCES_DEFAULT = 100;
+
+// This is selected in concert with max-resources-per-entry to keep memory usage
+// low-ish. The default of the combo of the two is ~50k
+static const char PREDICTOR_MAX_URI_LENGTH_PREF[] =
+ "network.predictor.max-uri-length";
+static const uint32_t PREDICTOR_MAX_URI_LENGTH_DEFAULT = 500;
+
+static const char PREDICTOR_DOING_TESTS_PREF[] = "network.predictor.doing-tests";
+
+static const char PREDICTOR_CLEANED_UP_PREF[] = "network.predictor.cleaned-up";
+
+// All these time values are in sec
+static const uint32_t ONE_DAY = 86400U;
+static const uint32_t ONE_WEEK = 7U * ONE_DAY;
+static const uint32_t ONE_MONTH = 30U * ONE_DAY;
+static const uint32_t ONE_YEAR = 365U * ONE_DAY;
+
+static const uint32_t STARTUP_WINDOW = 5U * 60U; // 5min
+
+// Version of metadata entries we expect
+static const uint32_t METADATA_VERSION = 1;
+
+// Flags available in entries
+// FLAG_PREFETCHABLE - we have determined that this item is eligible for prefetch
+static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
+
+// We save 12 bits in the "flags" section of our metadata for actual flags, the
+// rest are to keep track of a rolling count of which loads a resource has been
+// used on to determine if we can prefetch that resource or not;
+static const uint8_t kRollingLoadOffset = 12;
+static const int32_t kMaxPrefetchRollingLoadCount = 20;
+static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
+
+// ID Extensions for cache entries
+#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
+
+// Get the full origin (scheme, host, port) out of a URI (maybe should be part
+// of nsIURI instead?)
+static nsresult
+ExtractOrigin(nsIURI *uri, nsIURI **originUri, nsIIOService *ioService)
+{
+ nsAutoCString s;
+ s.Truncate();
+ nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewURI(originUri, s, nullptr, nullptr, ioService);
+}
+
+// All URIs we get passed *must* be http or https if they're not null. This
+// helps ensure that.
+static bool
+IsNullOrHttp(nsIURI *uri)
+{
+ if (!uri) {
+ return true;
+ }
+
+ bool isHTTP = false;
+ uri->SchemeIs("http", &isHTTP);
+ if (!isHTTP) {
+ uri->SchemeIs("https", &isHTTP);
+ }
+
+ return isHTTP;
+}
+
+// Listener for the speculative DNS requests we'll fire off, which just ignores
+// the result (since we're just trying to warm the cache). This also exists to
+// reduce round-trips to the main thread, by being something threadsafe the
+// Predictor can use.
+
+NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener);
+
+NS_IMETHODIMP
+Predictor::DNSListener::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ return NS_OK;
+}
+
+// Class to proxy important information from the initial predictor call through
+// the cache API and back into the internals of the predictor. We can't use the
+// predictor itself, as it may have multiple actions in-flight, and each action
+// has different parameters.
+NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
+
+Predictor::Action::Action(bool fullUri, bool predict,
+ Predictor::Reason reason,
+ nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier,
+ Predictor *predictor)
+ :mFullUri(fullUri)
+ ,mPredict(predict)
+ ,mTargetURI(targetURI)
+ ,mSourceURI(sourceURI)
+ ,mVerifier(verifier)
+ ,mStackCount(0)
+ ,mPredictor(predictor)
+{
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+Predictor::Action::Action(bool fullUri, bool predict,
+ Predictor::Reason reason,
+ nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier,
+ Predictor *predictor, uint8_t stackCount)
+ :mFullUri(fullUri)
+ ,mPredict(predict)
+ ,mTargetURI(targetURI)
+ ,mSourceURI(sourceURI)
+ ,mVerifier(verifier)
+ ,mStackCount(stackCount)
+ ,mPredictor(predictor)
+{
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+Predictor::Action::~Action()
+{ }
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryCheck(nsICacheEntry *entry,
+ nsIApplicationCache *appCache,
+ uint32_t *result)
+{
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew,
+ nsIApplicationCache *appCache,
+ nsresult result)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
+
+ nsAutoCString targetURI, sourceURI;
+ mTargetURI->GetAsciiSpec(targetURI);
+ if (mSourceURI) {
+ mSourceURI->GetAsciiSpec(sourceURI);
+ }
+ PREDICTOR_LOG(("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
+ "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
+ "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08x",
+ this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
+ targetURI.get(), sourceURI.get(), mStackCount,
+ isNew, result));
+ if (NS_FAILED(result)) {
+ PREDICTOR_LOG(("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08X). "
+ "Aborting.", this, result));
+ return NS_OK;
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME,
+ mStartTime);
+ if (mPredict) {
+ bool predicted = mPredictor->PredictInternal(mPredictReason, entry, isNew,
+ mFullUri, mTargetURI,
+ mVerifier, mStackCount);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_WORK_TIME, mStartTime);
+ if (predicted) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime);
+ } else {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime);
+ }
+ } else {
+ mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI,
+ mSourceURI);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_LEARN_WORK_TIME, mStartTime);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Predictor,
+ nsINetworkPredictor,
+ nsIObserver,
+ nsISpeculativeConnectionOverrider,
+ nsIInterfaceRequestor,
+ nsICacheEntryMetaDataVisitor,
+ nsINetworkPredictorVerifier)
+
+Predictor::Predictor()
+ :mInitialized(false)
+ ,mEnabled(true)
+ ,mEnableHoverOnSSL(false)
+ ,mEnablePrefetch(true)
+ ,mPageDegradationDay(PREDICTOR_PAGE_DELTA_DAY_DEFAULT)
+ ,mPageDegradationWeek(PREDICTOR_PAGE_DELTA_WEEK_DEFAULT)
+ ,mPageDegradationMonth(PREDICTOR_PAGE_DELTA_MONTH_DEFAULT)
+ ,mPageDegradationYear(PREDICTOR_PAGE_DELTA_YEAR_DEFAULT)
+ ,mPageDegradationMax(PREDICTOR_PAGE_DELTA_MAX_DEFAULT)
+ ,mSubresourceDegradationDay(PREDICTOR_SUB_DELTA_DAY_DEFAULT)
+ ,mSubresourceDegradationWeek(PREDICTOR_SUB_DELTA_WEEK_DEFAULT)
+ ,mSubresourceDegradationMonth(PREDICTOR_SUB_DELTA_MONTH_DEFAULT)
+ ,mSubresourceDegradationYear(PREDICTOR_SUB_DELTA_YEAR_DEFAULT)
+ ,mSubresourceDegradationMax(PREDICTOR_SUB_DELTA_MAX_DEFAULT)
+ ,mPrefetchRollingLoadCount(PREFETCH_ROLLING_LOAD_DEFAULT)
+ ,mPrefetchMinConfidence(PREFETCH_MIN_DEFAULT)
+ ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT)
+ ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT)
+ ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT)
+ ,mPrefetchForceValidFor(PREFETCH_FORCE_VALID_DEFAULT)
+ ,mMaxResourcesPerEntry(PREDICTOR_MAX_RESOURCES_DEFAULT)
+ ,mStartupCount(1)
+ ,mMaxURILength(PREDICTOR_MAX_URI_LENGTH_DEFAULT)
+ ,mDoingTests(false)
+{
+ MOZ_ASSERT(!sSelf, "multiple Predictor instances!");
+ sSelf = this;
+}
+
+Predictor::~Predictor()
+{
+ if (mInitialized)
+ Shutdown();
+
+ sSelf = nullptr;
+}
+
+// Predictor::nsIObserver
+
+nsresult
+Predictor::InstallObserver()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Preferences::AddBoolVarCache(&mEnabled, PREDICTOR_ENABLED_PREF, true);
+ Preferences::AddBoolVarCache(&mEnableHoverOnSSL,
+ PREDICTOR_SSL_HOVER_PREF, false);
+ Preferences::AddBoolVarCache(&mEnablePrefetch, PREDICTOR_PREFETCH_PREF, true);
+ Preferences::AddIntVarCache(&mPageDegradationDay,
+ PREDICTOR_PAGE_DELTA_DAY_PREF,
+ PREDICTOR_PAGE_DELTA_DAY_DEFAULT);
+ Preferences::AddIntVarCache(&mPageDegradationWeek,
+ PREDICTOR_PAGE_DELTA_WEEK_PREF,
+ PREDICTOR_PAGE_DELTA_WEEK_DEFAULT);
+ Preferences::AddIntVarCache(&mPageDegradationMonth,
+ PREDICTOR_PAGE_DELTA_MONTH_PREF,
+ PREDICTOR_PAGE_DELTA_MONTH_DEFAULT);
+ Preferences::AddIntVarCache(&mPageDegradationYear,
+ PREDICTOR_PAGE_DELTA_YEAR_PREF,
+ PREDICTOR_PAGE_DELTA_YEAR_DEFAULT);
+ Preferences::AddIntVarCache(&mPageDegradationMax,
+ PREDICTOR_PAGE_DELTA_MAX_PREF,
+ PREDICTOR_PAGE_DELTA_MAX_DEFAULT);
+
+ Preferences::AddIntVarCache(&mSubresourceDegradationDay,
+ PREDICTOR_SUB_DELTA_DAY_PREF,
+ PREDICTOR_SUB_DELTA_DAY_DEFAULT);
+ Preferences::AddIntVarCache(&mSubresourceDegradationWeek,
+ PREDICTOR_SUB_DELTA_WEEK_PREF,
+ PREDICTOR_SUB_DELTA_WEEK_DEFAULT);
+ Preferences::AddIntVarCache(&mSubresourceDegradationMonth,
+ PREDICTOR_SUB_DELTA_MONTH_PREF,
+ PREDICTOR_SUB_DELTA_MONTH_DEFAULT);
+ Preferences::AddIntVarCache(&mSubresourceDegradationYear,
+ PREDICTOR_SUB_DELTA_YEAR_PREF,
+ PREDICTOR_SUB_DELTA_YEAR_DEFAULT);
+ Preferences::AddIntVarCache(&mSubresourceDegradationMax,
+ PREDICTOR_SUB_DELTA_MAX_PREF,
+ PREDICTOR_SUB_DELTA_MAX_DEFAULT);
+
+ Preferences::AddIntVarCache(&mPrefetchRollingLoadCount,
+ PREDICTOR_PREFETCH_ROLLING_LOAD_PREF,
+ PREFETCH_ROLLING_LOAD_DEFAULT);
+ Preferences::AddIntVarCache(&mPrefetchMinConfidence,
+ PREDICTOR_PREFETCH_MIN_PREF,
+ PREFETCH_MIN_DEFAULT);
+ Preferences::AddIntVarCache(&mPreconnectMinConfidence,
+ PREDICTOR_PRECONNECT_MIN_PREF,
+ PRECONNECT_MIN_DEFAULT);
+ Preferences::AddIntVarCache(&mPreresolveMinConfidence,
+ PREDICTOR_PRERESOLVE_MIN_PREF,
+ PRERESOLVE_MIN_DEFAULT);
+ Preferences::AddIntVarCache(&mRedirectLikelyConfidence,
+ PREDICTOR_REDIRECT_LIKELY_PREF,
+ REDIRECT_LIKELY_DEFAULT);
+
+ Preferences::AddIntVarCache(&mPrefetchForceValidFor,
+ PREDICTOR_PREFETCH_FORCE_VALID_PREF,
+ PREFETCH_FORCE_VALID_DEFAULT);
+
+ Preferences::AddIntVarCache(&mMaxResourcesPerEntry,
+ PREDICTOR_MAX_RESOURCES_PREF,
+ PREDICTOR_MAX_RESOURCES_DEFAULT);
+
+ Preferences::AddBoolVarCache(&mCleanedUp, PREDICTOR_CLEANED_UP_PREF, false);
+
+ Preferences::AddUintVarCache(&mMaxURILength, PREDICTOR_MAX_URI_LENGTH_PREF,
+ PREDICTOR_MAX_URI_LENGTH_DEFAULT);
+
+ Preferences::AddBoolVarCache(&mDoingTests, PREDICTOR_DOING_TESTS_PREF, false);
+
+ if (!mCleanedUp) {
+ mCleanupTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mCleanupTimer->Init(this, 60 * 1000, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return rv;
+}
+
+void
+Predictor::RemoveObserver()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
+
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ if (mCleanupTimer) {
+ mCleanupTimer->Cancel();
+ mCleanupTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+Predictor::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data_unicode)
+{
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor observing something off main thread!");
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ } else if (!strcmp("timer-callback", topic)) {
+ MaybeCleanupOldDBFiles();
+ mCleanupTimer = nullptr;
+ }
+
+ return rv;
+}
+
+// Predictor::nsISpeculativeConnectionOverrider
+
+NS_IMETHODIMP
+Predictor::GetIgnoreIdle(bool *ignoreIdle)
+{
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetParallelSpeculativeConnectLimit(
+ uint32_t *parallelSpeculativeConnectLimit)
+{
+ *parallelSpeculativeConnectLimit = 6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetIsFromPredictor(bool *isFromPredictor)
+{
+ *isFromPredictor = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetAllow1918(bool *allow1918)
+{
+ *allow1918 = false;
+ return NS_OK;
+}
+
+// Predictor::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+Predictor::GetInterface(const nsIID &iid, void **result)
+{
+ return QueryInterface(iid, result);
+}
+
+// Predictor::nsICacheEntryMetaDataVisitor
+
+#define SEEN_META_DATA "predictor::seen"
+#define RESOURCE_META_DATA "predictor::resource-count"
+#define META_DATA_PREFIX "predictor::"
+
+static bool
+IsURIMetadataElement(const char *key)
+{
+ return StringBeginsWith(nsDependentCString(key),
+ NS_LITERAL_CSTRING(META_DATA_PREFIX)) &&
+ !NS_LITERAL_CSTRING(SEEN_META_DATA).Equals(key) &&
+ !NS_LITERAL_CSTRING(RESOURCE_META_DATA).Equals(key);
+}
+
+nsresult
+Predictor::OnMetaDataElement(const char *asciiKey, const char *asciiValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(asciiKey)) {
+ // This isn't a bit of metadata we care about
+ return NS_OK;
+ }
+
+ nsCString key, value;
+ key.AssignASCII(asciiKey);
+ value.AssignASCII(asciiValue);
+ mKeysToOperateOn.AppendElement(key);
+ mValuesToOperateOn.AppendElement(value);
+
+ return NS_OK;
+}
+
+// Predictor::nsINetworkPredictor
+
+nsresult
+Predictor::Init()
+{
+ MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+
+ rv = InstallObserver();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLastStartupTime = mStartupTime = NOW_IN_SECONDS();
+
+ if (!mDNSListener) {
+ mDNSListener = new DNSListener();
+ }
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<LoadContextInfo> lci =
+ new LoadContextInfo(false, NeckoOriginAttributes());
+
+ rv = cacheStorageService->DiskCacheStorage(lci, false,
+ getter_AddRefs(mCacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIOService = do_GetService("@mozilla.org/network/io-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewURI(getter_AddRefs(mStartupURI),
+ "predictor://startup", nullptr, mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSpeculativeService = do_QueryInterface(mIOService, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitialized = true;
+
+ return rv;
+}
+
+namespace {
+class PredictorThreadShutdownRunner : public Runnable
+{
+public:
+ PredictorThreadShutdownRunner(nsIThread *ioThread, bool success)
+ :mIOThread(ioThread)
+ ,mSuccess(success)
+ { }
+ ~PredictorThreadShutdownRunner() { }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "Shutting down io thread off main thread!");
+ if (mSuccess) {
+ // This means the cleanup happened. Mark so we don't try in the
+ // future.
+ Preferences::SetBool(PREDICTOR_CLEANED_UP_PREF, true);
+ }
+ return mIOThread->AsyncShutdown();
+ }
+
+private:
+ nsCOMPtr<nsIThread> mIOThread;
+ bool mSuccess;
+};
+
+class PredictorOldCleanupRunner : public Runnable
+{
+public:
+ PredictorOldCleanupRunner(nsIThread *ioThread, nsIFile *dbFile)
+ :mIOThread(ioThread)
+ ,mDBFile(dbFile)
+ { }
+
+ ~PredictorOldCleanupRunner() { }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "Cleaning up old files on main thread!");
+ nsresult rv = CheckForAndDeleteOldDBFiles();
+ RefPtr<PredictorThreadShutdownRunner> runner =
+ new PredictorThreadShutdownRunner(mIOThread, NS_SUCCEEDED(rv));
+ NS_DispatchToMainThread(runner);
+ return NS_OK;
+ }
+
+private:
+ nsresult CheckForAndDeleteOldDBFiles()
+ {
+ nsCOMPtr<nsIFile> oldDBFile;
+ nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool fileExists = false;
+ rv = oldDBFile->Exists(&fileExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fileExists) {
+ rv = oldDBFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ fileExists = false;
+ rv = mDBFile->Exists(&fileExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fileExists) {
+ rv = mDBFile->Remove(false);
+ }
+
+ return rv;
+ }
+
+ nsCOMPtr<nsIThread> mIOThread;
+ nsCOMPtr<nsIFile> mDBFile;
+};
+
+} // namespace
+
+void
+Predictor::MaybeCleanupOldDBFiles()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mEnabled || mCleanedUp) {
+ return;
+ }
+
+ mCleanedUp = true;
+
+ // This is used for cleaning up junk left over from the old backend
+ // built on top of sqlite, if necessary.
+ nsCOMPtr<nsIFile> dbFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(dbFile));
+ RETURN_IF_FAILED(rv);
+ rv = dbFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite"));
+ RETURN_IF_FAILED(rv);
+
+ nsCOMPtr<nsIThread> ioThread;
+ rv = NS_NewNamedThread("NetPredictClean", getter_AddRefs(ioThread));
+ RETURN_IF_FAILED(rv);
+
+ RefPtr<PredictorOldCleanupRunner> runner =
+ new PredictorOldCleanupRunner(ioThread, dbFile);
+ ioThread->Dispatch(runner, NS_DISPATCH_NORMAL);
+}
+
+void
+Predictor::Shutdown()
+{
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
+ return;
+ }
+
+ RemoveObserver();
+
+ mInitialized = false;
+}
+
+nsresult
+Predictor::Create(nsISupports *aOuter, const nsIID& aIID,
+ void **aResult)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<Predictor> svc = new Predictor();
+ if (IsNeckoChild()) {
+ // Child threads only need to be call into the public interface methods
+ // so we don't bother with initialization
+ return svc->QueryInterface(aIID, aResult);
+ }
+
+ rv = svc->Init();
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop"));
+ }
+
+ // We treat init failure the same as the service being disabled, since this
+ // is all an optimization anyway. No need to freak people out. That's why we
+ // gladly continue on QI'ing here.
+ rv = svc->QueryInterface(aIID, aResult);
+
+ return rv;
+}
+
+// Called from the main thread to initiate predictive actions
+NS_IMETHODIMP
+Predictor::Predict(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorPredictReason reason, nsILoadContext *loadContext,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Predict"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" called on child process"));
+
+ ipc::OptionalURIParams serTargetURI, serSourceURI;
+ SerializeURI(targetURI, serTargetURI);
+ SerializeURI(sourceURI, serSourceURI);
+
+ IPC::SerializedLoadContext serLoadContext;
+ serLoadContext.Init(loadContext);
+
+ // If two different threads are predicting concurently, this will be
+ // overwritten. Thankfully, we only use this in tests, which will
+ // overwrite mVerifier perhaps multiple times for each individual test;
+ // however, within each test, the multiple predict calls should have the
+ // same verifier.
+ if (verifier) {
+ PREDICTOR_LOG((" was given a verifier"));
+ mChildVerifier = verifier;
+ }
+ PREDICTOR_LOG((" forwarding to parent process"));
+ gNeckoChild->SendPredPredict(serTargetURI, serSourceURI,
+ reason, serLoadContext, verifier);
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!mEnabled) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (loadContext && loadContext->UsePrivateBrowsing()) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ // Nothing we can do for non-HTTP[S] schemes
+ PREDICTOR_LOG((" got non-http[s] URI"));
+ return NS_OK;
+ }
+
+ // Ensure we've been given the appropriate arguments for the kind of
+ // prediction we're being asked to do
+ nsCOMPtr<nsIURI> uriKey = targetURI;
+ nsCOMPtr<nsIURI> originKey;
+ switch (reason) {
+ case nsINetworkPredictor::PREDICT_LINK:
+ if (!targetURI || !sourceURI) {
+ PREDICTOR_LOG((" link invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ // Link hover is a special case where we can predict without hitting the
+ // db, so let's go ahead and fire off that prediction here.
+ PredictForLink(targetURI, sourceURI, verifier);
+ return NS_OK;
+ case nsINetworkPredictor::PREDICT_LOAD:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" load invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ case nsINetworkPredictor::PREDICT_STARTUP:
+ if (targetURI || sourceURI) {
+ PREDICTOR_LOG((" startup invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ uriKey = mStartupURI;
+ originKey = mStartupURI;
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Predictor::Reason argReason;
+ argReason.mPredict = reason;
+
+ // First we open the regular cache entry, to ensure we don't gum up the works
+ // waiting on the less-important predictor-only cache entry
+ RefPtr<Predictor::Action> uriAction =
+ new Predictor::Action(Predictor::Action::IS_FULL_URI,
+ Predictor::Action::DO_PREDICT, argReason, targetURI,
+ nullptr, verifier, this);
+ nsAutoCString uriKeyStr;
+ uriKey->GetAsciiSpec(uriKeyStr);
+ PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
+ reason, uriAction.get()));
+ uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::OPEN_PRIORITY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), openFlags, uriAction);
+
+ // Now we do the origin-only (and therefore predictor-only) entry
+ nsCOMPtr<nsIURI> targetOrigin;
+ nsresult rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!originKey) {
+ originKey = targetOrigin;
+ }
+
+ RefPtr<Predictor::Action> originAction =
+ new Predictor::Action(Predictor::Action::IS_ORIGIN,
+ Predictor::Action::DO_PREDICT, argReason,
+ targetOrigin, nullptr, verifier, this);
+ nsAutoCString originKeyStr;
+ originKey->GetAsciiSpec(originKeyStr);
+ PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p", originKeyStr.get(),
+ reason, originAction.get()));
+ openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ mCacheDiskStorage->AsyncOpenURI(originKey,
+ NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
+ openFlags, originAction);
+
+ PREDICTOR_LOG((" predict returning"));
+ return NS_OK;
+}
+
+bool
+Predictor::PredictInternal(PredictorPredictReason reason, nsICacheEntry *entry,
+ bool isNew, bool fullUri, nsIURI *targetURI,
+ nsINetworkPredictorVerifier *verifier,
+ uint8_t stackCount)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictInternal"));
+ bool rv = false;
+
+ if (reason == nsINetworkPredictor::PREDICT_LOAD) {
+ MaybeLearnForStartup(targetURI, fullUri);
+ }
+
+ if (isNew) {
+ // nothing else we can do here
+ PREDICTOR_LOG((" new entry"));
+ return rv;
+ }
+
+ switch (reason) {
+ case nsINetworkPredictor::PREDICT_LOAD:
+ rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
+ break;
+ case nsINetworkPredictor::PREDICT_STARTUP:
+ rv = PredictForStartup(entry, fullUri, verifier);
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ MOZ_ASSERT(false, "Got unexpected value for prediction reason");
+ }
+
+ return rv;
+}
+
+void
+Predictor::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForLink"));
+ if (!mSpeculativeService) {
+ PREDICTOR_LOG((" missing speculative service"));
+ return;
+ }
+
+ if (!mEnableHoverOnSSL) {
+ bool isSSL = false;
+ sourceURI->SchemeIs("https", &isSSL);
+ if (isSSL) {
+ // We don't want to predict from an HTTPS page, to avoid info leakage
+ PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
+ return;
+ }
+ }
+
+ mSpeculativeService->SpeculativeConnect2(targetURI, nullptr, nullptr);
+ if (verifier) {
+ PREDICTOR_LOG((" sending verification"));
+ verifier->OnPredictPreconnect(targetURI);
+ }
+}
+
+// This is the driver for prediction based on a new pageload.
+static const uint8_t MAX_PAGELOAD_DEPTH = 10;
+bool
+Predictor::PredictForPageload(nsICacheEntry *entry, nsIURI *targetURI,
+ uint8_t stackCount, bool fullUri,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForPageload"));
+
+ if (stackCount > MAX_PAGELOAD_DEPTH) {
+ PREDICTOR_LOG((" exceeded recursion depth!"));
+ return false;
+ }
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
+ PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
+
+ int32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> redirectURI;
+ if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
+ getter_AddRefs(redirectURI))) {
+ mPreconnects.AppendElement(redirectURI);
+ Predictor::Reason reason;
+ reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
+ RefPtr<Predictor::Action> redirectAction =
+ new Predictor::Action(Predictor::Action::IS_FULL_URI,
+ Predictor::Action::DO_PREDICT, reason, redirectURI,
+ nullptr, verifier, this, stackCount + 1);
+ nsAutoCString redirectUriString;
+ redirectURI->GetAsciiSpec(redirectUriString);
+ PREDICTOR_LOG((" Predict redirect uri=%s action=%p", redirectUriString.get(),
+ redirectAction.get()));
+ uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::OPEN_PRIORITY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ mCacheDiskStorage->AsyncOpenURI(redirectURI, EmptyCString(), openFlags,
+ redirectAction);
+ return RunPredictions(nullptr, verifier);
+ }
+
+ CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation, fullUri);
+
+ return RunPredictions(targetURI, verifier);
+}
+
+// This is the driver for predicting at browser startup time based on pages that
+// have previously been loaded close to startup.
+bool
+Predictor::PredictForStartup(nsICacheEntry *entry, bool fullUri,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForStartup"));
+ int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
+ CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
+ globalDegradation, fullUri);
+ return RunPredictions(nullptr, verifier);
+}
+
+// This calculates how much to degrade our confidence in our data based on
+// the last time this top-level resource was loaded. This "global degradation"
+// applies to *all* subresources we have associated with the top-level
+// resource. This will be in addition to any reduction in confidence we have
+// associated with a particular subresource.
+int32_t
+Predictor::CalculateGlobalDegradation(uint32_t lastLoad)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int32_t globalDegradation;
+ uint32_t delta = NOW_IN_SECONDS() - lastLoad;
+ if (delta < ONE_DAY) {
+ globalDegradation = mPageDegradationDay;
+ } else if (delta < ONE_WEEK) {
+ globalDegradation = mPageDegradationWeek;
+ } else if (delta < ONE_MONTH) {
+ globalDegradation = mPageDegradationMonth;
+ } else if (delta < ONE_YEAR) {
+ globalDegradation = mPageDegradationYear;
+ } else {
+ globalDegradation = mPageDegradationMax;
+ }
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
+ globalDegradation);
+ return globalDegradation;
+}
+
+// This calculates our overall confidence that a particular subresource will be
+// loaded as part of a top-level load.
+// @param hitCount - the number of times we have loaded this subresource as part
+// of this top-level load
+// @param hitsPossible - the number of times we have performed this top-level
+// load
+// @param lastHit - the timestamp of the last time we loaded this subresource as
+// part of this top-level load
+// @param lastPossible - the timestamp of the last time we performed this
+// top-level load
+// @param globalDegradation - the degradation for this top-level load as
+// determined by CalculateGlobalDegradation
+int32_t
+Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
+ uint32_t lastHit, uint32_t lastPossible,
+ int32_t globalDegradation)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED> predictionsCalculated;
+ ++predictionsCalculated;
+
+ if (!hitsPossible) {
+ return 0;
+ }
+
+ int32_t baseConfidence = (hitCount * 100) / hitsPossible;
+ int32_t maxConfidence = 100;
+ int32_t confidenceDegradation = 0;
+
+ if (lastHit < lastPossible) {
+ // We didn't load this subresource the last time this top-level load was
+ // performed, so let's not bother preconnecting (at the very least).
+ maxConfidence = mPreconnectMinConfidence - 1;
+
+ // Now calculate how much we want to degrade our confidence based on how
+ // long it's been between the last time we did this top-level load and the
+ // last time this top-level load included this subresource.
+ PRTime delta = lastPossible - lastHit;
+ if (delta == 0) {
+ confidenceDegradation = 0;
+ } else if (delta < ONE_DAY) {
+ confidenceDegradation = mSubresourceDegradationDay;
+ } else if (delta < ONE_WEEK) {
+ confidenceDegradation = mSubresourceDegradationWeek;
+ } else if (delta < ONE_MONTH) {
+ confidenceDegradation = mSubresourceDegradationMonth;
+ } else if (delta < ONE_YEAR) {
+ confidenceDegradation = mSubresourceDegradationYear;
+ } else {
+ confidenceDegradation = mSubresourceDegradationMax;
+ maxConfidence = 0;
+ }
+ }
+
+ // Calculate our confidence and clamp it to between 0 and maxConfidence
+ // (<= 100)
+ int32_t confidence = baseConfidence - confidenceDegradation - globalDegradation;
+ confidence = std::max(confidence, 0);
+ confidence = std::min(confidence, maxConfidence);
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
+ Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
+ confidenceDegradation);
+ Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
+ return confidence;
+}
+
+static void
+MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
+ const uint32_t flags, nsCString &newValue)
+{
+ newValue.Truncate();
+ newValue.AppendInt(METADATA_VERSION);
+ newValue.Append(',');
+ newValue.AppendInt(hitCount);
+ newValue.Append(',');
+ newValue.AppendInt(lastHit);
+ newValue.Append(',');
+ newValue.AppendInt(flags);
+}
+
+// On every page load, the rolling window gets shifted by one bit, leaving the
+// lowest bit at 0, to indicate that the subresource in question has not been
+// seen on the most recent page load. If, at some point later during the page load,
+// the subresource is seen again, we will then set the lowest bit to 1. This is
+// how we keep track of how many of the last n pageloads (for n <= 20) a particular
+// subresource has been seen.
+// The rolling window is kept in the upper 20 bits of the flags element of the
+// metadata. This saves 12 bits for regular old flags.
+void
+Predictor::UpdateRollingLoadCount(nsICacheEntry *entry, const uint32_t flags,
+ const char *key, const uint32_t hitCount,
+ const uint32_t lastHit)
+{
+ // Extract just the rolling load count from the flags, shift it to clear the
+ // lowest bit, and put the new value with the existing flags.
+ uint32_t rollingLoadCount = flags & ~kFlagsMask;
+ rollingLoadCount <<= 1;
+ uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
+
+ // Finally, update the metadata on the cache entry.
+ nsAutoCString newValue;
+ MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
+ entry->SetMetaDataElement(key, newValue.BeginReading());
+}
+
+void
+Predictor::SanitizePrefs()
+{
+ if (mPrefetchRollingLoadCount < 0) {
+ mPrefetchRollingLoadCount = 0;
+ } else if (mPrefetchRollingLoadCount > kMaxPrefetchRollingLoadCount) {
+ mPrefetchRollingLoadCount = kMaxPrefetchRollingLoadCount;
+ }
+}
+
+void
+Predictor::CalculatePredictions(nsICacheEntry *entry, nsIURI *referrer,
+ uint32_t lastLoad, uint32_t loadCount,
+ int32_t globalDegradation, bool fullUri)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ SanitizePrefs();
+
+ // Since the visitor gets called under a cache lock, all we do there is get
+ // copies of the keys/values we care about, and then do the real work here
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
+ keysToOperateOn.SwapElements(mKeysToOperateOn);
+ valuesToOperateOn.SwapElements(mValuesToOperateOn);
+
+ MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
+ for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
+ const char *key = keysToOperateOn[i].BeginReading();
+ const char *value = valuesToOperateOn[i].BeginReading();
+
+ nsCOMPtr<nsIURI> uri;
+ uint32_t hitCount, lastHit, flags;
+ if (!ParseMetaDataEntry(key, value, getter_AddRefs(uri), hitCount, lastHit, flags)) {
+ // This failed, get rid of it so we don't waste space
+ entry->SetMetaDataElement(key, nullptr);
+ continue;
+ }
+
+ int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
+ lastLoad, globalDegradation);
+ if (fullUri) {
+ UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
+ }
+ PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key, value, confidence));
+ if (!fullUri) {
+ // Not full URI - don't prefetch! No sense in it!
+ PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
+ flags &= ~FLAG_PREFETCHABLE;
+ } else if (!referrer) {
+ // No referrer means we can't prefetch, so pretend it's non-cacheable,
+ // no matter what.
+ PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
+ flags &= ~FLAG_PREFETCHABLE;
+ } else {
+ uint32_t expectedRollingLoadCount = (1 << mPrefetchRollingLoadCount) - 1;
+ expectedRollingLoadCount <<= kRollingLoadOffset;
+ if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
+ PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
+ flags &= ~FLAG_PREFETCHABLE;
+ }
+ }
+
+ PREDICTOR_LOG((" setting up prediction"));
+ SetupPrediction(confidence, flags, uri);
+ }
+}
+
+// (Maybe) adds a predictive action to the prediction runner, based on our
+// calculated confidence for the subresource in question.
+void
+Predictor::SetupPrediction(int32_t confidence, uint32_t flags, nsIURI *uri)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoCString uriStr;
+ uri->GetAsciiSpec(uriStr);
+ PREDICTOR_LOG(("SetupPrediction mEnablePrefetch=%d mPrefetchMinConfidence=%d "
+ "mPreconnectMinConfidence=%d mPreresolveMinConfidence=%d "
+ "flags=%d confidence=%d uri=%s", mEnablePrefetch,
+ mPrefetchMinConfidence, mPreconnectMinConfidence,
+ mPreresolveMinConfidence, flags, confidence, uriStr.get()));
+ if (mEnablePrefetch && (flags & FLAG_PREFETCHABLE) &&
+ (mPrefetchRollingLoadCount || (confidence >= mPrefetchMinConfidence))) {
+ mPrefetches.AppendElement(uri);
+ } else if (confidence >= mPreconnectMinConfidence) {
+ mPreconnects.AppendElement(uri);
+ } else if (confidence >= mPreresolveMinConfidence) {
+ mPreresolves.AppendElement(uri);
+ }
+}
+
+nsresult
+Predictor::Prefetch(nsIURI *uri, nsIURI *referrer,
+ nsINetworkPredictorVerifier *verifier)
+{
+ nsAutoCString strUri, strReferrer;
+ uri->GetAsciiSpec(strUri);
+ referrer->GetAsciiSpec(strReferrer);
+ PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
+ strUri.get(), strReferrer.get(), verifier));
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, /* aLoadGroup */
+ nullptr, /* aCallbacks */
+ nsIRequest::LOAD_BACKGROUND);
+
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" NS_NewChannel failed rv=0x%X", rv));
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ httpChannel = do_QueryInterface(channel);
+ if (!httpChannel) {
+ PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ httpChannel->SetReferrer(referrer);
+ // XXX - set a header here to indicate this is a prefetch?
+
+ nsCOMPtr<nsIStreamListener> listener = new PrefetchListener(verifier, uri,
+ this);
+ PREDICTOR_LOG((" calling AsyncOpen2 listener=%p channel=%p", listener.get(),
+ channel.get()));
+ rv = channel->AsyncOpen2(listener);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" AsyncOpen2 failed rv=0x%X", rv));
+ }
+
+ return rv;
+}
+
+// Runs predictions that have been set up.
+bool
+Predictor::RunPredictions(nsIURI *referrer, nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
+
+ PREDICTOR_LOG(("Predictor::RunPredictions"));
+
+ bool predicted = false;
+ uint32_t len, i;
+
+ nsTArray<nsCOMPtr<nsIURI>> prefetches, preconnects, preresolves;
+ prefetches.SwapElements(mPrefetches);
+ preconnects.SwapElements(mPreconnects);
+ preresolves.SwapElements(mPreresolves);
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS> totalPredictions;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS> totalPreconnects;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES> totalPreresolves;
+
+ len = prefetches.Length();
+ for (i = 0; i < len; ++i) {
+ PREDICTOR_LOG((" doing prefetch"));
+ nsCOMPtr<nsIURI> uri = prefetches[i];
+ if (NS_SUCCEEDED(Prefetch(uri, referrer, verifier))) {
+ ++totalPredictions;
+ ++totalPrefetches;
+ predicted = true;
+ }
+ }
+
+ len = preconnects.Length();
+ for (i = 0; i < len; ++i) {
+ PREDICTOR_LOG((" doing preconnect"));
+ nsCOMPtr<nsIURI> uri = preconnects[i];
+ ++totalPredictions;
+ ++totalPreconnects;
+ mSpeculativeService->SpeculativeConnect2(uri, nullptr, this);
+ predicted = true;
+ if (verifier) {
+ PREDICTOR_LOG((" sending preconnect verification"));
+ verifier->OnPredictPreconnect(uri);
+ }
+ }
+
+ len = preresolves.Length();
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ for (i = 0; i < len; ++i) {
+ nsCOMPtr<nsIURI> uri = preresolves[i];
+ ++totalPredictions;
+ ++totalPreresolves;
+ nsAutoCString hostname;
+ uri->GetAsciiHost(hostname);
+ PREDICTOR_LOG((" doing preresolve %s", hostname.get()));
+ nsCOMPtr<nsICancelable> tmpCancelable;
+ mDnsService->AsyncResolve(hostname,
+ (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::RESOLVE_SPECULATE),
+ mDNSListener, nullptr,
+ getter_AddRefs(tmpCancelable));
+ predicted = true;
+ if (verifier) {
+ PREDICTOR_LOG((" sending preresolve verification"));
+ verifier->OnPredictDNS(uri);
+ }
+ }
+
+ return predicted;
+}
+
+// Find out if a top-level page is likely to redirect.
+bool
+Predictor::WouldRedirect(nsICacheEntry *entry, uint32_t loadCount,
+ uint32_t lastLoad, int32_t globalDegradation,
+ nsIURI **redirectURI)
+{
+ // TODO - not doing redirects for first go around
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return false;
+}
+
+// Called from the main thread to update the database
+NS_IMETHODIMP
+Predictor::Learn(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadContext *loadContext)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Learn"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" called on child process"));
+
+ ipc::URIParams serTargetURI;
+ SerializeURI(targetURI, serTargetURI);
+
+ ipc::OptionalURIParams serSourceURI;
+ SerializeURI(sourceURI, serSourceURI);
+
+ IPC::SerializedLoadContext serLoadContext;
+ serLoadContext.Init(loadContext);
+
+ PREDICTOR_LOG((" forwarding to parent"));
+ gNeckoChild->SendPredLearn(serTargetURI, serSourceURI, reason,
+ serLoadContext);
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!mEnabled) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (loadContext && loadContext->UsePrivateBrowsing()) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ PREDICTOR_LOG((" got non-HTTP[S] URI"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIURI> targetOrigin;
+ nsCOMPtr<nsIURI> sourceOrigin;
+ nsCOMPtr<nsIURI> uriKey;
+ nsCOMPtr<nsIURI> originKey;
+ nsresult rv;
+
+ switch (reason) {
+ case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" load toplevel invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = targetURI;
+ originKey = targetOrigin;
+ break;
+ case nsINetworkPredictor::LEARN_STARTUP:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" startup invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = mStartupURI;
+ originKey = mStartupURI;
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
+ case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
+ if (!targetURI || !sourceURI) {
+ PREDICTOR_LOG((" redirect/subresource invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = sourceURI;
+ originKey = sourceOrigin;
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
+ ++learnAttempts;
+
+ Predictor::Reason argReason;
+ argReason.mLearn = reason;
+
+ // We always open the full uri (general cache) entry first, so we don't gum up
+ // the works waiting on predictor-only entries to open
+ RefPtr<Predictor::Action> uriAction =
+ new Predictor::Action(Predictor::Action::IS_FULL_URI,
+ Predictor::Action::DO_LEARN, argReason, targetURI,
+ sourceURI, nullptr, this);
+ nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
+ uriKey->GetAsciiSpec(uriKeyStr);
+ targetURI->GetAsciiSpec(targetUriStr);
+ if (sourceURI) {
+ sourceURI->GetAsciiSpec(sourceUriStr);
+ }
+ PREDICTOR_LOG((" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
+ "action=%p", uriKeyStr.get(), targetUriStr.get(),
+ sourceUriStr.get(), reason, uriAction.get()));
+ // For learning full URI things, we *always* open readonly and secretly, as we
+ // rely on actual pageloads to update the entry's metadata for us.
+ uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
+ // Learning for toplevel we want to open the full uri entry priority, since
+ // it's likely this entry will be used soon anyway, and we want this to be
+ // opened ASAP.
+ uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
+ }
+ mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), uriOpenFlags,
+ uriAction);
+
+ // Now we open the origin-only (and therefore predictor-only) entry
+ RefPtr<Predictor::Action> originAction =
+ new Predictor::Action(Predictor::Action::IS_ORIGIN,
+ Predictor::Action::DO_LEARN, argReason, targetOrigin,
+ sourceOrigin, nullptr, this);
+ nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
+ originKey->GetAsciiSpec(originKeyStr);
+ targetOrigin->GetAsciiSpec(targetOriginStr);
+ if (sourceOrigin) {
+ sourceOrigin->GetAsciiSpec(sourceOriginStr);
+ }
+ PREDICTOR_LOG((" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
+ "action=%p", originKeyStr.get(), targetOriginStr.get(),
+ sourceOriginStr.get(), reason, originAction.get()));
+ uint32_t originOpenFlags;
+ if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
+ // This is the only case when we want to update the 'last used' metadata on
+ // the cache entry we're getting. This only applies to predictor-specific
+ // entries.
+ originOpenFlags = nsICacheStorage::OPEN_NORMALLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ } else {
+ originOpenFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ }
+ mCacheDiskStorage->AsyncOpenURI(originKey,
+ NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
+ originOpenFlags, originAction);
+
+ PREDICTOR_LOG(("Predictor::Learn returning"));
+ return NS_OK;
+}
+
+void
+Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry,
+ bool isNew, bool fullUri, nsIURI *targetURI,
+ nsIURI *sourceURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::LearnInternal"));
+
+ nsCString junk;
+ if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
+ NS_FAILED(entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
+ // This is an origin-only entry that we haven't seen before. Let's mark it
+ // as seen.
+ PREDICTOR_LOG((" marking new origin entry as seen"));
+ nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" failed to mark origin entry seen"));
+ return;
+ }
+
+ // Need to ensure someone else can get to the entry if necessary
+ entry->MetaDataReady();
+ return;
+ }
+
+ switch (reason) {
+ case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
+ // This case only exists to be used during tests - code outside the
+ // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
+ // The predictor xpcshell test needs this branch, however, because we
+ // have no real page loads in xpcshell, and this is how we fake it up
+ // so that all the work that normally happens behind the scenes in a
+ // page load can be done for testing purposes.
+ if (fullUri && mDoingTests) {
+ PREDICTOR_LOG((" WARNING - updating rolling load count. "
+ "If you see this outside tests, you did it wrong"));
+ SanitizePrefs();
+
+ // Since the visitor gets called under a cache lock, all we do there is get
+ // copies of the keys/values we care about, and then do the real work here
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
+ keysToOperateOn.SwapElements(mKeysToOperateOn);
+ valuesToOperateOn.SwapElements(mValuesToOperateOn);
+
+ MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
+ for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
+ const char *key = keysToOperateOn[i].BeginReading();
+ const char *value = valuesToOperateOn[i].BeginReading();
+
+ nsCOMPtr<nsIURI> uri;
+ uint32_t hitCount, lastHit, flags;
+ if (!ParseMetaDataEntry(nullptr, value, nullptr, hitCount, lastHit, flags)) {
+ // This failed, get rid of it so we don't waste space
+ entry->SetMetaDataElement(key, nullptr);
+ continue;
+ }
+ UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
+ }
+ } else {
+ PREDICTOR_LOG((" nothing to do for toplevel"));
+ }
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
+ if (fullUri) {
+ LearnForRedirect(entry, targetURI);
+ }
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
+ LearnForSubresource(entry, targetURI);
+ break;
+ case nsINetworkPredictor::LEARN_STARTUP:
+ LearnForStartup(entry, targetURI);
+ break;
+ default:
+ PREDICTOR_LOG((" unexpected reason value"));
+ MOZ_ASSERT(false, "Got unexpected value for learn reason!");
+ }
+}
+
+NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
+
+NS_IMETHODIMP
+Predictor::SpaceCleaner::OnMetaDataElement(const char *key, const char *value)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(key)) {
+ // This isn't a bit of metadata we care about
+ return NS_OK;
+ }
+
+ uint32_t hitCount, lastHit, flags;
+ bool ok = mPredictor->ParseMetaDataEntry(nullptr, value, nullptr,
+ hitCount, lastHit, flags);
+
+ if (!ok) {
+ // Couldn't parse this one, just get rid of it
+ nsCString nsKey;
+ nsKey.AssignASCII(key);
+ mLongKeysToDelete.AppendElement(nsKey);
+ return NS_OK;
+ }
+
+ nsCString uri(key + (sizeof(META_DATA_PREFIX) - 1));
+ uint32_t uriLength = uri.Length();
+ if (uriLength > mPredictor->mMaxURILength) {
+ // Default to getting rid of URIs that are too long and were put in before
+ // we had our limit on URI length, in order to free up some space.
+ nsCString nsKey;
+ nsKey.AssignASCII(key);
+ mLongKeysToDelete.AppendElement(nsKey);
+ return NS_OK;
+ }
+
+ if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
+ mLRUKeyToDelete = key;
+ mLRUStamp = lastHit;
+ }
+
+ return NS_OK;
+}
+
+void
+Predictor::SpaceCleaner::Finalize(nsICacheEntry *entry)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mLRUKeyToDelete) {
+ entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
+ }
+
+ for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
+ entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
+ }
+}
+
+// Called when a subresource has been hit from a top-level load. Uses the two
+// helper functions above to update the database appropriately.
+void
+Predictor::LearnForSubresource(nsICacheEntry *entry, nsIURI *targetURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::LearnForSubresource"));
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ RETURN_IF_FAILED(rv);
+
+ int32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ RETURN_IF_FAILED(rv);
+
+ nsCString key;
+ key.AssignLiteral(META_DATA_PREFIX);
+ nsCString uri;
+ targetURI->GetAsciiSpec(uri);
+ key.Append(uri);
+ if (uri.Length() > mMaxURILength) {
+ // We do this to conserve space/prevent OOMs
+ PREDICTOR_LOG((" uri too long!"));
+ entry->SetMetaDataElement(key.BeginReading(), nullptr);
+ return;
+ }
+
+ nsCString value;
+ rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
+
+ uint32_t hitCount, lastHit, flags;
+ bool isNewResource = (NS_FAILED(rv) ||
+ !ParseMetaDataEntry(nullptr, value.BeginReading(),
+ nullptr, hitCount, lastHit, flags));
+
+ int32_t resourceCount = 0;
+ if (isNewResource) {
+ // This is a new addition
+ PREDICTOR_LOG((" new resource"));
+ nsCString s;
+ rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
+ if (NS_SUCCEEDED(rv)) {
+ resourceCount = atoi(s.BeginReading());
+ }
+ if (resourceCount >= mMaxResourcesPerEntry) {
+ RefPtr<Predictor::SpaceCleaner> cleaner =
+ new Predictor::SpaceCleaner(this);
+ entry->VisitMetaData(cleaner);
+ cleaner->Finalize(entry);
+ } else {
+ ++resourceCount;
+ }
+ nsAutoCString count;
+ count.AppendInt(resourceCount);
+ rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" failed to update resource count"));
+ return;
+ }
+ hitCount = 1;
+ flags = 0;
+ } else {
+ PREDICTOR_LOG((" existing resource"));
+ hitCount = std::min(hitCount + 1, static_cast<uint32_t>(loadCount));
+ }
+
+ // Update the rolling load count to mark this sub-resource as seen on the
+ // most-recent pageload so it can be eligible for prefetch (assuming all
+ // the other stars align).
+ flags |= (1 << kRollingLoadOffset);
+
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
+ rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
+ PREDICTOR_LOG((" SetMetaDataElement -> 0x%08X", rv));
+ if (NS_FAILED(rv) && isNewResource) {
+ // Roll back the increment to the resource count we made above.
+ PREDICTOR_LOG((" rolling back resource count update"));
+ --resourceCount;
+ if (resourceCount == 0) {
+ entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr);
+ } else {
+ nsAutoCString count;
+ count.AppendInt(resourceCount);
+ entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
+ }
+ }
+}
+
+// This is called when a top-level loaded ended up redirecting to a different
+// URI so we can keep track of that fact.
+void
+Predictor::LearnForRedirect(nsICacheEntry *entry, nsIURI *targetURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO - not doing redirects for first go around
+ PREDICTOR_LOG(("Predictor::LearnForRedirect"));
+}
+
+// This will add a page to our list of startup pages if it's being loaded
+// before our startup window has expired.
+void
+Predictor::MaybeLearnForStartup(nsIURI *uri, bool fullUri)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO - not doing startup for first go around
+ PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
+}
+
+// Add information about a top-level load to our list of startup pages
+void
+Predictor::LearnForStartup(nsICacheEntry *entry, nsIURI *targetURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // These actually do the same set of work, just on different entries, so we
+ // can pass through to get the real work done here
+ PREDICTOR_LOG(("Predictor::LearnForStartup"));
+ LearnForSubresource(entry, targetURI);
+}
+
+bool
+Predictor::ParseMetaDataEntry(const char *key, const char *value, nsIURI **uri,
+ uint32_t &hitCount, uint32_t &lastHit,
+ uint32_t &flags)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::ParseMetaDataEntry key=%s value=%s",
+ key ? key : "", value));
+
+ const char *comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find first comma"));
+ return false;
+ }
+
+ uint32_t version = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" version -> %u", version));
+
+ if (version != METADATA_VERSION) {
+ PREDICTOR_LOG((" metadata version mismatch %u != %u", version,
+ METADATA_VERSION));
+ return false;
+ }
+
+ value = comma + 1;
+ comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find second comma"));
+ return false;
+ }
+
+ hitCount = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" hitCount -> %u", hitCount));
+
+ value = comma + 1;
+ comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find third comma"));
+ return false;
+ }
+
+ lastHit = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" lastHit -> %u", lastHit));
+
+ value = comma + 1;
+ flags = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" flags -> %u", flags));
+
+ if (key) {
+ const char *uriStart = key + (sizeof(META_DATA_PREFIX) - 1);
+ nsresult rv = NS_NewURI(uri, uriStart, nullptr, mIOService);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" NS_NewURI returned 0x%X", rv));
+ return false;
+ }
+ PREDICTOR_LOG((" uri -> %s", uriStart));
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+Predictor::Reset()
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Reset"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" forwarding to parent process"));
+ gNeckoChild->SendPredReset();
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!mEnabled) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
+ PREDICTOR_LOG((" created a resetter"));
+ mCacheDiskStorage->AsyncVisitStorage(reset, true);
+ PREDICTOR_LOG((" Cache async launched, returning now"));
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Predictor::Resetter,
+ nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor,
+ nsICacheStorageVisitor);
+
+Predictor::Resetter::Resetter(Predictor *predictor)
+ :mEntriesToVisit(0)
+ ,mPredictor(predictor)
+{ }
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry *entry,
+ nsIApplicationCache *appCache,
+ uint32_t *result)
+{
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew,
+ nsIApplicationCache *appCache,
+ nsresult result)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(result)) {
+ // This can happen when we've tried to open an entry that doesn't exist for
+ // some non-reset operation, and then get reset shortly thereafter (as
+ // happens in some of our tests).
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+ return NS_OK;
+ }
+
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToDelete;
+ keysToDelete.SwapElements(mKeysToDelete);
+
+ for (size_t i = 0; i < keysToDelete.Length(); ++i) {
+ const char *key = keysToDelete[i].BeginReading();
+ entry->SetMetaDataElement(key, nullptr);
+ }
+
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnMetaDataElement(const char *asciiKey,
+ const char *asciiValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!StringBeginsWith(nsDependentCString(asciiKey),
+ NS_LITERAL_CSTRING(META_DATA_PREFIX))) {
+ // Not a metadata entry we care about, carry on
+ return NS_OK;
+ }
+
+ nsCString key;
+ key.AssignASCII(asciiKey);
+ mKeysToDelete.AppendElement(key);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount, uint64_t consumption,
+ uint64_t capacity, nsIFile *diskDirectory)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryInfo(nsIURI *uri, const nsACString &idEnhance,
+ int64_t dataSize, int32_t fetchCount,
+ uint32_t lastModifiedTime, uint32_t expirationTime,
+ bool aPinned)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // The predictor will only ever touch entries with no idEnhance ("") or an
+ // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
+ // don't match that to avoid doing extra work.
+ if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) {
+ // This is an entry we own, so we can just doom it entirely
+ mPredictor->mCacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr);
+ } else if (idEnhance.IsEmpty()) {
+ // This is an entry we don't own, so we have to be a little more careful and
+ // just get rid of our own metadata entries. Append it to an array of things
+ // to operate on and then do the operations later so we don't end up calling
+ // Complete() multiple times/too soon.
+ ++mEntriesToVisit;
+ mURIsToVisit.AppendElement(uri);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryVisitCompleted()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsTArray<nsCOMPtr<nsIURI>> urisToVisit;
+ urisToVisit.SwapElements(mURIsToVisit);
+
+ MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
+ if (!mEntriesToVisit) {
+ Complete();
+ return NS_OK;
+ }
+
+ uint32_t entriesToVisit = urisToVisit.Length();
+ for (uint32_t i = 0; i < entriesToVisit; ++i) {
+ nsCString u;
+ urisToVisit[i]->GetAsciiSpec(u);
+ mPredictor->mCacheDiskStorage->AsyncOpenURI(
+ urisToVisit[i], EmptyCString(),
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | nsICacheStorage::CHECK_MULTITHREADED,
+ this);
+ }
+
+ return NS_OK;
+}
+
+void
+Predictor::Resetter::Complete()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
+ return;
+ }
+
+ obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
+}
+
+// Helper functions to make using the predictor easier from native code
+
+static nsresult
+EnsureGlobalPredictor(nsINetworkPredictor **aPredictor)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1",
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ predictor.forget(aPredictor);
+ return NS_OK;
+}
+
+nsresult
+PredictorPredict(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorPredictReason reason, nsILoadContext *loadContext,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->Predict(targetURI, sourceURI, reason,
+ loadContext, verifier);
+}
+
+nsresult
+PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadContext *loadContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadGroup *loadGroup)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ loadContext = do_GetInterface(callbacks);
+ }
+ }
+
+ return predictor->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsIDocument *document)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+
+ if (document) {
+ loadContext = document->GetLoadContext();
+ }
+
+ return predictor->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+PredictorLearnRedirect(nsIURI *targetURI, nsIChannel *channel,
+ nsILoadContext *loadContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> sourceURI;
+ nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool sameUri;
+ rv = targetURI->Equals(sourceURI, &sameUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (sameUri) {
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->Learn(targetURI, sourceURI,
+ nsINetworkPredictor::LEARN_LOAD_REDIRECT,
+ loadContext);
+}
+
+// nsINetworkPredictorVerifier
+
+/**
+ * Call through to the child's verifier (only during tests)
+ */
+NS_IMETHODIMP
+Predictor::OnPredictPrefetch(nsIURI *aURI, uint32_t httpStatus)
+{
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child process
+ // will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictPrefetch(aURI, httpStatus);
+ }
+ return NS_OK;
+ }
+
+ ipc::URIParams serURI;
+ SerializeURI(aURI, serURI);
+
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictPrefetch(serURI, httpStatus)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::OnPredictPreconnect(nsIURI *aURI) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child process
+ // will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictPreconnect(aURI);
+ }
+ return NS_OK;
+ }
+
+ ipc::URIParams serURI;
+ SerializeURI(aURI, serURI);
+
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictPreconnect(serURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::OnPredictDNS(nsIURI *aURI) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child process
+ // will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictDNS(aURI);
+ }
+ return NS_OK;
+ }
+
+ ipc::URIParams serURI;
+ SerializeURI(aURI, serURI);
+
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictDNS(serURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Predictor::PrefetchListener
+// nsISupports
+NS_IMPL_ISUPPORTS(Predictor::PrefetchListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+// nsIRequestObserver
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ mStartTime = TimeStamp::Now();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%X", this, aStatusCode));
+ NS_ENSURE_ARG(aRequest);
+ if (NS_FAILED(aStatusCode)) {
+ return aStatusCode;
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME, mStartTime);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ PREDICTOR_LOG((" Could not get HTTP Channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel);
+ if (!cachingChannel) {
+ PREDICTOR_LOG((" Could not get caching channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (NS_SUCCEEDED(rv) && httpStatus == 200) {
+ rv = cachingChannel->ForceCacheEntryValidFor(mPredictor->mPrefetchForceValidFor);
+ PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%X",
+ mPredictor->mPrefetchForceValidFor, rv));
+ } else {
+ rv = cachingChannel->ForceCacheEntryValidFor(0);
+ PREDICTOR_LOG((" removing any forced validity rv=%X", rv));
+ }
+
+ nsAutoCString reqName;
+ rv = aRequest->GetName(reqName);
+ if (NS_FAILED(rv)) {
+ reqName.AssignLiteral("<unknown>");
+ }
+
+ PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus));
+
+ if (mVerifier) {
+ mVerifier->OnPredictPrefetch(mURI, httpStatus);
+ }
+
+ return rv;
+}
+
+// nsIStreamListener
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ const uint32_t aCount)
+{
+ uint32_t result;
+ return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
+}
+
+// Miscellaneous Predictor
+
+void
+Predictor::UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead &requestHead,
+ nsHttpResponseHead *responseHead,
+ nsILoadContextInfo *lci)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (lci && lci->IsPrivate()) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
+ return;
+ }
+
+ if (!sourceURI || !targetURI) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability missing source or target uri"));
+ return;
+ }
+
+ if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
+ return;
+ }
+
+ RefPtr<Predictor> self = sSelf;
+ if (self) {
+ nsAutoCString method;
+ requestHead.Method(method);
+ self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus,
+ method);
+ }
+}
+
+void
+Predictor::UpdateCacheabilityInternal(nsIURI *sourceURI, nsIURI *targetURI,
+ uint32_t httpStatus,
+ const nsCString &method)
+{
+ PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return;
+ }
+
+ if (!mEnabled) {
+ PREDICTOR_LOG((" not enabled"));
+ return;
+ }
+
+ if (!mEnablePrefetch) {
+ PREDICTOR_LOG((" prefetch not enabled"));
+ return;
+ }
+
+ uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ RefPtr<Predictor::CacheabilityAction> action =
+ new Predictor::CacheabilityAction(targetURI, httpStatus, method, this);
+ nsAutoCString uri;
+ targetURI->GetAsciiSpec(uri);
+ PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get()));
+ mCacheDiskStorage->AsyncOpenURI(sourceURI, EmptyCString(), openFlags, action);
+}
+
+NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction,
+ nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor);
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry *entry,
+ nsIApplicationCache *appCache,
+ uint32_t *result)
+{
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry *entry,
+ bool isNew,
+ nsIApplicationCache *appCache,
+ nsresult result)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // This is being opened read-only, so isNew should always be false
+ MOZ_ASSERT(!isNew);
+
+ PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
+ if (NS_FAILED(result)) {
+ // Nothing to do
+ PREDICTOR_LOG((" nothing to do result=%X isNew=%d", result, isNew));
+ return NS_OK;
+ }
+
+ nsresult rv = entry->VisitMetaData(this);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" VisitMetaData returned %x", rv));
+ return NS_OK;
+ }
+
+ nsTArray<nsCString> keysToCheck, valuesToCheck;
+ keysToCheck.SwapElements(mKeysToCheck);
+ valuesToCheck.SwapElements(mValuesToCheck);
+
+ MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length());
+ for (size_t i = 0; i < keysToCheck.Length(); ++i) {
+ const char *key = keysToCheck[i].BeginReading();
+ const char *value = valuesToCheck[i].BeginReading();
+ nsCOMPtr<nsIURI> uri;
+ uint32_t hitCount, lastHit, flags;
+
+ if (!mPredictor->ParseMetaDataEntry(key, value, getter_AddRefs(uri),
+ hitCount, lastHit, flags)) {
+ PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value));
+ continue;
+ }
+
+ bool eq = false;
+ if (NS_SUCCEEDED(uri->Equals(mTargetURI, &eq)) && eq) {
+ if (mHttpStatus == 200 && mMethod.EqualsLiteral("GET")) {
+ PREDICTOR_LOG((" marking %s cacheable", key));
+ flags |= FLAG_PREFETCHABLE;
+ } else {
+ PREDICTOR_LOG((" marking %s uncacheable", key));
+ flags &= ~FLAG_PREFETCHABLE;
+ }
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastHit, flags, newValue);
+ entry->SetMetaDataElement(key, newValue.BeginReading());
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnMetaDataElement(const char *asciiKey,
+ const char *asciiValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(asciiKey)) {
+ return NS_OK;
+ }
+
+ nsCString key, value;
+ key.AssignASCII(asciiKey);
+ value.AssignASCII(asciiValue);
+ mKeysToCheck.AppendElement(key);
+ mValuesToCheck.AppendElement(value);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/Predictor.h b/netwerk/base/Predictor.h
new file mode 100644
index 0000000000..69c597598f
--- /dev/null
+++ b/netwerk/base/Predictor.h
@@ -0,0 +1,480 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Predictor_h
+#define mozilla_net_Predictor_h
+
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+
+#include "nsCOMPtr.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsIDNSListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIStreamListener.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/TimeStamp.h"
+
+class nsICacheStorage;
+class nsIDNSService;
+class nsIIOService;
+class nsILoadContextInfo;
+class nsITimer;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
+class Predictor : public nsINetworkPredictor
+ , public nsIObserver
+ , public nsISpeculativeConnectionOverrider
+ , public nsIInterfaceRequestor
+ , public nsICacheEntryMetaDataVisitor
+ , public nsINetworkPredictorVerifier
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKPREDICTOR
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSINETWORKPREDICTORVERIFIER
+
+ Predictor();
+
+ nsresult Init();
+ void Shutdown();
+ static nsresult Create(nsISupports *outer, const nsIID& iid, void **result);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ static void UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead &requestHead,
+ nsHttpResponseHead *reqponseHead,
+ nsILoadContextInfo *lci);
+
+private:
+ virtual ~Predictor();
+
+ // Stores callbacks for a child process predictor (for test purposes)
+ nsCOMPtr<nsINetworkPredictorVerifier> mChildVerifier;
+
+ union Reason {
+ PredictorLearnReason mLearn;
+ PredictorPredictReason mPredict;
+ };
+
+ class DNSListener : public nsIDNSListener
+ {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ DNSListener()
+ { }
+
+ private:
+ virtual ~DNSListener()
+ { }
+ };
+
+ class Action : public nsICacheEntryOpenCallback
+ {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+
+ Action(bool fullUri, bool predict, Reason reason,
+ nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier, Predictor *predictor);
+ Action(bool fullUri, bool predict, Reason reason,
+ nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier, Predictor *predictor,
+ uint8_t stackCount);
+
+ static const bool IS_FULL_URI = true;
+ static const bool IS_ORIGIN = false;
+
+ static const bool DO_PREDICT = true;
+ static const bool DO_LEARN = false;
+
+ private:
+ virtual ~Action();
+
+ bool mFullUri : 1;
+ bool mPredict : 1;
+ union {
+ PredictorPredictReason mPredictReason;
+ PredictorLearnReason mLearnReason;
+ };
+ nsCOMPtr<nsIURI> mTargetURI;
+ nsCOMPtr<nsIURI> mSourceURI;
+ nsCOMPtr<nsINetworkPredictorVerifier> mVerifier;
+ TimeStamp mStartTime;
+ uint8_t mStackCount;
+ RefPtr<Predictor> mPredictor;
+ };
+
+ class CacheabilityAction : public nsICacheEntryOpenCallback
+ , public nsICacheEntryMetaDataVisitor
+ {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+
+ CacheabilityAction(nsIURI *targetURI, uint32_t httpStatus,
+ const nsCString &method, Predictor *predictor)
+ :mTargetURI(targetURI)
+ ,mHttpStatus(httpStatus)
+ ,mMethod(method)
+ ,mPredictor(predictor)
+ { }
+
+ private:
+ virtual ~CacheabilityAction() { }
+
+ nsCOMPtr<nsIURI> mTargetURI;
+ uint32_t mHttpStatus;
+ nsCString mMethod;
+ RefPtr<Predictor> mPredictor;
+ nsTArray<nsCString> mKeysToCheck;
+ nsTArray<nsCString> mValuesToCheck;
+ };
+
+ class Resetter : public nsICacheEntryOpenCallback,
+ public nsICacheEntryMetaDataVisitor,
+ public nsICacheStorageVisitor
+ {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSICACHESTORAGEVISITOR
+
+ explicit Resetter(Predictor *predictor);
+
+ private:
+ virtual ~Resetter() { }
+
+ void Complete();
+
+ uint32_t mEntriesToVisit;
+ nsTArray<nsCString> mKeysToDelete;
+ RefPtr<Predictor> mPredictor;
+ nsTArray<nsCOMPtr<nsIURI>> mURIsToVisit;
+ };
+
+ class SpaceCleaner : public nsICacheEntryMetaDataVisitor
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+
+ explicit SpaceCleaner(Predictor *predictor)
+ :mLRUStamp(0)
+ ,mLRUKeyToDelete(nullptr)
+ ,mPredictor(predictor)
+ { }
+
+ void Finalize(nsICacheEntry *entry);
+
+ private:
+ virtual ~SpaceCleaner() { }
+ uint32_t mLRUStamp;
+ const char *mLRUKeyToDelete;
+ nsTArray<nsCString> mLongKeysToDelete;
+ RefPtr<Predictor> mPredictor;
+ };
+
+ class PrefetchListener : public nsIStreamListener
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ PrefetchListener(nsINetworkPredictorVerifier *verifier, nsIURI *uri,
+ Predictor *predictor)
+ :mVerifier(verifier)
+ ,mURI(uri)
+ ,mPredictor(predictor)
+ { }
+
+ private:
+ virtual ~PrefetchListener() { }
+
+ nsCOMPtr<nsINetworkPredictorVerifier> mVerifier;
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<Predictor> mPredictor;
+ TimeStamp mStartTime;
+ };
+
+ // Observer-related stuff
+ nsresult InstallObserver();
+ void RemoveObserver();
+
+ // Service startup utilities
+ void MaybeCleanupOldDBFiles();
+
+ // The guts of prediction
+
+ // This is the top-level driver for doing any prediction that needs
+ // information from the cache. Returns true if any predictions were queued up
+ // * reason - What kind of prediction this is/why this prediction is
+ // happening (pageload, startup)
+ // * entry - the cache entry with the information we need
+ // * isNew - whether or not the cache entry is brand new and empty
+ // * fullUri - whether we are doing predictions based on a full page URI, or
+ // just the origin of the page
+ // * targetURI - the URI that we are predicting based upon - IOW, the URI
+ // that is being loaded or being redirected to
+ // * verifier - used for testing to verify the expected predictions happen
+ // * stackCount - used to ensure we don't recurse too far trying to find the
+ // final redirection in a redirect chain
+ bool PredictInternal(PredictorPredictReason reason, nsICacheEntry *entry,
+ bool isNew, bool fullUri, nsIURI *targetURI,
+ nsINetworkPredictorVerifier *verifier,
+ uint8_t stackCount);
+
+ // Used when predicting because the user's mouse hovered over a link
+ // * targetURI - the URI target of the link
+ // * sourceURI - the URI of the page on which the link appears
+ // * verifier - used for testing to verify the expected predictions happen
+ void PredictForLink(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier);
+
+ // Used when predicting because a page is being loaded (which may include
+ // being the target of a redirect). All arguments are the same as for
+ // PredictInternal. Returns true if any predictions were queued up.
+ bool PredictForPageload(nsICacheEntry *entry,
+ nsIURI *targetURI,
+ uint8_t stackCount,
+ bool fullUri,
+ nsINetworkPredictorVerifier *verifier);
+
+ // Used when predicting pages that will be used near browser startup. All
+ // arguments are the same as for PredictInternal. Returns true if any
+ // predictions were queued up.
+ bool PredictForStartup(nsICacheEntry *entry,
+ bool fullUri,
+ nsINetworkPredictorVerifier *verifier);
+
+ // Utilities related to prediction
+
+ // Used to update our rolling load count (how many of the last n loads was a
+ // partular resource loaded on?)
+ // * entry - cache entry of page we're loading
+ // * flags - value that contains our rolling count as the top 20 bits (but
+ // we may use fewer than those 20 bits for calculations)
+ // * key - metadata key that we will update on entry
+ // * hitCount - part of the metadata we need to preserve
+ // * lastHit - part of the metadata we need to preserve
+ void UpdateRollingLoadCount(nsICacheEntry *entry, const uint32_t flags,
+ const char *key, const uint32_t hitCount,
+ const uint32_t lastHit);
+
+ // Used to calculate how much to degrade our confidence for all resources
+ // on a particular page, because of how long ago the most recent load of that
+ // page was. Returns a value between 0 (very recent most recent load) and 100
+ // (very distant most recent load)
+ // * lastLoad - time stamp of most recent load of a page
+ int32_t CalculateGlobalDegradation(uint32_t lastLoad);
+
+ // Used to calculate how confident we are that a particular resource will be
+ // used. Returns a value between 0 (no confidence) and 100 (very confident)
+ // * hitCount - number of times this resource has been seen when loading
+ // this page
+ // * hitsPossible - number of times this page has been loaded
+ // * lastHit - timestamp of the last time this resource was seen when
+ // loading this page
+ // * lastPossible - timestamp of the last time this page was loaded
+ // * globalDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ int32_t CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
+ uint32_t lastHit, uint32_t lastPossible,
+ int32_t globalDegradation);
+
+ // Used to calculate all confidence values for all resources associated with a
+ // page.
+ // * entry - the cache entry with all necessary information about this page
+ // * referrer - the URI that we are loading (may be null)
+ // * lastLoad - timestamp of the last time this page was loaded
+ // * loadCount - number of times this page has been loaded
+ // * gloablDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ // * fullUri - whether we're predicting for a full URI or origin-only
+ void CalculatePredictions(nsICacheEntry *entry, nsIURI *referrer,
+ uint32_t lastLoad, uint32_t loadCount,
+ int32_t globalDegradation, bool fullUri);
+
+ // Used to prepare any necessary prediction for a resource on a page
+ // * confidence - value calculated by CalculateConfidence for this resource
+ // * flags - the flags taken from the resource
+ // * uri - the URI of the resource
+ void SetupPrediction(int32_t confidence, uint32_t flags, nsIURI *uri);
+
+ // Used to kick off a prefetch from RunPredictions if necessary
+ // * uri - the URI to prefetch
+ // * referrer - the URI of the referring page
+ // * verifier - used for testing to ensure the expected prefetch happens
+ nsresult Prefetch(nsIURI *uri, nsIURI *referrer, nsINetworkPredictorVerifier *verifier);
+
+ // Used to actually perform any predictions set up via SetupPrediction.
+ // Returns true if any predictions were performed.
+ // * referrer - the URI we are predicting from
+ // * verifier - used for testing to ensure the expected predictions happen
+ bool RunPredictions(nsIURI *referrer, nsINetworkPredictorVerifier *verifier);
+
+ // Used to guess whether a page will redirect to another page or not. Returns
+ // true if a redirection is likely.
+ // * entry - cache entry with all necessary information about this page
+ // * loadCount - number of times this page has been loaded
+ // * lastLoad - timestamp of the last time this page was loaded
+ // * globalDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ // * redirectURI - if this returns true, the URI that is likely to be
+ // redirected to, otherwise null
+ bool WouldRedirect(nsICacheEntry *entry, uint32_t loadCount,
+ uint32_t lastLoad, int32_t globalDegradation,
+ nsIURI **redirectURI);
+
+ // The guts of learning information
+
+ // This is the top-level driver for doing any updating of our information in
+ // the cache
+ // * reason - why this learn is happening (pageload, startup, redirect)
+ // * entry - the cache entry with the information we need
+ // * isNew - whether or not the cache entry is brand new and empty
+ // * fullUri - whether we are doing predictions based on a full page URI, or
+ // just the origin of the page
+ // * targetURI - the URI that we are adding to our data - most often a
+ // resource loaded by a page the user navigated to
+ // * sourceURI - the URI that caused targetURI to be loaded, usually the
+ // page the user navigated to
+ void LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry,
+ bool isNew, bool fullUri, nsIURI *targetURI,
+ nsIURI *sourceURI);
+
+ // Used when learning about a resource loaded by a page
+ // * entry - the cache entry with information that needs updating
+ // * targetURI - the URI of the resource that was loaded by the page
+ void LearnForSubresource(nsICacheEntry *entry, nsIURI *targetURI);
+
+ // Used when learning about a redirect from one page to another
+ // * entry - the cache entry of the page that was redirected from
+ // * targetURI - the URI of the redirect target
+ void LearnForRedirect(nsICacheEntry *entry, nsIURI *targetURI);
+
+ // Used to learn about pages loaded close to browser startup. This results in
+ // LearnForStartup being called if we are, in fact, near browser startup
+ // * uri - the URI of a page that has been loaded (may not have been near
+ // browser startup)
+ // * fullUri - true if this is a full page uri, false if it's an origin
+ void MaybeLearnForStartup(nsIURI *uri, bool fullUri);
+
+ // Used in conjunction with MaybeLearnForStartup to learn about pages loaded
+ // close to browser startup
+ // * entry - the cache entry that stores the startup page list
+ // * targetURI - the URI of a page that was loaded near browser startup
+ void LearnForStartup(nsICacheEntry *entry, nsIURI *targetURI);
+
+ // Used to parse the data we store in cache metadata
+ // * key - the cache metadata key
+ // * value - the cache metadata value
+ // * uri - (out) the URI this metadata entry was about
+ // * hitCount - (out) the number of times this URI has been seen
+ // * lastHit - (out) timestamp of the last time this URI was seen
+ // * flags - (out) flags for this metadata entry
+ bool ParseMetaDataEntry(const char *key, const char *value, nsIURI **uri,
+ uint32_t &hitCount, uint32_t &lastHit,
+ uint32_t &flags);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ void UpdateCacheabilityInternal(nsIURI *sourceURI, nsIURI *targetURI,
+ uint32_t httpStatus, const nsCString &method);
+
+ // Make sure our prefs are in their expected range of values
+ void SanitizePrefs();
+
+ // Our state
+ bool mInitialized;
+
+ bool mEnabled;
+ bool mEnableHoverOnSSL;
+ bool mEnablePrefetch;
+
+ int32_t mPageDegradationDay;
+ int32_t mPageDegradationWeek;
+ int32_t mPageDegradationMonth;
+ int32_t mPageDegradationYear;
+ int32_t mPageDegradationMax;
+
+ int32_t mSubresourceDegradationDay;
+ int32_t mSubresourceDegradationWeek;
+ int32_t mSubresourceDegradationMonth;
+ int32_t mSubresourceDegradationYear;
+ int32_t mSubresourceDegradationMax;
+
+ int32_t mPrefetchRollingLoadCount;
+ int32_t mPrefetchMinConfidence;
+ int32_t mPreconnectMinConfidence;
+ int32_t mPreresolveMinConfidence;
+ int32_t mRedirectLikelyConfidence;
+
+ int32_t mPrefetchForceValidFor;
+
+ int32_t mMaxResourcesPerEntry;
+
+ bool mCleanedUp;
+ nsCOMPtr<nsITimer> mCleanupTimer;
+
+ nsTArray<nsCString> mKeysToOperateOn;
+ nsTArray<nsCString> mValuesToOperateOn;
+
+ nsCOMPtr<nsICacheStorage> mCacheDiskStorage;
+
+ nsCOMPtr<nsIIOService> mIOService;
+ nsCOMPtr<nsISpeculativeConnect> mSpeculativeService;
+
+ nsCOMPtr<nsIURI> mStartupURI;
+ uint32_t mStartupTime;
+ uint32_t mLastStartupTime;
+ int32_t mStartupCount;
+
+ uint32_t mMaxURILength;
+
+ nsCOMPtr<nsIDNSService> mDnsService;
+
+ RefPtr<DNSListener> mDNSListener;
+
+ nsTArray<nsCOMPtr<nsIURI>> mPrefetches;
+ nsTArray<nsCOMPtr<nsIURI>> mPreconnects;
+ nsTArray<nsCOMPtr<nsIURI>> mPreresolves;
+
+ bool mDoingTests;
+
+ static Predictor *sSelf;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Predictor_h
diff --git a/netwerk/base/PrivateBrowsingChannel.h b/netwerk/base/PrivateBrowsingChannel.h
new file mode 100644
index 0000000000..10c6645023
--- /dev/null
+++ b/netwerk/base/PrivateBrowsingChannel.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sts=4 sw=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_PrivateBrowsingChannel_h__
+#define mozilla_net_PrivateBrowsingChannel_h__
+
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsCOMPtr.h"
+#include "nsILoadGroup.h"
+#include "nsILoadContext.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsNetUtil.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+template <class Channel>
+class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel
+{
+public:
+ PrivateBrowsingChannel() :
+ mPrivateBrowsingOverriden(false),
+ mPrivateBrowsing(false)
+ {
+ }
+
+ NS_IMETHOD SetPrivate(bool aPrivate)
+ {
+ // Make sure that we don't have a load context
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(static_cast<Channel*>(this), loadContext);
+ MOZ_ASSERT(!loadContext);
+ if (loadContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPrivateBrowsingOverriden = true;
+ mPrivateBrowsing = aPrivate;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetIsChannelPrivate(bool *aResult)
+ {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrivateBrowsing;
+ return NS_OK;
+ }
+
+ NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool *aResult)
+ {
+ NS_ENSURE_ARG_POINTER(aValue);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrivateBrowsingOverriden;
+ if (mPrivateBrowsingOverriden) {
+ *aValue = mPrivateBrowsing;
+ }
+ return NS_OK;
+ }
+
+ // Must be called every time the channel's callbacks or loadGroup is updated
+ void UpdatePrivateBrowsing()
+ {
+ // once marked as private we never go un-private
+ if (mPrivateBrowsing) {
+ return;
+ }
+
+ auto channel = static_cast<Channel*>(this);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(channel, loadContext);
+ if (loadContext) {
+ mPrivateBrowsing = loadContext->UsePrivateBrowsing();
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ Unused << channel->GetLoadInfo(getter_AddRefs(loadInfo));
+ if (loadInfo) {
+ NeckoOriginAttributes attrs = loadInfo->GetOriginAttributes();
+ mPrivateBrowsing = attrs.mPrivateBrowsingId > 0;
+ }
+ }
+
+ bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const
+ {
+ // Make sure that the private bit override flag is not set.
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ if (!aCallbacks) {
+ return true;
+ }
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks);
+ if (!loadContext) {
+ return true;
+ }
+ MOZ_ASSERT(!mPrivateBrowsingOverriden);
+ return !mPrivateBrowsingOverriden;
+ }
+
+ bool CanSetLoadGroup(nsILoadGroup* aLoadGroup) const
+ {
+ // Make sure that the private bit override flag is not set.
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ if (!aLoadGroup) {
+ return true;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ // From this point on, we just hand off the work to CanSetCallbacks,
+ // because the logic is exactly the same.
+ return CanSetCallbacks(callbacks);
+ }
+
+protected:
+ bool mPrivateBrowsingOverriden;
+ bool mPrivateBrowsing;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
+
diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp
new file mode 100644
index 0000000000..4d7a6c1fd5
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -0,0 +1,1015 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProxyAutoConfig.h"
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsThreadUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIURLParser.h"
+#include "nsJSUtils.h"
+#include "jsfriendapi.h"
+#include "prnetdb.h"
+#include "nsITimer.h"
+#include "mozilla/net/DNS.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+
+namespace mozilla {
+namespace net {
+
+// These are some global helper symbols the PAC format requires that we provide that
+// are initialized as part of the global javascript context used for PAC evaluations.
+// Additionally dnsResolve(host) and myIpAddress() are supplied in the same context
+// but are implemented as c++ helpers. alert(msg) is similarly defined.
+
+static const char *sPacUtils =
+ "function dnsDomainIs(host, domain) {\n"
+ " return (host.length >= domain.length &&\n"
+ " host.substring(host.length - domain.length) == domain);\n"
+ "}\n"
+ ""
+ "function dnsDomainLevels(host) {\n"
+ " return host.split('.').length - 1;\n"
+ "}\n"
+ ""
+ "function convert_addr(ipchars) {\n"
+ " var bytes = ipchars.split('.');\n"
+ " var result = ((bytes[0] & 0xff) << 24) |\n"
+ " ((bytes[1] & 0xff) << 16) |\n"
+ " ((bytes[2] & 0xff) << 8) |\n"
+ " (bytes[3] & 0xff);\n"
+ " return result;\n"
+ "}\n"
+ ""
+ "function isInNet(ipaddr, pattern, maskstr) {\n"
+ " var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n"
+ " if (test == null) {\n"
+ " ipaddr = dnsResolve(ipaddr);\n"
+ " if (ipaddr == null)\n"
+ " return false;\n"
+ " } else if (test[1] > 255 || test[2] > 255 || \n"
+ " test[3] > 255 || test[4] > 255) {\n"
+ " return false; // not an IP address\n"
+ " }\n"
+ " var host = convert_addr(ipaddr);\n"
+ " var pat = convert_addr(pattern);\n"
+ " var mask = convert_addr(maskstr);\n"
+ " return ((host & mask) == (pat & mask));\n"
+ " \n"
+ "}\n"
+ ""
+ "function isPlainHostName(host) {\n"
+ " return (host.search('\\\\.') == -1);\n"
+ "}\n"
+ ""
+ "function isResolvable(host) {\n"
+ " var ip = dnsResolve(host);\n"
+ " return (ip != null);\n"
+ "}\n"
+ ""
+ "function localHostOrDomainIs(host, hostdom) {\n"
+ " return (host == hostdom) ||\n"
+ " (hostdom.lastIndexOf(host + '.', 0) == 0);\n"
+ "}\n"
+ ""
+ "function shExpMatch(url, pattern) {\n"
+ " pattern = pattern.replace(/\\./g, '\\\\.');\n"
+ " pattern = pattern.replace(/\\*/g, '.*');\n"
+ " pattern = pattern.replace(/\\?/g, '.');\n"
+ " var newRe = new RegExp('^'+pattern+'$');\n"
+ " return newRe.test(url);\n"
+ "}\n"
+ ""
+ "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n"
+ "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n"
+ ""
+ "function weekdayRange() {\n"
+ " function getDay(weekday) {\n"
+ " if (weekday in wdays) {\n"
+ " return wdays[weekday];\n"
+ " }\n"
+ " return -1;\n"
+ " }\n"
+ " var date = new Date();\n"
+ " var argc = arguments.length;\n"
+ " var wday;\n"
+ " if (argc < 1)\n"
+ " return false;\n"
+ " if (arguments[argc - 1] == 'GMT') {\n"
+ " argc--;\n"
+ " wday = date.getUTCDay();\n"
+ " } else {\n"
+ " wday = date.getDay();\n"
+ " }\n"
+ " var wd1 = getDay(arguments[0]);\n"
+ " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n"
+ " return (wd1 == -1 || wd2 == -1) ? false\n"
+ " : (wd1 <= wd2) ? (wd1 <= wday && wday <= wd2)\n"
+ " : (wd2 >= wday || wday >= wd1);\n"
+ "}\n"
+ ""
+ "function dateRange() {\n"
+ " function getMonth(name) {\n"
+ " if (name in months) {\n"
+ " return months[name];\n"
+ " }\n"
+ " return -1;\n"
+ " }\n"
+ " var date = new Date();\n"
+ " var argc = arguments.length;\n"
+ " if (argc < 1) {\n"
+ " return false;\n"
+ " }\n"
+ " var isGMT = (arguments[argc - 1] == 'GMT');\n"
+ "\n"
+ " if (isGMT) {\n"
+ " argc--;\n"
+ " }\n"
+ " // function will work even without explict handling of this case\n"
+ " if (argc == 1) {\n"
+ " var tmp = parseInt(arguments[0]);\n"
+ " if (isNaN(tmp)) {\n"
+ " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n"
+ " getMonth(arguments[0]));\n"
+ " } else if (tmp < 32) {\n"
+ " return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n"
+ " } else { \n"
+ " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n"
+ " tmp);\n"
+ " }\n"
+ " }\n"
+ " var year = date.getFullYear();\n"
+ " var date1, date2;\n"
+ " date1 = new Date(year, 0, 1, 0, 0, 0);\n"
+ " date2 = new Date(year, 11, 31, 23, 59, 59);\n"
+ " var adjustMonth = false;\n"
+ " for (var i = 0; i < (argc >> 1); i++) {\n"
+ " var tmp = parseInt(arguments[i]);\n"
+ " if (isNaN(tmp)) {\n"
+ " var mon = getMonth(arguments[i]);\n"
+ " date1.setMonth(mon);\n"
+ " } else if (tmp < 32) {\n"
+ " adjustMonth = (argc <= 2);\n"
+ " date1.setDate(tmp);\n"
+ " } else {\n"
+ " date1.setFullYear(tmp);\n"
+ " }\n"
+ " }\n"
+ " for (var i = (argc >> 1); i < argc; i++) {\n"
+ " var tmp = parseInt(arguments[i]);\n"
+ " if (isNaN(tmp)) {\n"
+ " var mon = getMonth(arguments[i]);\n"
+ " date2.setMonth(mon);\n"
+ " } else if (tmp < 32) {\n"
+ " date2.setDate(tmp);\n"
+ " } else {\n"
+ " date2.setFullYear(tmp);\n"
+ " }\n"
+ " }\n"
+ " if (adjustMonth) {\n"
+ " date1.setMonth(date.getMonth());\n"
+ " date2.setMonth(date.getMonth());\n"
+ " }\n"
+ " if (isGMT) {\n"
+ " var tmp = date;\n"
+ " tmp.setFullYear(date.getUTCFullYear());\n"
+ " tmp.setMonth(date.getUTCMonth());\n"
+ " tmp.setDate(date.getUTCDate());\n"
+ " tmp.setHours(date.getUTCHours());\n"
+ " tmp.setMinutes(date.getUTCMinutes());\n"
+ " tmp.setSeconds(date.getUTCSeconds());\n"
+ " date = tmp;\n"
+ " }\n"
+ " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n"
+ " : (date2 >= date) || (date >= date1);\n"
+ "}\n"
+ ""
+ "function timeRange() {\n"
+ " var argc = arguments.length;\n"
+ " var date = new Date();\n"
+ " var isGMT= false;\n"
+ ""
+ " if (argc < 1) {\n"
+ " return false;\n"
+ " }\n"
+ " if (arguments[argc - 1] == 'GMT') {\n"
+ " isGMT = true;\n"
+ " argc--;\n"
+ " }\n"
+ "\n"
+ " var hour = isGMT ? date.getUTCHours() : date.getHours();\n"
+ " var date1, date2;\n"
+ " date1 = new Date();\n"
+ " date2 = new Date();\n"
+ "\n"
+ " if (argc == 1) {\n"
+ " return (hour == arguments[0]);\n"
+ " } else if (argc == 2) {\n"
+ " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n"
+ " } else {\n"
+ " switch (argc) {\n"
+ " case 6:\n"
+ " date1.setSeconds(arguments[2]);\n"
+ " date2.setSeconds(arguments[5]);\n"
+ " case 4:\n"
+ " var middle = argc >> 1;\n"
+ " date1.setHours(arguments[0]);\n"
+ " date1.setMinutes(arguments[1]);\n"
+ " date2.setHours(arguments[middle]);\n"
+ " date2.setMinutes(arguments[middle + 1]);\n"
+ " if (middle == 2) {\n"
+ " date2.setSeconds(59);\n"
+ " }\n"
+ " break;\n"
+ " default:\n"
+ " throw 'timeRange: bad number of arguments'\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (isGMT) {\n"
+ " date.setFullYear(date.getUTCFullYear());\n"
+ " date.setMonth(date.getUTCMonth());\n"
+ " date.setDate(date.getUTCDate());\n"
+ " date.setHours(date.getUTCHours());\n"
+ " date.setMinutes(date.getUTCMinutes());\n"
+ " date.setSeconds(date.getUTCSeconds());\n"
+ " }\n"
+ " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n"
+ " : (date2 >= date) || (date >= date1);\n"
+ "\n"
+ "}\n"
+ "";
+
+// sRunning is defined for the helper functions only while the
+// Javascript engine is running and the PAC object cannot be deleted
+// or reset.
+static uint32_t sRunningIndex = 0xdeadbeef;
+static ProxyAutoConfig *GetRunning()
+{
+ MOZ_ASSERT(sRunningIndex != 0xdeadbeef);
+ return static_cast<ProxyAutoConfig *>(PR_GetThreadPrivate(sRunningIndex));
+}
+
+static void SetRunning(ProxyAutoConfig *arg)
+{
+ MOZ_ASSERT(sRunningIndex != 0xdeadbeef);
+ PR_SetThreadPrivate(sRunningIndex, arg);
+}
+
+// The PACResolver is used for dnsResolve()
+class PACResolver final : public nsIDNSListener
+ , public nsITimerCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ PACResolver()
+ : mStatus(NS_ERROR_FAILURE)
+ {
+ }
+
+ // nsIDNSListener
+ NS_IMETHOD OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *record,
+ nsresult status) override
+ {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ mRequest = nullptr;
+ mStatus = status;
+ mResponse = record;
+ return NS_OK;
+ }
+
+ // nsITimerCallback
+ NS_IMETHOD Notify(nsITimer *timer) override
+ {
+ if (mRequest)
+ mRequest->Cancel(NS_ERROR_NET_TIMEOUT);
+ mTimer = nullptr;
+ return NS_OK;
+ }
+
+ nsresult mStatus;
+ nsCOMPtr<nsICancelable> mRequest;
+ nsCOMPtr<nsIDNSRecord> mResponse;
+ nsCOMPtr<nsITimer> mTimer;
+
+private:
+ ~PACResolver() {}
+};
+NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback)
+
+static
+void PACLogToConsole(nsString &aMessage)
+{
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!consoleService)
+ return;
+
+ consoleService->LogStringMessage(aMessage.get());
+}
+
+// Javascript errors and warnings are logged to the main error console
+static void
+PACLogErrorOrWarning(const nsAString& aKind, JSErrorReport* aReport)
+{
+ nsString formattedMessage(NS_LITERAL_STRING("PAC Execution "));
+ formattedMessage += aKind;
+ formattedMessage += NS_LITERAL_STRING(": ");
+ if (aReport->message())
+ formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str()));
+ formattedMessage += NS_LITERAL_STRING(" [");
+ formattedMessage.Append(aReport->linebuf(), aReport->linebufLength());
+ formattedMessage += NS_LITERAL_STRING("]");
+ PACLogToConsole(formattedMessage);
+}
+
+static void
+PACWarningReporter(JSContext* aCx, JSErrorReport* aReport)
+{
+ MOZ_ASSERT(aReport);
+ MOZ_ASSERT(JSREPORT_IS_WARNING(aReport->flags));
+
+ PACLogErrorOrWarning(NS_LITERAL_STRING("Warning"), aReport);
+}
+
+class MOZ_STACK_CLASS AutoPACErrorReporter
+{
+ JSContext* mCx;
+
+public:
+ explicit AutoPACErrorReporter(JSContext* aCx)
+ : mCx(aCx)
+ {}
+ ~AutoPACErrorReporter() {
+ if (!JS_IsExceptionPending(mCx)) {
+ return;
+ }
+ JS::RootedValue exn(mCx);
+ if (!JS_GetPendingException(mCx, &exn)) {
+ return;
+ }
+ JS_ClearPendingException(mCx);
+
+ js::ErrorReport report(mCx);
+ if (!report.init(mCx, exn, js::ErrorReport::WithSideEffects)) {
+ JS_ClearPendingException(mCx);
+ return;
+ }
+
+ PACLogErrorOrWarning(NS_LITERAL_STRING("Error"), report.report());
+ }
+};
+
+// timeout of 0 means the normal necko timeout strategy, otherwise the dns request
+// will be canceled after aTimeout milliseconds
+static
+bool PACResolve(const nsCString &aHostName, NetAddr *aNetAddr,
+ unsigned int aTimeout)
+{
+ if (!GetRunning()) {
+ NS_WARNING("PACResolve without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout);
+}
+
+ProxyAutoConfig::ProxyAutoConfig()
+ : mJSContext(nullptr)
+ , mJSNeedsSetup(false)
+ , mShutdown(false)
+ , mIncludePath(false)
+{
+ MOZ_COUNT_CTOR(ProxyAutoConfig);
+}
+
+bool
+ProxyAutoConfig::ResolveAddress(const nsCString &aHostName,
+ NetAddr *aNetAddr,
+ unsigned int aTimeout)
+{
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns)
+ return false;
+
+ RefPtr<PACResolver> helper = new PACResolver();
+
+ if (NS_FAILED(dns->AsyncResolve(aHostName,
+ nsIDNSService::RESOLVE_PRIORITY_MEDIUM,
+ helper,
+ NS_GetCurrentThread(),
+ getter_AddRefs(helper->mRequest))))
+ return false;
+
+ if (aTimeout && helper->mRequest) {
+ if (!mTimer)
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (mTimer) {
+ mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT);
+ helper->mTimer = mTimer;
+ }
+ }
+
+ // Spin the event loop of the pac thread until lookup is complete.
+ // nsPACman is responsible for keeping a queue and only allowing
+ // one PAC execution at a time even when it is called re-entrantly.
+ while (helper->mRequest)
+ NS_ProcessNextEvent(NS_GetCurrentThread());
+
+ if (NS_FAILED(helper->mStatus) ||
+ NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr)))
+ return false;
+ return true;
+}
+
+static
+bool PACResolveToString(const nsCString &aHostName,
+ nsCString &aDottedDecimal,
+ unsigned int aTimeout)
+{
+ NetAddr netAddr;
+ if (!PACResolve(aHostName, &netAddr, aTimeout))
+ return false;
+
+ char dottedDecimal[128];
+ if (!NetAddrToString(&netAddr, dottedDecimal, sizeof(dottedDecimal)))
+ return false;
+
+ aDottedDecimal.Assign(dottedDecimal);
+ return true;
+}
+
+// dnsResolve(host) javascript implementation
+static
+bool PACDnsResolve(JSContext *cx, unsigned int argc, JS::Value *vp)
+{
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!args.requireAtLeast(cx, "dnsResolve", 1))
+ return false;
+
+ JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0]));
+ if (!arg1)
+ return false;
+
+ nsAutoJSString hostName;
+ nsAutoCString dottedDecimal;
+
+ if (!hostName.init(cx, arg1))
+ return false;
+ if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) {
+ JSString *dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ args.rval().setString(dottedDecimalString);
+ }
+ else {
+ args.rval().setNull();
+ }
+
+ return true;
+}
+
+// myIpAddress() javascript implementation
+static
+bool PACMyIpAddress(JSContext *cx, unsigned int argc, JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!GetRunning()) {
+ NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->MyIPAddress(args);
+}
+
+// proxyAlert(msg) javascript implementation
+static
+bool PACProxyAlert(JSContext *cx, unsigned int argc, JS::Value *vp)
+{
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "alert", 1))
+ return false;
+
+ JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0]));
+ if (!arg1)
+ return false;
+
+ nsAutoJSString message;
+ if (!message.init(cx, arg1))
+ return false;
+
+ nsAutoString alertMessage;
+ alertMessage.SetCapacity(32 + message.Length());
+ alertMessage += NS_LITERAL_STRING("PAC-alert: ");
+ alertMessage += message;
+ PACLogToConsole(alertMessage);
+
+ args.rval().setUndefined(); /* return undefined */
+ return true;
+}
+
+static const JSFunctionSpec PACGlobalFunctions[] = {
+ JS_FS("dnsResolve", PACDnsResolve, 1, 0),
+
+ // a global "var pacUseMultihomedDNS = true;" will change behavior
+ // of myIpAddress to actively use DNS
+ JS_FS("myIpAddress", PACMyIpAddress, 0, 0),
+ JS_FS("alert", PACProxyAlert, 1, 0),
+ JS_FS_END
+};
+
+// JSContextWrapper is a c++ object that manages the context for the JS engine
+// used on the PAC thread. It is initialized and destroyed on the PAC thread.
+class JSContextWrapper
+{
+ public:
+ static JSContextWrapper *Create()
+ {
+ JSContext* cx = JS_NewContext(sContextHeapSize);
+ if (NS_WARN_IF(!cx))
+ return nullptr;
+
+ JSContextWrapper *entry = new JSContextWrapper(cx);
+ if (NS_FAILED(entry->Init())) {
+ delete entry;
+ return nullptr;
+ }
+
+ return entry;
+ }
+
+ JSContext *Context() const
+ {
+ return mContext;
+ }
+
+ JSObject *Global() const
+ {
+ return mGlobal;
+ }
+
+ ~JSContextWrapper()
+ {
+ mGlobal = nullptr;
+
+ MOZ_COUNT_DTOR(JSContextWrapper);
+
+ if (mContext) {
+ JS_DestroyContext(mContext);
+ }
+ }
+
+ void SetOK()
+ {
+ mOK = true;
+ }
+
+ bool IsOK()
+ {
+ return mOK;
+ }
+
+private:
+ static const unsigned sContextHeapSize = 4 << 20; // 4 MB
+
+ JSContext *mContext;
+ JS::PersistentRooted<JSObject*> mGlobal;
+ bool mOK;
+
+ static const JSClass sGlobalClass;
+
+ explicit JSContextWrapper(JSContext* cx)
+ : mContext(cx), mGlobal(cx, nullptr), mOK(false)
+ {
+ MOZ_COUNT_CTOR(JSContextWrapper);
+ }
+
+ nsresult Init()
+ {
+ /*
+ * Not setting this will cause JS_CHECK_RECURSION to report false
+ * positives
+ */
+ JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024);
+
+ JS::SetWarningReporter(mContext, PACWarningReporter);
+
+ if (!JS::InitSelfHostedCode(mContext)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ JSAutoRequest ar(mContext);
+
+ JS::CompartmentOptions options;
+ options.creationOptions().setZone(JS::SystemZone);
+ options.behaviors().setVersion(JSVERSION_LATEST);
+ mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
+ JS::DontFireOnNewGlobalHook, options);
+ if (!mGlobal) {
+ JS_ClearPendingException(mContext);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ JS::Rooted<JSObject*> global(mContext, mGlobal);
+
+ JSAutoCompartment ac(mContext, global);
+ AutoPACErrorReporter aper(mContext);
+ if (!JS_InitStandardClasses(mContext, global)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS_FireOnNewGlobalObject(mContext, global);
+
+ return NS_OK;
+ }
+};
+
+static const JSClassOps sJSContextWrapperGlobalClassOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ JS_GlobalObjectTraceHook
+};
+
+const JSClass JSContextWrapper::sGlobalClass = {
+ "PACResolutionThreadGlobal",
+ JSCLASS_GLOBAL_FLAGS,
+ &sJSContextWrapperGlobalClassOps
+};
+
+void
+ProxyAutoConfig::SetThreadLocalIndex(uint32_t index)
+{
+ sRunningIndex = index;
+}
+
+nsresult
+ProxyAutoConfig::Init(const nsCString &aPACURI,
+ const nsCString &aPACScript,
+ bool aIncludePath)
+{
+ mPACURI = aPACURI;
+ mPACScript = sPacUtils;
+ mPACScript.Append(aPACScript);
+ mIncludePath = aIncludePath;
+
+ if (!GetRunning())
+ return SetupJS();
+
+ mJSNeedsSetup = true;
+ return NS_OK;
+}
+
+nsresult
+ProxyAutoConfig::SetupJS()
+{
+ mJSNeedsSetup = false;
+ MOZ_ASSERT(!GetRunning(), "JIT is running");
+
+ delete mJSContext;
+ mJSContext = nullptr;
+
+ if (mPACScript.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ NS_GetCurrentThread()->SetCanInvokeJS(true);
+
+ mJSContext = JSContextWrapper::Create();
+ if (!mJSContext)
+ return NS_ERROR_FAILURE;
+
+ JSContext* cx = mJSContext->Context();
+ JSAutoRequest ar(cx);
+ JSAutoCompartment ac(cx, mJSContext->Global());
+ AutoPACErrorReporter aper(cx);
+
+ // check if this is a data: uri so that we don't spam the js console with
+ // huge meaningless strings. this is not on the main thread, so it can't
+ // use nsIURI scheme methods
+ bool isDataURI = nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5);
+
+ SetRunning(this);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(mPACURI.get(), 1);
+ JS::Rooted<JSScript*> script(cx);
+ if (!JS_CompileScript(cx, mPACScript.get(), mPACScript.Length(), options,
+ &script) ||
+ !JS_ExecuteScript(cx, script))
+ {
+ nsString alertMessage(NS_LITERAL_STRING("PAC file failed to install from "));
+ if (isDataURI) {
+ alertMessage += NS_LITERAL_STRING("data: URI");
+ }
+ else {
+ alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
+ }
+ PACLogToConsole(alertMessage);
+ SetRunning(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ SetRunning(nullptr);
+
+ mJSContext->SetOK();
+ nsString alertMessage(NS_LITERAL_STRING("PAC file installed from "));
+ if (isDataURI) {
+ alertMessage += NS_LITERAL_STRING("data: URI");
+ }
+ else {
+ alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
+ }
+ PACLogToConsole(alertMessage);
+
+ // we don't need these now
+ mPACScript.Truncate();
+ mPACURI.Truncate();
+
+ return NS_OK;
+}
+
+nsresult
+ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI,
+ const nsCString &aTestHost,
+ nsACString &result)
+{
+ if (mJSNeedsSetup)
+ SetupJS();
+
+ if (!mJSContext || !mJSContext->IsOK())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ JSContext *cx = mJSContext->Context();
+ JSAutoRequest ar(cx);
+ JSAutoCompartment ac(cx, mJSContext->Global());
+ AutoPACErrorReporter aper(cx);
+
+ // the sRunning flag keeps a new PAC file from being installed
+ // while the event loop is spinning on a DNS function. Don't early return.
+ SetRunning(this);
+ mRunningHost = aTestHost;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCString clensedURI = aTestURI;
+
+ if (!mIncludePath) {
+ nsCOMPtr<nsIURLParser> urlParser =
+ do_GetService(NS_STDURLPARSER_CONTRACTID);
+ int32_t pathLen = 0;
+ if (urlParser) {
+ uint32_t schemePos;
+ int32_t schemeLen;
+ uint32_t authorityPos;
+ int32_t authorityLen;
+ uint32_t pathPos;
+ rv = urlParser->ParseURL(aTestURI.get(), aTestURI.Length(),
+ &schemePos, &schemeLen,
+ &authorityPos, &authorityLen,
+ &pathPos, &pathLen);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ if (pathLen) {
+ // cut off the path but leave the initial slash
+ pathLen--;
+ }
+ aTestURI.Left(clensedURI, aTestURI.Length() - pathLen);
+ }
+ }
+
+ JS::RootedString uriString(cx, JS_NewStringCopyZ(cx, clensedURI.get()));
+ JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get()));
+
+ if (uriString && hostString) {
+ JS::AutoValueArray<2> args(cx);
+ args[0].setString(uriString);
+ args[1].setString(hostString);
+
+ JS::Rooted<JS::Value> rval(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+ bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval);
+
+ if (ok && rval.isString()) {
+ nsAutoJSString pacString;
+ if (pacString.init(cx, rval.toString())) {
+ CopyUTF16toUTF8(pacString, result);
+ rv = NS_OK;
+ }
+ }
+ }
+
+ mRunningHost.Truncate();
+ SetRunning(nullptr);
+ return rv;
+}
+
+void
+ProxyAutoConfig::GC()
+{
+ if (!mJSContext || !mJSContext->IsOK())
+ return;
+
+ JSAutoCompartment ac(mJSContext->Context(), mJSContext->Global());
+ JS_MaybeGC(mJSContext->Context());
+}
+
+ProxyAutoConfig::~ProxyAutoConfig()
+{
+ MOZ_COUNT_DTOR(ProxyAutoConfig);
+ NS_ASSERTION(!mJSContext,
+ "~ProxyAutoConfig leaking JS context that "
+ "should have been deleted on pac thread");
+}
+
+void
+ProxyAutoConfig::Shutdown()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown");
+
+ if (GetRunning() || mShutdown)
+ return;
+
+ mShutdown = true;
+ delete mJSContext;
+ mJSContext = nullptr;
+}
+
+bool
+ProxyAutoConfig::SrcAddress(const NetAddr *remoteAddress, nsCString &localAddress)
+{
+ PRFileDesc *fd;
+ fd = PR_OpenUDPSocket(remoteAddress->raw.family);
+ if (!fd)
+ return false;
+
+ PRNetAddr prRemoteAddress;
+ NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress);
+ if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) {
+ PR_Close(fd);
+ return false;
+ }
+
+ PRNetAddr localName;
+ if (PR_GetSockName(fd, &localName) != PR_SUCCESS) {
+ PR_Close(fd);
+ return false;
+ }
+
+ PR_Close(fd);
+
+ char dottedDecimal[128];
+ if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != PR_SUCCESS)
+ return false;
+
+ localAddress.Assign(dottedDecimal);
+
+ return true;
+}
+
+// hostName is run through a dns lookup and then a udp socket is connected
+// to the result. If that all works, the local IP address of the socket is
+// returned to the javascript caller and |*aResult| is set to true. Otherwise
+// |*aResult| is set to false.
+bool
+ProxyAutoConfig::MyIPAddressTryHost(const nsCString &hostName,
+ unsigned int timeout,
+ const JS::CallArgs &aArgs,
+ bool* aResult)
+{
+ *aResult = false;
+
+ NetAddr remoteAddress;
+ nsAutoCString localDottedDecimal;
+ JSContext *cx = mJSContext->Context();
+
+ if (PACResolve(hostName, &remoteAddress, timeout) &&
+ SrcAddress(&remoteAddress, localDottedDecimal)) {
+ JSString *dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ *aResult = true;
+ aArgs.rval().setString(dottedDecimalString);
+ }
+ return true;
+}
+
+bool
+ProxyAutoConfig::MyIPAddress(const JS::CallArgs &aArgs)
+{
+ nsAutoCString remoteDottedDecimal;
+ nsAutoCString localDottedDecimal;
+ JSContext *cx = mJSContext->Context();
+ JS::RootedValue v(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+
+ bool useMultihomedDNS =
+ JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) &&
+ !v.isUndefined() && ToBoolean(v);
+
+ // first, lookup the local address of a socket connected
+ // to the host of uri being resolved by the pac file. This is
+ // v6 safe.. but is the last step like that
+ bool rvalAssigned = false;
+ if (useMultihomedDNS) {
+ if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+ } else {
+ // we can still do the fancy multi homing thing if the host is a literal
+ PRNetAddr tempAddr;
+ memset(&tempAddr, 0, sizeof(PRNetAddr));
+ if ((PR_StringToNetAddr(mRunningHost.get(), &tempAddr) == PR_SUCCESS) &&
+ (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned)) {
+ return rvalAssigned;
+ }
+ }
+
+ // next, look for a route to a public internet address that doesn't need DNS.
+ // This is the google anycast dns address, but it doesn't matter if it
+ // remains operable (as we don't contact it) as long as the address stays
+ // in commonly routed IP address space.
+ remoteDottedDecimal.AssignLiteral("8.8.8.8");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // finally, use the old algorithm based on the local hostname
+ nsAutoCString hostName;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ // without multihomedDNS use such a short timeout that we are basically
+ // just looking at the cache for raw dotted decimals
+ uint32_t timeout = useMultihomedDNS ? kTimeout : 1;
+ if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) &&
+ PACResolveToString(hostName, localDottedDecimal, timeout)) {
+ JSString *dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+ }
+
+ // next try a couple RFC 1918 variants.. maybe there is a
+ // local route
+ remoteDottedDecimal.AssignLiteral("192.168.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // more RFC 1918
+ remoteDottedDecimal.AssignLiteral("10.0.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // who knows? let's fallback to localhost
+ localDottedDecimal.AssignLiteral("127.0.0.1");
+ JSString *dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h
new file mode 100644
index 0000000000..1f7b88ac6a
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ProxyAutoConfig_h__
+#define ProxyAutoConfig_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsITimer;
+namespace JS {
+class CallArgs;
+} // namespace JS
+
+namespace mozilla { namespace net {
+
+class JSContextWrapper;
+union NetAddr;
+
+// The ProxyAutoConfig class is meant to be created and run on a
+// non main thread. It synchronously resolves PAC files by blocking that
+// thread and running nested event loops. GetProxyForURI is not re-entrant.
+
+class ProxyAutoConfig {
+public:
+ ProxyAutoConfig();
+ ~ProxyAutoConfig();
+
+ nsresult Init(const nsCString &aPACURI,
+ const nsCString &aPACScript,
+ bool aIncludePath);
+ void SetThreadLocalIndex(uint32_t index);
+ void Shutdown();
+ void GC();
+ bool MyIPAddress(const JS::CallArgs &aArgs);
+ bool ResolveAddress(const nsCString &aHostName,
+ NetAddr *aNetAddr, unsigned int aTimeout);
+
+ /**
+ * Get the proxy string for the specified URI. The proxy string is
+ * given by the following:
+ *
+ * result = proxy-spec *( proxy-sep proxy-spec )
+ * proxy-spec = direct-type | proxy-type LWS proxy-host [":" proxy-port]
+ * direct-type = "DIRECT"
+ * proxy-type = "PROXY" | "HTTP" | "HTTPS" | "SOCKS" | "SOCKS4" | "SOCKS5"
+ * proxy-sep = ";" LWS
+ * proxy-host = hostname | ipv4-address-literal
+ * proxy-port = <any 16-bit unsigned integer>
+ * LWS = *( SP | HT )
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ *
+ * NOTE: direct-type and proxy-type are case insensitive
+ * NOTE: SOCKS implies SOCKS4
+ *
+ * Examples:
+ * "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT"
+ * "SOCKS socksproxy"
+ * "DIRECT"
+ *
+ * XXX add support for IPv6 address literals.
+ * XXX quote whatever the official standard is for PAC.
+ *
+ * @param aTestURI
+ * The URI as an ASCII string to test.
+ * @param aTestHost
+ * The ASCII hostname to test.
+ *
+ * @param result
+ * result string as defined above.
+ */
+ nsresult GetProxyForURI(const nsCString &aTestURI,
+ const nsCString &aTestHost,
+ nsACString &result);
+
+private:
+ // allow 665ms for myipaddress dns queries. That's 95th percentile.
+ const static unsigned int kTimeout = 665;
+
+ // used to compile the PAC file and setup the execution context
+ nsresult SetupJS();
+
+ bool SrcAddress(const NetAddr *remoteAddress, nsCString &localAddress);
+ bool MyIPAddressTryHost(const nsCString &hostName, unsigned int timeout,
+ const JS::CallArgs &aArgs, bool* aResult);
+
+ JSContextWrapper *mJSContext;
+ bool mJSNeedsSetup;
+ bool mShutdown;
+ nsCString mPACScript;
+ nsCString mPACURI;
+ bool mIncludePath;
+ nsCString mRunningHost;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ProxyAutoConfig_h__
diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp
new file mode 100644
index 0000000000..17e26603a9
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.cpp
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RedirectChannelRegistrar.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar)
+
+RedirectChannelRegistrar::RedirectChannelRegistrar()
+ : mRealChannels(32)
+ , mParentChannels(32)
+ , mId(1)
+ , mLock("RedirectChannelRegistrar")
+{
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::RegisterChannel(nsIChannel *channel,
+ uint32_t *_retval)
+{
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.Put(mId, channel);
+ *_retval = mId;
+
+ ++mId;
+
+ // Ensure we always provide positive ids
+ if (!mId)
+ mId = 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::GetRegisteredChannel(uint32_t id,
+ nsIChannel **_retval)
+{
+ MutexAutoLock lock(mLock);
+
+ if (!mRealChannels.Get(id, _retval))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::LinkChannels(uint32_t id,
+ nsIParentChannel *channel,
+ nsIChannel** _retval)
+{
+ MutexAutoLock lock(mLock);
+
+ if (!mRealChannels.Get(id, _retval))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mParentChannels.Put(id, channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::GetParentChannel(uint32_t id,
+ nsIParentChannel **_retval)
+{
+ MutexAutoLock lock(mLock);
+
+ if (!mParentChannels.Get(id, _retval))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::DeregisterChannels(uint32_t id)
+{
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.Remove(id);
+ mParentChannels.Remove(id);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/RedirectChannelRegistrar.h b/netwerk/base/RedirectChannelRegistrar.h
new file mode 100644
index 0000000000..46e46c264a
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.h
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RedirectChannelRegistrar_h__
+#define RedirectChannelRegistrar_h__
+
+#include "nsIRedirectChannelRegistrar.h"
+
+#include "nsIChannel.h"
+#include "nsIParentChannel.h"
+#include "nsInterfaceHashtable.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREDIRECTCHANNELREGISTRAR
+
+ RedirectChannelRegistrar();
+
+private:
+ ~RedirectChannelRegistrar() {}
+
+protected:
+ typedef nsInterfaceHashtable<nsUint32HashKey, nsIChannel>
+ ChannelHashtable;
+ typedef nsInterfaceHashtable<nsUint32HashKey, nsIParentChannel>
+ ParentChannelHashtable;
+
+ ChannelHashtable mRealChannels;
+ ParentChannelHashtable mParentChannels;
+ uint32_t mId;
+ Mutex mLock;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ReferrerPolicy.h b/netwerk/base/ReferrerPolicy.h
new file mode 100644
index 0000000000..591b9daf03
--- /dev/null
+++ b/netwerk/base/ReferrerPolicy.h
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ReferrerPolicy_h__
+#define ReferrerPolicy_h__
+
+#include "nsStringGlue.h"
+#include "nsIHttpChannel.h"
+#include "nsUnicharUtils.h"
+
+namespace mozilla { namespace net {
+
+enum ReferrerPolicy {
+ /* spec tokens: never no-referrer */
+ RP_No_Referrer = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER,
+
+ /* spec tokens: origin */
+ RP_Origin = nsIHttpChannel::REFERRER_POLICY_ORIGIN,
+
+ /* spec tokens: default no-referrer-when-downgrade */
+ RP_No_Referrer_When_Downgrade = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
+ RP_Default = nsIHttpChannel::REFERRER_POLICY_DEFAULT,
+
+ /* spec tokens: origin-when-cross-origin */
+ RP_Origin_When_Crossorigin = nsIHttpChannel::REFERRER_POLICY_ORIGIN_WHEN_XORIGIN,
+
+ /* spec tokens: always unsafe-url */
+ RP_Unsafe_URL = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL,
+
+ /* spec tokens: same-origin */
+ RP_Same_Origin = nsIHttpChannel::REFERRER_POLICY_SAME_ORIGIN,
+
+ /* spec tokens: strict-origin */
+ RP_Strict_Origin = nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN,
+
+ /* spec tokens: strict-origin-when-cross-origin */
+ RP_Strict_Origin_When_Cross_Origin = nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN,
+
+ /* spec tokens: empty string */
+ /* The empty string "" corresponds to no referrer policy, or unset policy */
+ RP_Unset = nsIHttpChannel::REFERRER_POLICY_UNSET,
+};
+
+/* spec tokens: never no-referrer */
+const char kRPS_Never[] = "never";
+const char kRPS_No_Referrer[] = "no-referrer";
+
+/* spec tokens: origin */
+const char kRPS_Origin[] = "origin";
+
+/* spec tokens: default no-referrer-when-downgrade */
+const char kRPS_Default[] = "default";
+const char kRPS_No_Referrer_When_Downgrade[] = "no-referrer-when-downgrade";
+
+/* spec tokens: origin-when-cross-origin */
+const char kRPS_Origin_When_Cross_Origin[] = "origin-when-cross-origin";
+const char kRPS_Origin_When_Crossorigin[] = "origin-when-crossorigin";
+
+/* spec tokens: same-origin */
+const char kRPS_Same_Origin[] = "same-origin";
+
+/* spec tokens: strict-origin */
+const char kRPS_Strict_Origin[] = "strict-origin";
+
+/* spec tokens: strict-origin-when-cross-origin */
+const char kRPS_Strict_Origin_When_Cross_Origin[] = "strict-origin-when-cross-origin";
+
+/* spec tokens: always unsafe-url */
+const char kRPS_Always[] = "always";
+const char kRPS_Unsafe_URL[] = "unsafe-url";
+
+inline ReferrerPolicy
+ReferrerPolicyFromString(const nsAString& content)
+{
+ if (content.IsEmpty()) {
+ return RP_No_Referrer;
+ }
+
+ nsString lowerContent(content);
+ ToLowerCase(lowerContent);
+ // This is implemented step by step as described in the Referrer Policy
+ // specification, section "Determine token's Policy".
+ if (lowerContent.EqualsLiteral(kRPS_Never) ||
+ lowerContent.EqualsLiteral(kRPS_No_Referrer)) {
+ return RP_No_Referrer;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Origin)) {
+ return RP_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Default) ||
+ lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)) {
+ return RP_No_Referrer_When_Downgrade;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin) ||
+ lowerContent.EqualsLiteral(kRPS_Origin_When_Crossorigin)) {
+ return RP_Origin_When_Crossorigin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Same_Origin)) {
+ return RP_Same_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Strict_Origin)) {
+ return RP_Strict_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)) {
+ return RP_Strict_Origin_When_Cross_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Always) ||
+ lowerContent.EqualsLiteral(kRPS_Unsafe_URL)) {
+ return RP_Unsafe_URL;
+ }
+ // Spec says if none of the previous match, use empty string.
+ return RP_Unset;
+
+}
+
+inline bool
+IsValidReferrerPolicy(const nsAString& content)
+{
+ if (content.IsEmpty()) {
+ return true;
+ }
+
+ nsString lowerContent(content);
+ ToLowerCase(lowerContent);
+
+ return lowerContent.EqualsLiteral(kRPS_Never)
+ || lowerContent.EqualsLiteral(kRPS_No_Referrer)
+ || lowerContent.EqualsLiteral(kRPS_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Default)
+ || lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)
+ || lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Origin_When_Crossorigin)
+ || lowerContent.EqualsLiteral(kRPS_Same_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Strict_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Always)
+ || lowerContent.EqualsLiteral(kRPS_Unsafe_URL);
+}
+
+inline ReferrerPolicy
+AttributeReferrerPolicyFromString(const nsAString& content)
+{
+ // Specs : https://html.spec.whatwg.org/multipage/infrastructure.html#referrer-policy-attribute
+ // Spec says the empty string "" corresponds to no referrer policy, or RP_Unset
+ if (content.IsEmpty()) {
+ return RP_Unset;
+ }
+
+ nsString lowerContent(content);
+ ToLowerCase(lowerContent);
+
+ if (lowerContent.EqualsLiteral(kRPS_No_Referrer)) {
+ return RP_No_Referrer;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Origin)) {
+ return RP_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)) {
+ return RP_No_Referrer_When_Downgrade;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin)) {
+ return RP_Origin_When_Crossorigin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Unsafe_URL)) {
+ return RP_Unsafe_URL;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Strict_Origin)) {
+ return RP_Strict_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Same_Origin)) {
+ return RP_Same_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)) {
+ return RP_Strict_Origin_When_Cross_Origin;
+ }
+
+ // Spec says invalid value default is empty string state
+ // So, return RP_Unset if none of the previous match, return RP_Unset
+ return RP_Unset;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp
new file mode 100644
index 0000000000..b72e42b139
--- /dev/null
+++ b/netwerk/base/RequestContextService.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAutoPtr.h"
+#include "nsIObserverService.h"
+#include "nsIUUIDGenerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "RequestContextService.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Services.h"
+
+#include "mozilla/net/PSpdyPush.h"
+
+namespace mozilla {
+namespace net {
+
+// nsIRequestContext
+class RequestContext final : public nsIRequestContext
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXT
+
+ explicit RequestContext(const nsID& id);
+private:
+ virtual ~RequestContext();
+
+ nsID mID;
+ char mCID[NSID_LENGTH];
+ Atomic<uint32_t> mBlockingTransactionCount;
+ nsAutoPtr<SpdyPushCache> mSpdyCache;
+ nsCString mUserAgentOverride;
+};
+
+NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext)
+
+RequestContext::RequestContext(const nsID& aID)
+ : mBlockingTransactionCount(0)
+{
+ mID = aID;
+ mID.ToProvidedString(mCID);
+}
+
+RequestContext::~RequestContext()
+{
+}
+
+NS_IMETHODIMP
+RequestContext::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount)
+{
+ NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
+ *aBlockingTransactionCount = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::AddBlockingTransaction()
+{
+ mBlockingTransactionCount++;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveBlockingTransaction(uint32_t *outval)
+{
+ NS_ENSURE_ARG_POINTER(outval);
+ mBlockingTransactionCount--;
+ *outval = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetSpdyPushCache(mozilla::net::SpdyPushCache **aSpdyPushCache)
+{
+ *aSpdyPushCache = mSpdyCache.get();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::SetSpdyPushCache(mozilla::net::SpdyPushCache *aSpdyPushCache)
+{
+ mSpdyCache = aSpdyPushCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetID(nsID *outval)
+{
+ NS_ENSURE_ARG_POINTER(outval);
+ *outval = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetUserAgentOverride(nsACString& aUserAgentOverride)
+{
+ aUserAgentOverride = mUserAgentOverride;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::SetUserAgentOverride(const nsACString& aUserAgentOverride)
+{
+ mUserAgentOverride = aUserAgentOverride;
+ return NS_OK;
+}
+
+
+//nsIRequestContextService
+RequestContextService *RequestContextService::sSelf = nullptr;
+
+NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver)
+
+RequestContextService::RequestContextService()
+{
+ MOZ_ASSERT(!sSelf, "multiple rcs instances!");
+ MOZ_ASSERT(NS_IsMainThread());
+ sSelf = this;
+}
+
+RequestContextService::~RequestContextService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ Shutdown();
+ sSelf = nullptr;
+}
+
+nsresult
+RequestContextService::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+}
+
+void
+RequestContextService::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mTable.Clear();
+}
+
+/* static */ nsresult
+RequestContextService::Create(nsISupports *aOuter, const nsIID& aIID, void **aResult)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<RequestContextService> svc = new RequestContextService();
+ nsresult rv = svc->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return svc->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+RequestContextService::GetRequestContext(const nsID& rcID, nsIRequestContext **rc)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(rc);
+ *rc = nullptr;
+
+ if (!mTable.Get(rcID, rc)) {
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ mTable.Put(rcID, newSC);
+ newSC.swap(*rc);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::NewRequestContextID(nsID *rcID)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mUUIDGen) {
+ nsresult rv;
+ mUUIDGen = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mUUIDGen->GenerateUUIDInPlace(rcID);
+}
+
+NS_IMETHODIMP
+RequestContextService::RemoveRequestContext(const nsID& rcID)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mTable.Remove(rcID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data_unicode)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+} // ::mozilla::net
+} // ::mozilla
diff --git a/netwerk/base/RequestContextService.h b/netwerk/base/RequestContextService.h
new file mode 100644
index 0000000000..d7b8e971a1
--- /dev/null
+++ b/netwerk/base/RequestContextService.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__RequestContextService_h
+#define mozilla__net__RequestContextService_h
+
+#include "nsCOMPtr.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIObserver.h"
+#include "nsIRequestContext.h"
+
+class nsIUUIDGenerator;
+
+namespace mozilla {
+namespace net {
+
+class RequestContextService final
+ : public nsIRequestContextService
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXTSERVICE
+ NS_DECL_NSIOBSERVER
+
+ RequestContextService();
+
+ nsresult Init();
+ void Shutdown();
+ static nsresult Create(nsISupports *outer, const nsIID& iid, void **result);
+
+private:
+ virtual ~RequestContextService();
+
+ static RequestContextService *sSelf;
+
+ nsInterfaceHashtable<nsIDHashKey, nsIRequestContext> mTable;
+ nsCOMPtr<nsIUUIDGenerator> mUUIDGen;
+};
+
+} // ::mozilla::net
+} // ::mozilla
+
+#endif // mozilla__net__RequestContextService_h
diff --git a/netwerk/base/ShutdownLayer.cpp b/netwerk/base/ShutdownLayer.cpp
new file mode 100644
index 0000000000..65d792459d
--- /dev/null
+++ b/netwerk/base/ShutdownLayer.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "ShutdownLayer.h"
+#include "prerror.h"
+#include "private/pprio.h"
+#include "prmem.h"
+#include <winsock2.h>
+
+static PRDescIdentity sWinSockShutdownLayerIdentity;
+static PRIOMethods sWinSockShutdownLayerMethods;
+static PRIOMethods *sWinSockShutdownLayerMethodsPtr = nullptr;
+
+namespace mozilla {
+namespace net {
+
+extern PRDescIdentity nsNamedPipeLayerIdentity;
+
+} // namespace net
+} // namespace mozilla
+
+
+PRStatus
+WinSockClose(PRFileDesc *aFd)
+{
+ MOZ_RELEASE_ASSERT(aFd->identity == sWinSockShutdownLayerIdentity,
+ "Windows shutdown layer not on the top of the stack");
+
+ PROsfd osfd = PR_FileDesc2NativeHandle(aFd);
+ if (osfd != -1) {
+ shutdown(osfd, SD_BOTH);
+ }
+
+ aFd->identity = PR_INVALID_IO_LAYER;
+
+ if (aFd->lower) {
+ return aFd->lower->methods->close(aFd->lower);
+ } else {
+ return PR_SUCCESS;
+ }
+}
+
+nsresult mozilla::net::AttachShutdownLayer(PRFileDesc *aFd)
+{
+ if (!sWinSockShutdownLayerMethodsPtr) {
+ sWinSockShutdownLayerIdentity =
+ PR_GetUniqueIdentity("windows shutdown call layer");
+ sWinSockShutdownLayerMethods = *PR_GetDefaultIOMethods();
+ sWinSockShutdownLayerMethods.close = WinSockClose;
+ sWinSockShutdownLayerMethodsPtr = &sWinSockShutdownLayerMethods;
+ }
+
+ if (mozilla::net::nsNamedPipeLayerIdentity &&
+ PR_GetIdentitiesLayer(aFd, mozilla::net::nsNamedPipeLayerIdentity)) {
+ // Do not attach shutdown layer on named pipe layer,
+ // it is for PR_NSPR_IO_LAYER only.
+ return NS_OK;
+ }
+
+ PRFileDesc * layer;
+ PRStatus status;
+
+ layer = PR_CreateIOLayerStub(sWinSockShutdownLayerIdentity,
+ sWinSockShutdownLayerMethodsPtr);
+ if (!layer) {
+ return NS_OK;
+ }
+
+ status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_DELETE(layer);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
diff --git a/netwerk/base/ShutdownLayer.h b/netwerk/base/ShutdownLayer.h
new file mode 100644
index 0000000000..59843b7d07
--- /dev/null
+++ b/netwerk/base/ShutdownLayer.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SHUTDOWNLAYER_H___
+#define SHUTDOWNLAYER_H___
+
+#include "nscore.h"
+#include "prio.h"
+
+namespace mozilla { namespace net {
+
+// This is only for windows. This layer will be attached jus before PR_CLose
+// is call and it will only call shutdown(sock).
+extern nsresult AttachShutdownLayer(PRFileDesc *fd);
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SHUTDOWN_H___ */
diff --git a/netwerk/base/SimpleBuffer.cpp b/netwerk/base/SimpleBuffer.cpp
new file mode 100644
index 0000000000..d0e311f77c
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleBuffer.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+SimpleBuffer::SimpleBuffer()
+ : mStatus(NS_OK)
+ , mAvailable(0)
+{
+ mOwningThread = PR_GetCurrentThread();
+}
+
+nsresult SimpleBuffer::Write(char *src, size_t len)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ while (len > 0) {
+ SimpleBufferPage *p = mBufferList.getLast();
+ if (p && (p->mWriteOffset == SimpleBufferPage::kSimpleBufferPageSize)) {
+ // no room.. make a new page
+ p = nullptr;
+ }
+ if (!p) {
+ p = new (fallible) SimpleBufferPage();
+ if (!p) {
+ mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return mStatus;
+ }
+ mBufferList.insertBack(p);
+ }
+ size_t roomOnPage = SimpleBufferPage::kSimpleBufferPageSize - p->mWriteOffset;
+ size_t toWrite = std::min(roomOnPage, len);
+ memcpy(p->mBuffer + p->mWriteOffset, src, toWrite);
+ src += toWrite;
+ len -= toWrite;
+ p->mWriteOffset += toWrite;
+ mAvailable += toWrite;
+ }
+ return NS_OK;
+}
+
+size_t SimpleBuffer::Read(char *dest, size_t maxLen)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+ if (NS_FAILED(mStatus)) {
+ return 0;
+ }
+
+ size_t rv = 0;
+ for (SimpleBufferPage *p = mBufferList.getFirst();
+ p && (rv < maxLen); p = mBufferList.getFirst()) {
+ size_t avail = p->mWriteOffset - p->mReadOffset;
+ size_t toRead = std::min(avail, (maxLen - rv));
+ memcpy(dest + rv, p->mBuffer + p->mReadOffset, toRead);
+ rv += toRead;
+ p->mReadOffset += toRead;
+ if (p->mReadOffset == p->mWriteOffset) {
+ p->remove();
+ delete p;
+ }
+ }
+
+ MOZ_ASSERT(mAvailable >= rv);
+ mAvailable -= rv;
+ return rv;
+}
+
+size_t SimpleBuffer::Available()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+ return NS_SUCCEEDED(mStatus) ? mAvailable : 0;
+}
+
+void SimpleBuffer::Clear()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+ SimpleBufferPage *p;
+ while ((p = mBufferList.popFirst())) {
+ delete p;
+ }
+ mAvailable = 0;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SimpleBuffer.h b/netwerk/base/SimpleBuffer.h
new file mode 100644
index 0000000000..7652390013
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SimpleBuffer_h__
+#define SimpleBuffer_h__
+
+/*
+ This class is similar to a nsPipe except it does not have any locking, stores
+ an unbounded amount of data, can only be used on one thread, and has much
+ simpler result code semantics to deal with.
+*/
+
+#include "prtypes.h"
+#include "mozilla/LinkedList.h"
+#include "nsIThreadInternal.h"
+
+namespace mozilla {
+namespace net {
+
+class SimpleBufferPage : public LinkedListElement<SimpleBufferPage>
+{
+public:
+ SimpleBufferPage() : mReadOffset(0), mWriteOffset(0) {}
+ static const size_t kSimpleBufferPageSize = 32000;
+
+private:
+ friend class SimpleBuffer;
+ char mBuffer[kSimpleBufferPageSize];
+ size_t mReadOffset;
+ size_t mWriteOffset;
+};
+
+class SimpleBuffer
+{
+public:
+ SimpleBuffer();
+ ~SimpleBuffer() {}
+
+ nsresult Write(char *stc, size_t len); // return OK or OUT_OF_MEMORY
+ size_t Read(char *dest, size_t maxLen); // return bytes read
+ size_t Available();
+ void Clear();
+
+private:
+ PRThread *mOwningThread;
+ nsresult mStatus;
+ AutoCleanLinkedList<SimpleBufferPage> mBufferList;
+ size_t mAvailable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/StreamingProtocolService.cpp b/netwerk/base/StreamingProtocolService.cpp
new file mode 100644
index 0000000000..3df3326bc1
--- /dev/null
+++ b/netwerk/base/StreamingProtocolService.cpp
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "StreamingProtocolService.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsIURI.h"
+#include "necko-config.h"
+
+#ifdef NECKO_PROTOCOL_rtsp
+#include "RtspControllerChild.h"
+#include "RtspController.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(StreamingProtocolControllerService,
+ nsIStreamingProtocolControllerService)
+
+/* static */
+StaticRefPtr<StreamingProtocolControllerService> sSingleton;
+
+/* static */
+already_AddRefed<StreamingProtocolControllerService>
+StreamingProtocolControllerService::GetInstance()
+{
+ if (!sSingleton) {
+ sSingleton = new StreamingProtocolControllerService();
+ ClearOnShutdown(&sSingleton);
+ }
+ RefPtr<StreamingProtocolControllerService> service = sSingleton.get();
+ return service.forget();
+}
+
+NS_IMETHODIMP
+StreamingProtocolControllerService::Create(nsIChannel *aChannel, nsIStreamingProtocolController **aResult)
+{
+ RefPtr<nsIStreamingProtocolController> mediacontroller;
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString scheme;
+
+ NS_ENSURE_ARG_POINTER(aChannel);
+ aChannel->GetURI(getter_AddRefs(uri));
+
+ nsresult rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef NECKO_PROTOCOL_rtsp
+ if (scheme.EqualsLiteral("rtsp")) {
+ if (IsNeckoChild()) {
+ mediacontroller = new RtspControllerChild(aChannel);
+ } else {
+ mediacontroller = new RtspController(aChannel);
+ }
+ }
+#endif
+
+ if (!mediacontroller) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ mediacontroller->Init(uri);
+
+ mediacontroller.forget(aResult);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/StreamingProtocolService.h b/netwerk/base/StreamingProtocolService.h
new file mode 100644
index 0000000000..96d51fe5c4
--- /dev/null
+++ b/netwerk/base/StreamingProtocolService.h
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_StreamingProtocolControllerService_h
+#define mozilla_net_StreamingProtocolControllerService_h
+
+#include "mozilla/StaticPtr.h"
+#include "nsIStreamingProtocolService.h"
+#include "nsIStreamingProtocolController.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ * This class implements a service to help to create streaming protocol controller.
+ */
+class StreamingProtocolControllerService : public nsIStreamingProtocolControllerService
+{
+private:
+ virtual ~StreamingProtocolControllerService() {};
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMINGPROTOCOLCONTROLLERSERVICE
+
+ StreamingProtocolControllerService() {};
+ static already_AddRefed<StreamingProtocolControllerService> GetInstance();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif //mozilla_net_StreamingProtocolControllerService_h
diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp
new file mode 100644
index 0000000000..b32a9a1887
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.cpp
@@ -0,0 +1,515 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TLSServerSocket.h"
+
+#include "mozilla/net/DNS.h"
+#include "nsAutoPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIServerSocket.h"
+#include "nsITimer.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransport2.h"
+#include "nsThreadUtils.h"
+#include "ScopedNSSTypes.h"
+#include "ssl.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket
+//-----------------------------------------------------------------------------
+
+TLSServerSocket::TLSServerSocket()
+ : mServerCert(nullptr)
+{
+}
+
+TLSServerSocket::~TLSServerSocket()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(TLSServerSocket,
+ nsServerSocket,
+ nsITLSServerSocket)
+
+nsresult
+TLSServerSocket::SetSocketDefaults()
+{
+ // Set TLS options on the listening socket
+ mFD = SSL_ImportFD(nullptr, mFD);
+ if (NS_WARN_IF(!mFD)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSL_OptionSet(mFD, SSL_SECURITY, true);
+ SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_CLIENT, false);
+ SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_SERVER, true);
+
+ // We don't currently notify the server API consumer of renegotiation events
+ // (to revalidate peer certs, etc.), so disable it for now.
+ SSL_OptionSet(mFD, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER);
+
+ SetSessionCache(true);
+ SetSessionTickets(true);
+ SetRequestClientCertificate(REQUEST_NEVER);
+
+ return NS_OK;
+}
+
+void
+TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+ const NetAddr& aClientAddr)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsresult rv;
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport;
+ if (NS_WARN_IF(!trans)) {
+ mCondition = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ RefPtr<TLSServerConnectionInfo> info = new TLSServerConnectionInfo();
+ info->mServerSocket = this;
+ info->mTransport = trans;
+ nsCOMPtr<nsISupports> infoSupports =
+ NS_ISUPPORTS_CAST(nsITLSServerConnectionInfo*, info);
+ rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr, infoSupports);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ // Override the default peer certificate validation, so that server consumers
+ // can make their own choice after the handshake completes.
+ SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr);
+ // Once the TLS handshake has completed, the server consumer is notified and
+ // has access to various TLS state details.
+ // It's safe to pass info here because the socket transport holds it as
+ // |mSecInfo| which keeps it alive for the lifetime of the socket.
+ SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback,
+ info);
+
+ // Notify the consumer of the new client so it can manage the streams.
+ // Security details aren't known yet. The security observer will be notified
+ // later when they are ready.
+ nsCOMPtr<nsIServerSocket> serverSocket =
+ do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this));
+ mListener->OnSocketAccepted(serverSocket, trans);
+}
+
+nsresult
+TLSServerSocket::OnSocketListen()
+{
+ if (NS_WARN_IF(!mServerCert)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ UniqueCERTCertificate cert(mServerCert->GetCert());
+ if (NS_WARN_IF(!cert)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr));
+ if (NS_WARN_IF(!key)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSLKEAType certKEA = NSS_FindCertKEAType(cert.get());
+
+ nsresult rv = MapSECStatus(SSL_ConfigSecureServer(mFD, cert.get(), key.get(),
+ certKEA));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+SECStatus
+TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checksig,
+ PRBool isServer)
+{
+ // Allow any client cert here, server consumer code can decide whether it's
+ // okay after being notified of the new client socket.
+ return SECSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket::nsITLSServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+TLSServerSocket::GetServerCert(nsIX509Cert** aCert)
+{
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = mServerCert;
+ NS_IF_ADDREF(*aCert);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetServerCert(nsIX509Cert* aCert)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ mServerCert = aCert;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetSessionCache(bool aEnabled)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_NO_CACHE, !aEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetSessionTickets(bool aEnabled)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_ENABLE_SESSION_TICKETS, aEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetRequestClientCertificate(uint32_t aMode)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_REQUEST_CERTIFICATE, aMode != REQUEST_NEVER);
+
+ switch (aMode) {
+ case REQUEST_ALWAYS:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR);
+ break;
+ case REQUIRE_FIRST_HANDSHAKE:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_FIRST_HANDSHAKE);
+ break;
+ case REQUIRE_ALWAYS:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS);
+ break;
+ default:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetCipherSuites(uint16_t* aCipherSuites, uint32_t aLength)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
+ uint16_t cipher_id = SSL_ImplementedCiphers[i];
+ if (SSL_CipherPrefSet(mFD, cipher_id, false) != SECSuccess) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+ }
+
+ for (uint32_t i = 0; i < aLength; ++i) {
+ if (SSL_CipherPrefSet(mFD, aCipherSuites[i], true) != SECSuccess) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetVersionRange(uint16_t aMinVersion, uint16_t aMaxVersion)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ SSLVersionRange range = {aMinVersion, aMaxVersion};
+ if (SSL_VersionRangeSet(mFD, &range) != SECSuccess) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerConnectionInfo
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class TLSServerSecurityObserverProxy final : public nsITLSServerSecurityObserver
+{
+ ~TLSServerSecurityObserverProxy() {}
+
+public:
+ explicit TLSServerSecurityObserverProxy(nsITLSServerSecurityObserver* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsITLSServerSecurityObserver>(aListener))
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERSECURITYOBSERVER
+
+ class OnHandshakeDoneRunnable : public Runnable
+ {
+ public:
+ OnHandshakeDoneRunnable(const nsMainThreadPtrHandle<nsITLSServerSecurityObserver>& aListener,
+ nsITLSServerSocket* aServer,
+ nsITLSClientStatus* aStatus)
+ : mListener(aListener)
+ , mServer(aServer)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+ nsCOMPtr<nsITLSServerSocket> mServer;
+ nsCOMPtr<nsITLSClientStatus> mStatus;
+ };
+
+private:
+ nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+};
+
+NS_IMPL_ISUPPORTS(TLSServerSecurityObserverProxy,
+ nsITLSServerSecurityObserver)
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDone(nsITLSServerSocket* aServer,
+ nsITLSClientStatus* aStatus)
+{
+ RefPtr<OnHandshakeDoneRunnable> r =
+ new OnHandshakeDoneRunnable(mListener, aServer, aStatus);
+ return NS_DispatchToMainThread(r);
+}
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable::Run()
+{
+ mListener->OnHandshakeDone(mServer, mStatus);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(TLSServerConnectionInfo,
+ nsITLSServerConnectionInfo,
+ nsITLSClientStatus)
+
+TLSServerConnectionInfo::TLSServerConnectionInfo()
+ : mServerSocket(nullptr)
+ , mTransport(nullptr)
+ , mPeerCert(nullptr)
+ , mTlsVersionUsed(TLS_VERSION_UNKNOWN)
+ , mKeyLength(0)
+ , mMacLength(0)
+ , mLock("TLSServerConnectionInfo.mLock")
+ , mSecurityObserver(nullptr)
+{
+}
+
+TLSServerConnectionInfo::~TLSServerConnectionInfo()
+{
+ if (!mSecurityObserver) {
+ return;
+ }
+
+ RefPtr<nsITLSServerSecurityObserver> observer;
+ {
+ MutexAutoLock lock(mLock);
+ observer = mSecurityObserver.forget();
+ }
+
+ if (observer) {
+ NS_ReleaseOnMainThread(observer.forget());
+ }
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::SetSecurityObserver(nsITLSServerSecurityObserver* aObserver)
+{
+ {
+ MutexAutoLock lock(mLock);
+ mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket)
+{
+ if (NS_WARN_IF(!aSocket)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aSocket = mServerSocket;
+ NS_IF_ADDREF(*aSocket);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus)
+{
+ if (NS_WARN_IF(!aStatus)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aStatus = this;
+ NS_IF_ADDREF(*aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert)
+{
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = mPeerCert;
+ NS_IF_ADDREF(*aCert);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed)
+{
+ if (NS_WARN_IF(!aTlsVersionUsed)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aTlsVersionUsed = mTlsVersionUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName)
+{
+ aCipherName.Assign(mCipherName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength)
+{
+ if (NS_WARN_IF(!aKeyLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aKeyLength = mKeyLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength)
+{
+ if (NS_WARN_IF(!aMacLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aMacLength = mMacLength;
+ return NS_OK;
+}
+
+// static
+void
+TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg)
+{
+ RefPtr<TLSServerConnectionInfo> info =
+ static_cast<TLSServerConnectionInfo*>(aArg);
+ nsISocketTransport* transport = info->mTransport;
+ // No longer needed outside this function, so clear the weak ref
+ info->mTransport = nullptr;
+ nsresult rv = info->HandshakeCallback(aFD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ transport->Close(rv);
+ }
+}
+
+nsresult
+TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD)
+{
+ nsresult rv;
+
+ UniqueCERTCertificate clientCert(SSL_PeerCertificate(aFD));
+ if (clientCert) {
+ nsCOMPtr<nsIX509CertDB> certDB =
+ do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIX509Cert> clientCertPSM;
+ rv = certDB->ConstructX509(reinterpret_cast<char*>(clientCert->derCert.data),
+ clientCert->derCert.len,
+ getter_AddRefs(clientCertPSM));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPeerCert = clientCertPSM;
+ }
+
+ SSLChannelInfo channelInfo;
+ rv = MapSECStatus(SSL_GetChannelInfo(aFD, &channelInfo, sizeof(channelInfo)));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mTlsVersionUsed = channelInfo.protocolVersion;
+
+ SSLCipherSuiteInfo cipherInfo;
+ rv = MapSECStatus(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+ sizeof(cipherInfo)));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCipherName.Assign(cipherInfo.cipherSuiteName);
+ mKeyLength = cipherInfo.effectiveKeyBits;
+ mMacLength = cipherInfo.macBits;
+
+ if (!mSecurityObserver) {
+ return NS_OK;
+ }
+
+ // Notify consumer code that handshake is complete
+ nsCOMPtr<nsITLSServerSecurityObserver> observer;
+ {
+ MutexAutoLock lock(mLock);
+ mSecurityObserver.swap(observer);
+ }
+ nsCOMPtr<nsITLSServerSocket> serverSocket;
+ GetServerSocket(getter_AddRefs(serverSocket));
+ observer->OnHandshakeDone(serverSocket, this);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/TLSServerSocket.h b/netwerk/base/TLSServerSocket.h
new file mode 100644
index 0000000000..9fb57e0cc2
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.h
@@ -0,0 +1,81 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_TLSServerSocket_h
+#define mozilla_net_TLSServerSocket_h
+
+#include "nsAutoPtr.h"
+#include "nsITLSServerSocket.h"
+#include "nsServerSocket.h"
+#include "nsString.h"
+#include "mozilla/Mutex.h"
+#include "seccomon.h"
+
+namespace mozilla {
+namespace net {
+
+class TLSServerSocket final : public nsServerSocket
+ , public nsITLSServerSocket
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSISERVERSOCKET(nsServerSocket::)
+ NS_DECL_NSITLSSERVERSOCKET
+
+ // Override methods from nsServerSocket
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const NetAddr& clientAddr) override;
+ virtual nsresult SetSocketDefaults() override;
+ virtual nsresult OnSocketListen() override;
+
+ TLSServerSocket();
+
+private:
+ virtual ~TLSServerSocket();
+
+ static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig, PRBool isServer);
+
+ nsCOMPtr<nsIX509Cert> mServerCert;
+};
+
+class TLSServerConnectionInfo : public nsITLSServerConnectionInfo
+ , public nsITLSClientStatus
+{
+ friend class TLSServerSocket;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERCONNECTIONINFO
+ NS_DECL_NSITLSCLIENTSTATUS
+
+ TLSServerConnectionInfo();
+
+private:
+ virtual ~TLSServerConnectionInfo();
+
+ static void HandshakeCallback(PRFileDesc* aFD, void* aArg);
+ nsresult HandshakeCallback(PRFileDesc* aFD);
+
+ RefPtr<TLSServerSocket> mServerSocket;
+ // Weak ref to the transport, to avoid cycles since the transport holds a
+ // reference to the TLSServerConnectionInfo object. This is not handed out to
+ // anyone, and is only used in HandshakeCallback to close the transport in
+ // case of an error. After this, it's set to nullptr.
+ nsISocketTransport* mTransport;
+ nsCOMPtr<nsIX509Cert> mPeerCert;
+ int16_t mTlsVersionUsed;
+ nsCString mCipherName;
+ uint32_t mKeyLength;
+ uint32_t mMacLength;
+ // lock protects access to mSecurityObserver
+ mozilla::Mutex mLock;
+ nsCOMPtr<nsITLSServerSecurityObserver> mSecurityObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSServerSocket_h
diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp
new file mode 100644
index 0000000000..d5b8a41df8
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.cpp
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ThrottleQueue.h"
+#include "nsISeekableStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsStreamUtils.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+class ThrottleInputStream final
+ : public nsIAsyncInputStream
+ , public nsISeekableStream
+{
+public:
+
+ ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ void AllowInput();
+
+private:
+
+ ~ThrottleInputStream();
+
+ nsCOMPtr<nsIInputStream> mStream;
+ RefPtr<ThrottleQueue> mQueue;
+ nsresult mClosedStatus;
+
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream, nsISeekableStream)
+
+ThrottleInputStream::ThrottleInputStream(nsIInputStream *aStream, ThrottleQueue* aQueue)
+ : mStream(aStream)
+ , mQueue(aQueue)
+ , mClosedStatus(NS_OK)
+{
+ MOZ_ASSERT(aQueue != nullptr);
+}
+
+ThrottleInputStream::~ThrottleInputStream()
+{
+ Close();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Close()
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ if (mQueue) {
+ mQueue->DequeueStream(this);
+ mQueue = nullptr;
+ mClosedStatus = NS_BASE_STREAM_CLOSED;
+ }
+ return mStream->Close();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Available(uint64_t* aResult)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ return mStream->Available(aResult);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->Read(aBuf, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Tell(int64_t* aResult)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Tell(aResult);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::SetEOF()
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->SetEOF();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::CloseWithStatus(nsresult aStatus)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ // Already closed, ignore.
+ return NS_OK;
+ }
+ if (NS_SUCCEEDED(aStatus)) {
+ aStatus = NS_BASE_STREAM_CLOSED;
+ }
+
+ mClosedStatus = Close();
+ if (NS_SUCCEEDED(mClosedStatus)) {
+ mClosedStatus = aStatus;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::AsyncWait(nsIInputStreamCallback *aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget *aEventTarget)
+{
+ if (aFlags != 0) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mCallback = aCallback;
+ mEventTarget = aEventTarget;
+ if (mCallback) {
+ mQueue->QueueStream(this);
+ } else {
+ mQueue->DequeueStream(this);
+ }
+ return NS_OK;
+}
+
+void
+ThrottleInputStream::AllowInput()
+{
+ MOZ_ASSERT(mCallback);
+ nsCOMPtr<nsIInputStreamCallback> callbackEvent =
+ NS_NewInputStreamReadyEvent(mCallback, mEventTarget);
+ mCallback = nullptr;
+ mEventTarget = nullptr;
+ callbackEvent->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback)
+
+ThrottleQueue::ThrottleQueue()
+ : mMeanBytesPerSecond(0)
+ , mMaxBytesPerSecond(0)
+ , mBytesProcessed(0)
+ , mTimerArmed(false)
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv))
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mTimer)
+ mTimer->SetTarget(sts);
+}
+
+ThrottleQueue::~ThrottleQueue()
+{
+ if (mTimer && mTimerArmed) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::RecordRead(uint32_t aBytesRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ ThrottleEntry entry;
+ entry.mTime = TimeStamp::Now();
+ entry.mBytesRead = aBytesRead;
+ mReadEvents.AppendElement(entry);
+ mBytesProcessed += aBytesRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Available(uint32_t aRemaining, uint32_t* aAvailable)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ TimeStamp now = TimeStamp::Now();
+ TimeStamp oneSecondAgo = now - TimeDuration::FromSeconds(1);
+ size_t i;
+
+ // Remove all stale events.
+ for (i = 0; i < mReadEvents.Length(); ++i) {
+ if (mReadEvents[i].mTime >= oneSecondAgo) {
+ break;
+ }
+ }
+ mReadEvents.RemoveElementsAt(0, i);
+
+ uint32_t totalBytes = 0;
+ for (i = 0; i < mReadEvents.Length(); ++i) {
+ totalBytes += mReadEvents[i].mBytesRead;
+ }
+
+ uint32_t spread = mMaxBytesPerSecond - mMeanBytesPerSecond;
+ double prob = static_cast<double>(rand()) / RAND_MAX;
+ uint32_t thisSliceBytes = mMeanBytesPerSecond - spread +
+ static_cast<uint32_t>(2 * spread * prob);
+
+ if (totalBytes >= thisSliceBytes) {
+ *aAvailable = 0;
+ } else {
+ *aAvailable = thisSliceBytes;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond)
+{
+ // Can be called on any thread.
+ if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 || aMaxBytesPerSecond < aMeanBytesPerSecond) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mMeanBytesPerSecond = aMeanBytesPerSecond;
+ mMaxBytesPerSecond = aMaxBytesPerSecond;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::BytesProcessed(uint64_t* aResult)
+{
+ *aResult = mBytesProcessed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::WrapStream(nsIInputStream* aInputStream, nsIAsyncInputStream** aResult)
+{
+ nsCOMPtr<nsIAsyncInputStream> result = new ThrottleInputStream(aInputStream, this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Notify(nsITimer* aTimer)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ // A notified reader may need to push itself back on the queue.
+ // Swap out the list of readers so that this works properly.
+ nsTArray<RefPtr<ThrottleInputStream>> events;
+ events.SwapElements(mAsyncEvents);
+
+ // Optimistically notify all the waiting readers, and then let them
+ // requeue if there isn't enough bandwidth.
+ for (size_t i = 0; i < events.Length(); ++i) {
+ events[i]->AllowInput();
+ }
+
+ mTimerArmed = false;
+ return NS_OK;
+}
+
+void
+ThrottleQueue::QueueStream(ThrottleInputStream* aStream)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (mAsyncEvents.IndexOf(aStream) == mAsyncEvents.NoIndex) {
+ mAsyncEvents.AppendElement(aStream);
+
+ if (!mTimerArmed) {
+ uint32_t ms = 1000;
+ if (mReadEvents.Length() > 0) {
+ TimeStamp t = mReadEvents[0].mTime + TimeDuration::FromSeconds(1);
+ TimeStamp now = TimeStamp::Now();
+
+ if (t > now) {
+ ms = static_cast<uint32_t>((t - now).ToMilliseconds());
+ } else {
+ ms = 1;
+ }
+ }
+
+ if (NS_SUCCEEDED(mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) {
+ mTimerArmed = true;
+ }
+ }
+ }
+}
+
+void
+ThrottleQueue::DequeueStream(ThrottleInputStream* aStream)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mAsyncEvents.RemoveElement(aStream);
+}
+
+}
+}
diff --git a/netwerk/base/ThrottleQueue.h b/netwerk/base/ThrottleQueue.h
new file mode 100644
index 0000000000..5e16c8ef60
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ThrottleQueue_h
+#define mozilla_net_ThrottleQueue_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace net {
+
+class ThrottleInputStream;
+
+/**
+ * An implementation of nsIInputChannelThrottleQueue that can be used
+ * to throttle uploads. This class is not thread-safe.
+ * Initialization and calls to WrapStream may be done on any thread;
+ * but otherwise, after creation, it can only be used on the socket
+ * thread. It currently throttles with a one second granularity, so
+ * may be a bit choppy.
+ */
+
+class ThrottleQueue final
+ : public nsIInputChannelThrottleQueue
+ , public nsITimerCallback
+{
+public:
+
+ ThrottleQueue();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE
+ NS_DECL_NSITIMERCALLBACK
+
+ void QueueStream(ThrottleInputStream* aStream);
+ void DequeueStream(ThrottleInputStream* aStream);
+
+private:
+
+ ~ThrottleQueue();
+
+ struct ThrottleEntry {
+ TimeStamp mTime;
+ uint32_t mBytesRead;
+ };
+
+ nsTArray<ThrottleEntry> mReadEvents;
+ uint32_t mMeanBytesPerSecond;
+ uint32_t mMaxBytesPerSecond;
+ uint64_t mBytesProcessed;
+
+ nsTArray<RefPtr<ThrottleInputStream>> mAsyncEvents;
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerArmed;
+};
+
+}
+}
+
+#endif // mozilla_net_ThrottleQueue_h
diff --git a/netwerk/base/Tickler.cpp b/netwerk/base/Tickler.cpp
new file mode 100644
index 0000000000..555fdbbe5e
--- /dev/null
+++ b/netwerk/base/Tickler.cpp
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Tickler.h"
+
+#ifdef MOZ_USE_WIFI_TICKLER
+#include "nsComponentManagerUtils.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "prnetdb.h"
+
+#include "mozilla/jni/Utils.h"
+#include "GeneratedJNIWrappers.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(Tickler, nsISupportsWeakReference, Tickler)
+
+Tickler::Tickler()
+ : mLock("Tickler::mLock")
+ , mActive(false)
+ , mCanceled(false)
+ , mEnabled(false)
+ , mDelay(16)
+ , mDuration(TimeDuration::FromMilliseconds(400))
+ , mFD(nullptr)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+Tickler::~Tickler()
+{
+ // non main thread uses of the tickler should hold weak
+ // references to it if they must hold a reference at all
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mThread) {
+ mThread->AsyncShutdown();
+ mThread = nullptr;
+ }
+
+ if (mTimer)
+ mTimer->Cancel();
+ if (mFD)
+ PR_Close(mFD);
+}
+
+nsresult
+Tickler::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mTimer);
+ MOZ_ASSERT(!mActive);
+ MOZ_ASSERT(!mThread);
+ MOZ_ASSERT(!mFD);
+
+ if (jni::IsAvailable()) {
+ java::GeckoAppShell::EnableNetworkNotifications();
+ }
+
+ mFD = PR_OpenUDPSocket(PR_AF_INET);
+ if (!mFD)
+ return NS_ERROR_FAILURE;
+
+ // make sure new socket has a ttl of 1
+ // failure is not fatal.
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_IpTimeToLive;
+ opt.value.ip_ttl = 1;
+ PR_SetSocketOption(mFD, &opt);
+
+ nsresult rv = NS_NewNamedThread("wifi tickler",
+ getter_AddRefs(mThread));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsITimer> tmpTimer(do_CreateInstance(NS_TIMER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = tmpTimer->SetTarget(mThread);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mTimer.swap(tmpTimer);
+
+ mAddr.inet.family = PR_AF_INET;
+ mAddr.inet.port = PR_htons (4886);
+ mAddr.inet.ip = 0;
+
+ return NS_OK;
+}
+
+void Tickler::Tickle()
+{
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mThread);
+ mLastTickle = TimeStamp::Now();
+ if (!mActive)
+ MaybeStartTickler();
+}
+
+void Tickler::PostCheckTickler()
+{
+ mLock.AssertCurrentThreadOwns();
+ mThread->Dispatch(NewRunnableMethod(this, &Tickler::CheckTickler),
+ NS_DISPATCH_NORMAL);
+ return;
+}
+
+void Tickler::MaybeStartTicklerUnlocked()
+{
+ MutexAutoLock lock(mLock);
+ MaybeStartTickler();
+}
+
+void Tickler::MaybeStartTickler()
+{
+ mLock.AssertCurrentThreadOwns();
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod(this, &Tickler::MaybeStartTicklerUnlocked));
+ return;
+ }
+
+ if (!mPrefs)
+ mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (mPrefs) {
+ int32_t val;
+ bool boolVal;
+
+ if (NS_SUCCEEDED(mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal)))
+ mEnabled = boolVal;
+
+ if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) {
+ if (val < 1)
+ val = 1;
+ if (val > 100000)
+ val = 100000;
+ mDuration = TimeDuration::FromMilliseconds(val);
+ }
+
+ if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) {
+ if (val < 1)
+ val = 1;
+ if (val > 1000)
+ val = 1000;
+ mDelay = static_cast<uint32_t>(val);
+ }
+ }
+
+ PostCheckTickler();
+}
+
+void Tickler::CheckTickler()
+{
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+
+ bool shouldRun = (!mCanceled) &&
+ ((TimeStamp::Now() - mLastTickle) <= mDuration);
+
+ if ((shouldRun && mActive) || (!shouldRun && !mActive))
+ return; // no change in state
+
+ if (mActive)
+ StopTickler();
+ else
+ StartTickler();
+}
+
+void Tickler::Cancel()
+{
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(NS_IsMainThread());
+ mCanceled = true;
+ if (mThread)
+ PostCheckTickler();
+}
+
+void Tickler::StopTickler()
+{
+ mLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+ MOZ_ASSERT(mTimer);
+ MOZ_ASSERT(mActive);
+
+ mTimer->Cancel();
+ mActive = false;
+}
+
+class TicklerTimer final : public nsITimerCallback
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ TicklerTimer(Tickler *aTickler)
+ {
+ mTickler = do_GetWeakReference(aTickler);
+ }
+
+private:
+ ~TicklerTimer() {}
+
+ nsWeakPtr mTickler;
+};
+
+void Tickler::StartTickler()
+{
+ mLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+ MOZ_ASSERT(!mActive);
+ MOZ_ASSERT(mTimer);
+
+ if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this),
+ mEnabled ? mDelay : 1000,
+ nsITimer::TYPE_REPEATING_SLACK)))
+ mActive = true;
+}
+
+// argument should be in network byte order
+void Tickler::SetIPV4Address(uint32_t address)
+{
+ mAddr.inet.ip = address;
+}
+
+// argument should be in network byte order
+void Tickler::SetIPV4Port(uint16_t port)
+{
+ mAddr.inet.port = port;
+}
+
+NS_IMPL_ISUPPORTS(TicklerTimer, nsITimerCallback)
+
+NS_IMETHODIMP TicklerTimer::Notify(nsITimer *timer)
+{
+ RefPtr<Tickler> tickler = do_QueryReferent(mTickler);
+ if (!tickler)
+ return NS_ERROR_FAILURE;
+ MutexAutoLock lock(tickler->mLock);
+
+ if (!tickler->mFD) {
+ tickler->StopTickler();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (tickler->mCanceled ||
+ ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) {
+ tickler->StopTickler();
+ return NS_OK;
+ }
+
+ if (!tickler->mEnabled)
+ return NS_OK;
+
+ PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0);
+ return NS_OK;
+}
+
+} // namespace mozilla::net
+} // namespace mozilla
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+namespace mozilla {
+namespace net {
+NS_IMPL_ISUPPORTS0(Tickler)
+} // namespace net
+} // namespace mozilla
+
+#endif // defined MOZ_USE_WIFI_TICKLER
+
diff --git a/netwerk/base/Tickler.h b/netwerk/base/Tickler.h
new file mode 100644
index 0000000000..573fe6e762
--- /dev/null
+++ b/netwerk/base/Tickler.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Tickler_h
+#define mozilla_net_Tickler_h
+
+// The tickler sends a regular 0 byte UDP heartbeat out to a
+// particular address for a short time after it has been touched. This
+// is used on some mobile wifi chipsets to mitigate Power Save Polling
+// (PSP) Mode when we are anticipating a response packet
+// soon. Typically PSP adds 100ms of latency to a read event because
+// the packet delivery is not triggered until the 802.11 beacon is
+// delivered to the host (100ms is the standard Access Point
+// configuration for the beacon interval.) Requesting a frequent
+// transmission and getting a CTS frame from the AP at least that
+// frequently allows for low latency receives when we have reason to
+// expect them (e.g a SYN-ACK).
+//
+// The tickler is used to allow RTT based phases of web transport to
+// complete quickly when on wifi - ARP, DNS, TCP handshake, SSL
+// handshake, HTTP headers, and the TCP slow start phase. The
+// transaction is given up to 400 miliseconds by default to get
+// through those phases before the tickler is disabled.
+//
+// The tickler only applies to wifi on mobile right now. Hopefully it
+// can also be restricted to particular handset models in the future.
+
+#if defined(ANDROID) && !defined(MOZ_B2G)
+#define MOZ_USE_WIFI_TICKLER
+#endif
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include <stdint.h>
+
+#ifdef MOZ_USE_WIFI_TICKLER
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsAutoPtr.h"
+#include "nsISupports.h"
+#include "nsIThread.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+#include "prio.h"
+
+class nsIPrefBranch;
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifdef MOZ_USE_WIFI_TICKLER
+
+// 8f769ed6-207c-4af9-9f7e-9e832da3754e
+#define NS_TICKLER_IID \
+{ 0x8f769ed6, 0x207c, 0x4af9, \
+ { 0x9f, 0x7e, 0x9e, 0x83, 0x2d, 0xa3, 0x75, 0x4e } }
+
+class Tickler final : public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TICKLER_IID)
+
+ // These methods are main thread only
+ Tickler();
+ void Cancel();
+ nsresult Init();
+ void SetIPV4Address(uint32_t address);
+ void SetIPV4Port(uint16_t port);
+
+ // Tickle the tickler to (re-)start the activity.
+ // May call from any thread
+ void Tickle();
+
+private:
+ ~Tickler();
+
+ friend class TicklerTimer;
+ Mutex mLock;
+ nsCOMPtr<nsIThread> mThread;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIPrefBranch> mPrefs;
+
+ bool mActive;
+ bool mCanceled;
+ bool mEnabled;
+ uint32_t mDelay;
+ TimeDuration mDuration;
+ PRFileDesc* mFD;
+
+ TimeStamp mLastTickle;
+ PRNetAddr mAddr;
+
+ // These functions may be called from any thread
+ void PostCheckTickler();
+ void MaybeStartTickler();
+ void MaybeStartTicklerUnlocked();
+
+ // Tickler thread only
+ void CheckTickler();
+ void StartTickler();
+ void StopTickler();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Tickler, NS_TICKLER_IID)
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+class Tickler final : public nsISupports
+{
+ ~Tickler() { }
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ Tickler() { }
+ nsresult Init() { return NS_ERROR_NOT_IMPLEMENTED; }
+ void Cancel() { }
+ void SetIPV4Address(uint32_t) { };
+ void SetIPV4Port(uint16_t) { }
+ void Tickle() { }
+};
+
+#endif // defined MOZ_USE_WIFI_TICKLER
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Tickler_h
diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build
new file mode 100644
index 0000000000..3b731db109
--- /dev/null
+++ b/netwerk/base/moz.build
@@ -0,0 +1,316 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'mozIThirdPartyUtil.idl',
+ 'nsIApplicationCache.idl',
+ 'nsIApplicationCacheChannel.idl',
+ 'nsIApplicationCacheContainer.idl',
+ 'nsIApplicationCacheService.idl',
+ 'nsIArrayBufferInputStream.idl',
+ 'nsIAsyncStreamCopier.idl',
+ 'nsIAsyncStreamCopier2.idl',
+ 'nsIAsyncVerifyRedirectCallback.idl',
+ 'nsIAuthInformation.idl',
+ 'nsIAuthModule.idl',
+ 'nsIAuthPrompt.idl',
+ 'nsIAuthPrompt2.idl',
+ 'nsIAuthPromptAdapterFactory.idl',
+ 'nsIAuthPromptCallback.idl',
+ 'nsIAuthPromptProvider.idl',
+ 'nsIBackgroundFileSaver.idl',
+ 'nsIBufferedStreams.idl',
+ 'nsIByteRangeRequest.idl',
+ 'nsICacheInfoChannel.idl',
+ 'nsICachingChannel.idl',
+ 'nsICancelable.idl',
+ 'nsICaptivePortalService.idl',
+ 'nsIChannel.idl',
+ 'nsIChannelEventSink.idl',
+ 'nsIChannelWithDivertableParentListener.idl',
+ 'nsIChildChannel.idl',
+ 'nsIClassOfService.idl',
+ 'nsIContentSniffer.idl',
+ 'nsICryptoFIPSInfo.idl',
+ 'nsICryptoHash.idl',
+ 'nsICryptoHMAC.idl',
+ 'nsIDashboard.idl',
+ 'nsIDashboardEventNotifier.idl',
+ 'nsIDeprecationWarner.idl',
+ 'nsIDivertableChannel.idl',
+ 'nsIDownloader.idl',
+ 'nsIEncodedChannel.idl',
+ 'nsIExternalProtocolHandler.idl',
+ 'nsIFileStreams.idl',
+ 'nsIFileURL.idl',
+ 'nsIForcePendingChannel.idl',
+ 'nsIFormPOSTActionChannel.idl',
+ 'nsIHttpAuthenticatorCallback.idl',
+ 'nsIHttpPushListener.idl',
+ 'nsIIncrementalDownload.idl',
+ 'nsIIncrementalStreamLoader.idl',
+ 'nsIInputStreamChannel.idl',
+ 'nsIInputStreamPump.idl',
+ 'nsIIOService.idl',
+ 'nsIIOService2.idl',
+ 'nsILoadContextInfo.idl',
+ 'nsILoadGroup.idl',
+ 'nsILoadGroupChild.idl',
+ 'nsILoadInfo.idl',
+ 'nsIMIMEInputStream.idl',
+ 'nsIMultiPartChannel.idl',
+ 'nsINestedURI.idl',
+ 'nsINetAddr.idl',
+ 'nsINetUtil.idl',
+ 'nsINetworkInfoService.idl',
+ 'nsINetworkInterceptController.idl',
+ 'nsINetworkLinkService.idl',
+ 'nsINetworkPredictor.idl',
+ 'nsINetworkPredictorVerifier.idl',
+ 'nsINetworkProperties.idl',
+ 'nsINSSErrorsService.idl',
+ 'nsINullChannel.idl',
+ 'nsIParentChannel.idl',
+ 'nsIParentRedirectingChannel.idl',
+ 'nsIPermission.idl',
+ 'nsIPermissionManager.idl',
+ 'nsIPrivateBrowsingChannel.idl',
+ 'nsIProgressEventSink.idl',
+ 'nsIPrompt.idl',
+ 'nsIProtocolHandler.idl',
+ 'nsIProtocolProxyCallback.idl',
+ 'nsIProtocolProxyFilter.idl',
+ 'nsIProtocolProxyService.idl',
+ 'nsIProtocolProxyService2.idl',
+ 'nsIProxiedChannel.idl',
+ 'nsIProxiedProtocolHandler.idl',
+ 'nsIProxyInfo.idl',
+ 'nsIRandomGenerator.idl',
+ 'nsIRedirectChannelRegistrar.idl',
+ 'nsIRedirectResultListener.idl',
+ 'nsIRequest.idl',
+ 'nsIRequestContext.idl',
+ 'nsIRequestObserver.idl',
+ 'nsIRequestObserverProxy.idl',
+ 'nsIResumableChannel.idl',
+ 'nsISecCheckWrapChannel.idl',
+ 'nsISecureBrowserUI.idl',
+ 'nsISecurityEventSink.idl',
+ 'nsISecurityInfoProvider.idl',
+ 'nsISensitiveInfoHiddenURI.idl',
+ 'nsISerializationHelper.idl',
+ 'nsIServerSocket.idl',
+ 'nsISimpleStreamListener.idl',
+ 'nsISocketFilter.idl',
+ 'nsISocketTransport.idl',
+ 'nsISocketTransportService.idl',
+ 'nsISpeculativeConnect.idl',
+ 'nsIStandardURL.idl',
+ 'nsIStreamingProtocolController.idl',
+ 'nsIStreamingProtocolService.idl',
+ 'nsIStreamListener.idl',
+ 'nsIStreamListenerTee.idl',
+ 'nsIStreamLoader.idl',
+ 'nsIStreamTransportService.idl',
+ 'nsISyncStreamListener.idl',
+ 'nsISystemProxySettings.idl',
+ 'nsIThreadRetargetableRequest.idl',
+ 'nsIThreadRetargetableStreamListener.idl',
+ 'nsIThrottledInputChannel.idl',
+ 'nsITimedChannel.idl',
+ 'nsITLSServerSocket.idl',
+ 'nsITraceableChannel.idl',
+ 'nsITransport.idl',
+ 'nsIUDPSocket.idl',
+ 'nsIUnicharStreamLoader.idl',
+ 'nsIUploadChannel.idl',
+ 'nsIUploadChannel2.idl',
+ 'nsIURI.idl',
+ 'nsIURIClassifier.idl',
+ 'nsIURIWithBlobImpl.idl',
+ 'nsIURIWithPrincipal.idl',
+ 'nsIURIWithQuery.idl',
+ 'nsIURL.idl',
+ 'nsIURLParser.idl',
+ 'nsPILoadGroupInternal.idl',
+ 'nsPISocketTransportService.idl',
+]
+
+if CONFIG['MOZ_TOOLKIT_SEARCH']:
+ XPIDL_SOURCES += [
+ 'nsIBrowserSearchService.idl',
+ ]
+
+XPIDL_MODULE = 'necko'
+
+EXPORTS += [
+ 'netCore.h',
+ 'nsASocketHandler.h',
+ 'nsAsyncRedirectVerifyHelper.h',
+ 'nsFileStreams.h',
+ 'nsInputStreamPump.h',
+ 'nsMIMEInputStream.h',
+ 'nsNetUtil.h',
+ 'nsNetUtilInlines.h',
+ 'nsReadLine.h',
+ 'nsSerializationHelper.h',
+ 'nsSimpleNestedURI.h',
+ 'nsSimpleURI.h',
+ 'nsStreamListenerWrapper.h',
+ 'nsTemporaryFileInputStream.h',
+ 'nsURIHashKey.h',
+ 'nsURLHelper.h',
+ 'nsURLParsers.h',
+]
+
+EXPORTS.mozilla += [
+ 'LoadContextInfo.h',
+ 'LoadInfo.h',
+ 'LoadTainting.h',
+]
+
+EXPORTS.mozilla.net += [
+ 'CaptivePortalService.h',
+ 'ChannelDiverterChild.h',
+ 'ChannelDiverterParent.h',
+ 'Dashboard.h',
+ 'DashboardTypes.h',
+ 'MemoryDownloader.h',
+ 'Predictor.h',
+ 'ReferrerPolicy.h',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ EXPORTS += [
+ 'NetStatistics.h',
+ ]
+
+UNIFIED_SOURCES += [
+ 'ArrayBufferInputStream.cpp',
+ 'BackgroundFileSaver.cpp',
+ 'CaptivePortalService.cpp',
+ 'ChannelDiverterChild.cpp',
+ 'ChannelDiverterParent.cpp',
+ 'Dashboard.cpp',
+ 'EventTokenBucket.cpp',
+ 'LoadContextInfo.cpp',
+ 'LoadInfo.cpp',
+ 'MemoryDownloader.cpp',
+ 'NetworkActivityMonitor.cpp',
+ 'nsAsyncRedirectVerifyHelper.cpp',
+ 'nsAsyncStreamCopier.cpp',
+ 'nsAuthInformationHolder.cpp',
+ 'nsBase64Encoder.cpp',
+ 'nsBaseChannel.cpp',
+ 'nsBaseContentStream.cpp',
+ 'nsBufferedStreams.cpp',
+ 'nsChannelClassifier.cpp',
+ 'nsDirectoryIndexStream.cpp',
+ 'nsDNSPrefetch.cpp',
+ 'nsDownloader.cpp',
+ 'nsFileStreams.cpp',
+ 'nsIncrementalDownload.cpp',
+ 'nsIncrementalStreamLoader.cpp',
+ 'nsInputStreamChannel.cpp',
+ 'nsInputStreamPump.cpp',
+ 'nsIOService.cpp',
+ 'nsLoadGroup.cpp',
+ 'nsMediaFragmentURIParser.cpp',
+ 'nsMIMEInputStream.cpp',
+ 'nsNetAddr.cpp',
+ 'nsNetUtil.cpp',
+ 'nsPACMan.cpp',
+ 'nsPreloadedStream.cpp',
+ 'nsProtocolProxyService.cpp',
+ 'nsProxyInfo.cpp',
+ 'nsRequestObserverProxy.cpp',
+ 'nsSecCheckWrapChannel.cpp',
+ 'nsSerializationHelper.cpp',
+ 'nsServerSocket.cpp',
+ 'nsSimpleNestedURI.cpp',
+ 'nsSimpleStreamListener.cpp',
+ 'nsSimpleURI.cpp',
+ 'nsSocketTransport2.cpp',
+ 'nsSocketTransportService2.cpp',
+ 'nsStandardURL.cpp',
+ 'nsStreamListenerTee.cpp',
+ 'nsStreamListenerWrapper.cpp',
+ 'nsStreamLoader.cpp',
+ 'nsStreamTransportService.cpp',
+ 'nsSyncStreamListener.cpp',
+ 'nsTemporaryFileInputStream.cpp',
+ 'nsTransportUtils.cpp',
+ 'nsUDPSocket.cpp',
+ 'nsUnicharStreamLoader.cpp',
+ 'nsURLHelper.cpp',
+ 'nsURLParsers.cpp',
+ 'PollableEvent.cpp',
+ 'Predictor.cpp',
+ 'ProxyAutoConfig.cpp',
+ 'RedirectChannelRegistrar.cpp',
+ 'RequestContextService.cpp',
+ 'SimpleBuffer.cpp',
+ 'StreamingProtocolService.cpp',
+ 'ThrottleQueue.cpp',
+ 'Tickler.cpp',
+ 'TLSServerSocket.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ SOURCES += [
+ 'nsURLHelperWin.cpp',
+ 'ShutdownLayer.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'nsURLHelperOSX.cpp',
+ ]
+else:
+ SOURCES += [
+ 'nsURLHelperUnix.cpp',
+ ]
+
+# nsINetworkInfoService support.
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ SOURCES += [
+ 'NetworkInfoServiceWindows.cpp',
+ 'nsNetworkInfoService.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'NetworkInfoServiceCocoa.cpp',
+ 'nsNetworkInfoService.cpp',
+ ]
+elif CONFIG['OS_ARCH'] == 'Linux':
+ SOURCES += [
+ 'NetworkInfoServiceLinux.cpp',
+ 'nsNetworkInfoService.cpp',
+ ]
+
+EXTRA_JS_MODULES += [
+ 'NetUtil.jsm',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/docshell/base',
+ '/dom/base',
+ '/netwerk/protocol/http',
+ '/netwerk/socket',
+ '/security/pkix/include'
+]
+
+if 'rtsp' in CONFIG['NECKO_PROTOCOLS']:
+ LOCAL_INCLUDES += [
+ '/netwerk/protocol/rtsp/controller',
+ '/netwerk/protocol/rtsp/rtsp',
+ ]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl
new file mode 100644
index 0000000000..2eea9550ad
--- /dev/null
+++ b/netwerk/base/mozIThirdPartyUtil.idl
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface mozIDOMWindowProxy;
+interface nsIChannel;
+
+/**
+ * Utility functions for determining whether a given URI, channel, or window
+ * hierarchy is third party with respect to a known URI.
+ */
+[scriptable, uuid(fd82700e-ffb4-4932-b7d6-08f0b5697dda)]
+interface mozIThirdPartyUtil : nsISupports
+{
+ /**
+ * isThirdPartyURI
+ *
+ * Determine whether two URIs are third party with respect to each other.
+ * This is determined by computing the base domain for both URIs. If they can
+ * be determined, and the base domains match, the request is defined as first
+ * party. If it cannot be determined because one or both URIs do not have a
+ * base domain (for instance, in the case of IP addresses, host aliases such
+ * as 'localhost', or a file:// URI), an exact string comparison on host is
+ * performed.
+ *
+ * For example, the URI "http://mail.google.com/" is not third party with
+ * respect to "http://images.google.com/", but "http://mail.yahoo.com/" and
+ * "http://192.168.1.1/" are.
+ *
+ * @return true if aFirstURI is third party with respect to aSecondURI.
+ *
+ * @throws if either URI is null, has a malformed host, or has an empty host
+ * and is not a file:// URI.
+ */
+ boolean isThirdPartyURI(in nsIURI aFirstURI, in nsIURI aSecondURI);
+
+ /**
+ * isThirdPartyWindow
+ *
+ * Determine whether the given window hierarchy is third party. This is done
+ * as follows:
+ *
+ * 1) Obtain the URI of the principal associated with 'aWindow'. Call this the
+ * 'bottom URI'.
+ * 2) If 'aURI' is provided, determine if it is third party with respect to
+ * the bottom URI. If so, return.
+ * 3) Find the same-type parent window, if there is one, and its URI.
+ * Determine whether it is third party with respect to the bottom URI. If
+ * so, return.
+ *
+ * Therefore, each level in the window hierarchy is tested. (This means that
+ * nested iframes with different base domains, even though the bottommost and
+ * topmost URIs might be equal, will be considered third party.)
+ *
+ * @param aWindow
+ * The bottommost window in the hierarchy.
+ * @param aURI
+ * A URI to test against. If null, the URI of the principal
+ * associated with 'aWindow' will be used.
+ *
+ * For example, if 'aURI' is "http://mail.google.com/", 'aWindow' has a URI
+ * of "http://google.com/", and its parent is the topmost content window with
+ * a URI of "http://mozilla.com", the result will be true.
+ *
+ * @return true if 'aURI' is third party with respect to any of the URIs
+ * associated with aWindow and its same-type parents.
+ *
+ * @throws if aWindow is null; the same-type parent of any window in the
+ * hierarchy cannot be determined; or the URI associated with any
+ * window in the hierarchy is null, has a malformed host, or has an
+ * empty host and is not a file:// URI.
+ *
+ * @see isThirdPartyURI
+ */
+ boolean isThirdPartyWindow(in mozIDOMWindowProxy aWindow, [optional] in nsIURI aURI);
+
+ /**
+ * isThirdPartyChannel
+ *
+ * Determine whether the given channel and its content window hierarchy is
+ * third party. This is done as follows:
+ *
+ * 1) If 'aChannel' is an nsIHttpChannel and has the
+ * 'forceAllowThirdPartyCookie' property set, then:
+ * a) If 'aURI' is null, return false.
+ * b) Otherwise, find the URI of the channel, determine whether it is
+ * foreign with respect to 'aURI', and return.
+ * 2) Find the URI of the channel and determine whether it is third party with
+ * respect to the URI of the channel. If so, return.
+ * 3) Obtain the bottommost nsIDOMWindow, and its same-type parent if it
+ * exists, from the channel's notification callbacks. Then:
+ * a) If the parent is the same as the bottommost window, and the channel
+ * has the LOAD_DOCUMENT_URI flag set, return false. This represents the
+ * case where a toplevel load is occurring and the window's URI has not
+ * yet been updated. (We have already checked that 'aURI' is not foreign
+ * with respect to the channel URI.)
+ * b) Otherwise, return the result of isThirdPartyWindow with arguments
+ * of the channel's bottommost window and the channel URI, respectively.
+ *
+ * Therefore, both the channel's URI and each level in the window hierarchy
+ * associated with the channel is tested.
+ *
+ * @param aChannel
+ * The channel associated with the load.
+ * @param aURI
+ * A URI to test against. If null, the URI of the channel will be used.
+ *
+ * For example, if 'aURI' is "http://mail.google.com/", 'aChannel' has a URI
+ * of "http://google.com/", and its parent is the topmost content window with
+ * a URI of "http://mozilla.com", the result will be true.
+ *
+ * @return true if aURI is third party with respect to the channel URI or any
+ * of the URIs associated with the same-type window hierarchy of the
+ * channel.
+ *
+ * @throws if 'aChannel' is null; the channel has no notification callbacks or
+ * an associated window; or isThirdPartyWindow throws.
+ *
+ * @see isThirdPartyWindow
+ */
+ boolean isThirdPartyChannel(in nsIChannel aChannel, [optional] in nsIURI aURI);
+
+ /**
+ * getBaseDomain
+ *
+ * Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
+ * "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
+ * dot may be present. If aHostURI is an IP address, an alias such as
+ * 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
+ * be the exact host. The result of this function should only be used in exact
+ * string comparisons, since substring comparisons will not be valid for the
+ * special cases elided above.
+ *
+ * @param aHostURI
+ * The URI to analyze.
+ *
+ * @return the base domain.
+ */
+ AUTF8String getBaseDomain(in nsIURI aHostURI);
+
+ /**
+ * getURIFromWindow
+ *
+ * Returns the URI associated with the script object principal for the
+ * window.
+ */
+ nsIURI getURIFromWindow(in mozIDOMWindowProxy aWindow);
+
+ /**
+ * getTopWindowForChannel
+ *
+ * Returns the top-level window associated with the given channel.
+ */
+ mozIDOMWindowProxy getTopWindowForChannel(in nsIChannel aChannel);
+};
+
+%{ C++
+/**
+ * The mozIThirdPartyUtil implementation is an XPCOM service registered
+ * under the ContractID:
+ */
+#define THIRDPARTYUTIL_CONTRACTID "@mozilla.org/thirdpartyutil;1"
+%}
+
diff --git a/netwerk/base/netCore.h b/netwerk/base/netCore.h
new file mode 100644
index 0000000000..7a0738cf94
--- /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..c15daecd85
--- /dev/null
+++ b/netwerk/base/nsASocketHandler.h
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsASocketHandler_h__
+#define nsASocketHandler_h__
+
+// socket handler used by nsISocketTransportService.
+// methods are only called on the socket thread.
+
+class nsASocketHandler : public nsISupports
+{
+public:
+ nsASocketHandler()
+ : mCondition(NS_OK)
+ , mPollFlags(0)
+ , mPollTimeout(UINT16_MAX)
+ , mIsPrivate(false)
+ {}
+
+ //
+ // this condition variable will be checked to determine if the socket
+ // handler should be detached. it must only be accessed on the socket
+ // thread.
+ //
+ nsresult mCondition;
+
+ //
+ // these flags can only be modified on the socket transport thread.
+ // the socket transport service will check these flags before calling
+ // PR_Poll.
+ //
+ uint16_t mPollFlags;
+
+ //
+ // this value specifies the maximum amount of time in seconds that may be
+ // spent waiting for activity on this socket. if this timeout is reached,
+ // then OnSocketReady will be called with outFlags = -1.
+ //
+ // the default value for this member is UINT16_MAX, which disables the
+ // timeout error checking. (i.e., a timeout value of UINT16_MAX is
+ // never reached.)
+ //
+ uint16_t mPollTimeout;
+
+ bool mIsPrivate;
+
+ //
+ // called to service a socket
+ //
+ // params:
+ // socketRef - socket identifier
+ // fd - socket file descriptor
+ // outFlags - value of PR_PollDesc::out_flags after PR_Poll returns
+ // or -1 if a timeout occurred
+ //
+ virtual void OnSocketReady(PRFileDesc *fd, int16_t outFlags) = 0;
+
+ //
+ // called when a socket is no longer under the control of the socket
+ // transport service. the socket handler may close the socket at this
+ // point. after this call returns, the handler will no longer be owned
+ // by the socket transport service.
+ //
+ virtual void OnSocketDetached(PRFileDesc *fd) = 0;
+
+ //
+ // called to determine if the socket is for a local peer.
+ // when used for server sockets, indicates if it only accepts local
+ // connections.
+ //
+ virtual void IsLocal(bool *aIsLocal) = 0;
+
+ //
+ // called to determine if this socket should not be terminated when Gecko
+ // is turned offline. This is mostly useful for the debugging server
+ // socket.
+ //
+ virtual void KeepWhenOffline(bool *aKeepWhenOffline)
+ {
+ *aKeepWhenOffline = false;
+ }
+
+ //
+ // called when global pref for keepalive has changed.
+ //
+ virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) { }
+
+ //
+ // returns the number of bytes sent/transmitted over the socket
+ //
+ virtual uint64_t ByteCountSent() = 0;
+ virtual uint64_t ByteCountReceived() = 0;
+};
+
+#endif // !nsASocketHandler_h__
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
new file mode 100644
index 0000000000..3b19b93c76
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+
+#include "nsIOService.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsILoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gRedirectLog("nsRedirect");
+#undef LOG
+#define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper,
+ nsIAsyncVerifyRedirectCallback,
+ nsIRunnable)
+
+class nsAsyncVerifyRedirectCallbackEvent : public Runnable {
+public:
+ nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback *cb,
+ nsresult result)
+ : mCallback(cb), mResult(result) {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ LOG(("nsAsyncVerifyRedirectCallbackEvent::Run() "
+ "callback to %p with result %x",
+ mCallback.get(), mResult));
+ (void) mCallback->OnRedirectVerifyCallback(mResult);
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback;
+ nsresult mResult;
+};
+
+nsAsyncRedirectVerifyHelper::nsAsyncRedirectVerifyHelper()
+ : mFlags(0),
+ mWaitingForRedirectCallback(false),
+ mCallbackInitiated(false),
+ mExpectedCallbacks(0),
+ mResult(NS_OK)
+{
+}
+
+nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper()
+{
+ NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0,
+ "Did not receive all required callbacks!");
+}
+
+nsresult
+nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags, bool synchronize)
+{
+ LOG(("nsAsyncRedirectVerifyHelper::Init() "
+ "oldChan=%p newChan=%p", oldChan, newChan));
+ mOldChan = oldChan;
+ mNewChan = newChan;
+ mFlags = flags;
+ mCallbackThread = do_GetCurrentThread();
+
+ if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE))) {
+ nsCOMPtr<nsILoadInfo> loadInfo = oldChan->GetLoadInfo();
+ if (loadInfo && loadInfo->GetDontFollowRedirects()) {
+ ExplicitCallback(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+ }
+
+ if (synchronize)
+ mWaitingForRedirectCallback = true;
+
+ nsresult rv;
+ rv = NS_DispatchToMainThread(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (synchronize) {
+ nsIThread *thread = NS_GetCurrentThread();
+ while (mWaitingForRedirectCallback) {
+ if (!NS_ProcessNextEvent(thread)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result)
+{
+ LOG(("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() "
+ "result=%x expectedCBs=%u mResult=%x",
+ result, mExpectedCallbacks, mResult));
+
+ MOZ_DIAGNOSTIC_ASSERT(mExpectedCallbacks > 0,
+ "OnRedirectVerifyCallback called more times than expected");
+ if (mExpectedCallbacks <= 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ --mExpectedCallbacks;
+
+ // If response indicates failure we may call back immediately
+ if (NS_FAILED(result)) {
+ // We chose to store the first failure-value (as opposed to the last)
+ if (NS_SUCCEEDED(mResult))
+ mResult = result;
+
+ // If InitCallback() has been called, just invoke the callback and
+ // return. Otherwise it will be invoked from InitCallback()
+ if (mCallbackInitiated) {
+ ExplicitCallback(mResult);
+ return NS_OK;
+ }
+ }
+
+ // If the expected-counter is in balance and InitCallback() was called, all
+ // sinks have agreed that the redirect is ok and we can invoke our callback
+ if (mCallbackInitiated && mExpectedCallbacks == 0) {
+ ExplicitCallback(mResult);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(nsIChannelEventSink *sink,
+ nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t flags)
+{
+ LOG(("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() "
+ "sink=%p expectedCBs=%u mResult=%x",
+ sink, mExpectedCallbacks, mResult));
+
+ ++mExpectedCallbacks;
+
+ if (IsOldChannelCanceled()) {
+ LOG((" old channel has been canceled, cancel the redirect by "
+ "emulating OnRedirectVerifyCallback..."));
+ (void) OnRedirectVerifyCallback(NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ nsresult rv =
+ sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
+
+ LOG((" result=%x expectedCBs=%u", rv, mExpectedCallbacks));
+
+ // If the sink returns failure from this call the redirect is vetoed. We
+ // emulate a callback from the sink in this case in order to perform all
+ // the necessary logic.
+ if (NS_FAILED(rv)) {
+ LOG((" emulating OnRedirectVerifyCallback..."));
+ (void) OnRedirectVerifyCallback(rv);
+ }
+
+ return rv; // Return the actual status since our caller may need it
+}
+
+void
+nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result)
+{
+ LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "result=%x expectedCBs=%u mCallbackInitiated=%u mResult=%x",
+ result, mExpectedCallbacks, mCallbackInitiated, mResult));
+
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback>
+ callback(do_QueryInterface(mOldChan));
+
+ if (!callback || !mCallbackThread) {
+ LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "callback=%p mCallbackThread=%p", callback.get(), mCallbackThread.get()));
+ return;
+ }
+
+ mCallbackInitiated = false; // reset to ensure only one callback
+ mWaitingForRedirectCallback = false;
+
+ // Now, dispatch the callback on the event-target which called Init()
+ nsCOMPtr<nsIRunnable> event =
+ new nsAsyncVerifyRedirectCallbackEvent(callback, result);
+ if (!event) {
+ NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "failed creating callback event!");
+ return;
+ }
+ nsresult rv = mCallbackThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "failed dispatching callback event!");
+ } else {
+ LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "dispatched callback event=%p", event.get()));
+ }
+
+}
+
+void
+nsAsyncRedirectVerifyHelper::InitCallback()
+{
+ LOG(("nsAsyncRedirectVerifyHelper::InitCallback() "
+ "expectedCBs=%d mResult=%x", mExpectedCallbacks, mResult));
+
+ mCallbackInitiated = true;
+
+ // Invoke the callback if we are done
+ if (mExpectedCallbacks == 0)
+ ExplicitCallback(mResult);
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::Run()
+{
+ /* If the channel got canceled after it fired AsyncOnChannelRedirect
+ * and before we got here, mostly because docloader load has been canceled,
+ * we must completely ignore this notification and prevent any further
+ * notification.
+ */
+ if (IsOldChannelCanceled()) {
+ ExplicitCallback(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+
+ // First, the global observer
+ NS_ASSERTION(gIOService, "Must have an IO service at this point");
+ LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService..."));
+ nsresult rv = gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan,
+ mFlags, this);
+ if (NS_FAILED(rv)) {
+ ExplicitCallback(rv);
+ return NS_OK;
+ }
+
+ // Now, the per-channel observers
+ nsCOMPtr<nsIChannelEventSink> sink;
+ NS_QueryNotificationCallbacks(mOldChan, sink);
+ if (sink) {
+ LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink..."));
+ rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags);
+ }
+
+ // All invocations to AsyncOnChannelRedirect has been done - call
+ // InitCallback() to flag this
+ InitCallback();
+ return NS_OK;
+}
+
+bool
+nsAsyncRedirectVerifyHelper::IsOldChannelCanceled()
+{
+ bool canceled;
+ nsCOMPtr<nsIHttpChannelInternal> oldChannelInternal =
+ do_QueryInterface(mOldChan);
+ if (oldChannelInternal) {
+ oldChannelInternal->GetCanceled(&canceled);
+ if (canceled) {
+ return true;
+ }
+ } else if (mOldChan) {
+ // For non-HTTP channels check on the status, failure
+ // indicates the channel has probably been canceled.
+ nsresult status = NS_ERROR_FAILURE;
+ mOldChan->GetStatus(&status);
+ if (NS_FAILED(status)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.h b/netwerk/base/nsAsyncRedirectVerifyHelper.h
new file mode 100644
index 0000000000..f67785498f
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAsyncRedirectVerifyHelper_h
+#define nsAsyncRedirectVerifyHelper_h
+
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+/**
+ * This class simplifies call of OnChannelRedirect of IOService and
+ * the sink bound with the channel being redirected while the result of
+ * redirect decision is returned through the callback.
+ */
+class nsAsyncRedirectVerifyHelper final : public nsIRunnable,
+ public nsIAsyncVerifyRedirectCallback
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+public:
+ nsAsyncRedirectVerifyHelper();
+
+ /*
+ * Calls AsyncOnChannelRedirect() on the given sink with the given
+ * channels and flags. Keeps track of number of async callbacks to expect.
+ */
+ nsresult DelegateOnChannelRedirect(nsIChannelEventSink *sink,
+ nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t flags);
+
+ /**
+ * Initialize and run the chain of AsyncOnChannelRedirect calls. OldChannel
+ * is QI'ed for nsIAsyncVerifyRedirectCallback. The result of the redirect
+ * decision is passed through this interface back to the oldChannel.
+ *
+ * @param oldChan
+ * channel being redirected, MUST implement
+ * nsIAsyncVerifyRedirectCallback
+ * @param newChan
+ * target of the redirect channel
+ * @param flags
+ * redirect flags
+ * @param synchronize
+ * set to TRUE if you want the Init method wait synchronously for
+ * all redirect callbacks
+ */
+ nsresult Init(nsIChannel* oldChan,
+ nsIChannel* newChan,
+ uint32_t flags,
+ bool synchronize = false);
+
+protected:
+ nsCOMPtr<nsIChannel> mOldChan;
+ nsCOMPtr<nsIChannel> mNewChan;
+ uint32_t mFlags;
+ bool mWaitingForRedirectCallback;
+ nsCOMPtr<nsIThread> mCallbackThread;
+ bool mCallbackInitiated;
+ int32_t mExpectedCallbacks;
+ nsresult mResult; // value passed to callback
+
+ void InitCallback();
+
+ /**
+ * Calls back to |oldChan| as described in Init()
+ */
+ void ExplicitCallback(nsresult result);
+
+private:
+ ~nsAsyncRedirectVerifyHelper();
+
+ bool IsOldChannelCanceled();
+};
+
+/*
+ * Helper to make the call-stack handle some control-flow for us
+ */
+class nsAsyncRedirectAutoCallback
+{
+public:
+ explicit nsAsyncRedirectAutoCallback(nsIAsyncVerifyRedirectCallback* aCallback)
+ : mCallback(aCallback)
+ {
+ mResult = NS_OK;
+ }
+ ~nsAsyncRedirectAutoCallback()
+ {
+ if (mCallback)
+ mCallback->OnRedirectVerifyCallback(mResult);
+ }
+ /*
+ * Call this is you want it to call back with a different result-code
+ */
+ void SetResult(nsresult aRes)
+ {
+ mResult = aRes;
+ }
+ /*
+ * Call this is you want to avoid the callback
+ */
+ void DontCallback()
+ {
+ mCallback = nullptr;
+ }
+private:
+ nsIAsyncVerifyRedirectCallback* mCallback;
+ nsresult mResult;
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsAsyncStreamCopier.cpp b/netwerk/base/nsAsyncStreamCopier.cpp
new file mode 100644
index 0000000000..6eec29d614
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.cpp
@@ -0,0 +1,419 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAsyncStreamCopier.h"
+#include "nsIOService.h"
+#include "nsIEventTarget.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIBufferedStreams.h"
+#include "nsIRequestObserver.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+#undef LOG
+//
+// MOZ_LOG=nsStreamCopier:5
+//
+static LazyLogModule gStreamCopierLog("nsStreamCopier");
+#define LOG(args) MOZ_LOG(gStreamCopierLog, mozilla::LogLevel::Debug, args)
+
+/**
+ * An event used to perform initialization off the main thread.
+ */
+class AsyncApplyBufferingPolicyEvent final: public Runnable
+{
+public:
+ /**
+ * @param aCopier
+ * The nsAsyncStreamCopier requesting the information.
+ */
+ explicit AsyncApplyBufferingPolicyEvent(nsAsyncStreamCopier* aCopier)
+ : mCopier(aCopier)
+ , mTarget(NS_GetCurrentThread())
+ { }
+ NS_IMETHOD Run() override
+ {
+ nsresult rv = mCopier->ApplyBufferingPolicy();
+ if (NS_FAILED(rv)) {
+ mCopier->Cancel(rv);
+ return NS_OK;
+ }
+
+ rv = mTarget->Dispatch(NewRunnableMethod(mCopier,
+ &nsAsyncStreamCopier::AsyncCopyInternal),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (NS_FAILED(rv)) {
+ mCopier->Cancel(rv);
+ }
+ return NS_OK;
+ }
+private:
+ RefPtr<nsAsyncStreamCopier> mCopier;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+
+
+//-----------------------------------------------------------------------------
+
+nsAsyncStreamCopier::nsAsyncStreamCopier()
+ : mLock("nsAsyncStreamCopier.mLock")
+ , mMode(NS_ASYNCCOPY_VIA_READSEGMENTS)
+ , mChunkSize(nsIOService::gDefaultSegmentSize)
+ , mStatus(NS_OK)
+ , mIsPending(false)
+ , mShouldSniffBuffering(false)
+{
+ LOG(("Creating nsAsyncStreamCopier @%x\n", this));
+}
+
+nsAsyncStreamCopier::~nsAsyncStreamCopier()
+{
+ LOG(("Destroying nsAsyncStreamCopier @%x\n", this));
+}
+
+bool
+nsAsyncStreamCopier::IsComplete(nsresult *status)
+{
+ MutexAutoLock lock(mLock);
+ if (status)
+ *status = mStatus;
+ return !mIsPending;
+}
+
+nsIRequest*
+nsAsyncStreamCopier::AsRequest()
+{
+ return static_cast<nsIRequest*>(static_cast<nsIAsyncStreamCopier*>(this));
+}
+
+void
+nsAsyncStreamCopier::Complete(nsresult status)
+{
+ LOG(("nsAsyncStreamCopier::Complete [this=%p status=%x]\n", this, status));
+
+ nsCOMPtr<nsIRequestObserver> observer;
+ nsCOMPtr<nsISupports> ctx;
+ {
+ MutexAutoLock lock(mLock);
+ mCopierCtx = nullptr;
+
+ if (mIsPending) {
+ mIsPending = false;
+ mStatus = status;
+
+ // setup OnStopRequest callback and release references...
+ observer = mObserver;
+ mObserver = nullptr;
+ }
+ }
+
+ if (observer) {
+ LOG((" calling OnStopRequest [status=%x]\n", status));
+ observer->OnStopRequest(AsRequest(), ctx, status);
+ }
+}
+
+void
+nsAsyncStreamCopier::OnAsyncCopyComplete(void *closure, nsresult status)
+{
+ nsAsyncStreamCopier *self = (nsAsyncStreamCopier *) closure;
+ self->Complete(status);
+ NS_RELEASE(self); // addref'd in AsyncCopy
+}
+
+//-----------------------------------------------------------------------------
+// nsISupports
+
+// We cannot use simply NS_IMPL_ISUPPORTSx as both
+// nsIAsyncStreamCopier and nsIAsyncStreamCopier2 implement nsIRequest
+
+NS_IMPL_ADDREF(nsAsyncStreamCopier)
+NS_IMPL_RELEASE(nsAsyncStreamCopier)
+NS_INTERFACE_TABLE_HEAD(nsAsyncStreamCopier)
+NS_INTERFACE_TABLE_BEGIN
+NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier)
+NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier2)
+NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsIRequest, nsIAsyncStreamCopier)
+NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsISupports, nsIAsyncStreamCopier)
+NS_INTERFACE_TABLE_END
+NS_INTERFACE_TABLE_TAIL
+
+//-----------------------------------------------------------------------------
+// nsIRequest
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetName(nsACString &name)
+{
+ name.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::IsPending(bool *result)
+{
+ *result = !IsComplete();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetStatus(nsresult *status)
+{
+ IsComplete(status);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Cancel(nsresult status)
+{
+ nsCOMPtr<nsISupports> copierCtx;
+ {
+ MutexAutoLock lock(mLock);
+ if (!mIsPending)
+ return NS_OK;
+ copierCtx.swap(mCopierCtx);
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ NS_WARNING("cancel with non-failure status code");
+ status = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (copierCtx)
+ NS_CancelAsyncCopy(copierCtx, status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Suspend()
+{
+ NS_NOTREACHED("nsAsyncStreamCopier::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Resume()
+{
+ NS_NOTREACHED("nsAsyncStreamCopier::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = LOAD_NORMAL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ return NS_OK;
+}
+
+nsresult
+nsAsyncStreamCopier::InitInternal(nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ uint32_t chunkSize,
+ bool closeSource,
+ bool closeSink)
+{
+ NS_ASSERTION(!mSource && !mSink, "Init() called more than once");
+ if (chunkSize == 0) {
+ chunkSize = nsIOService::gDefaultSegmentSize;
+ }
+ mChunkSize = chunkSize;
+
+ mSource = source;
+ mSink = sink;
+ mCloseSource = closeSource;
+ mCloseSink = closeSink;
+
+ if (target) {
+ mTarget = target;
+ } else {
+ nsresult rv;
+ mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ bool sourceBuffered,
+ bool sinkBuffered,
+ uint32_t chunkSize,
+ bool closeSource,
+ bool closeSink)
+{
+ NS_ASSERTION(sourceBuffered || sinkBuffered, "at least one stream must be buffered");
+ mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS
+ : NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ uint32_t chunkSize,
+ bool closeSource,
+ bool closeSink)
+{
+ mShouldSniffBuffering = true;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+/**
+ * Detect whether the input or the output stream is buffered,
+ * bufferize one of them if neither is buffered.
+ */
+nsresult
+nsAsyncStreamCopier::ApplyBufferingPolicy()
+{
+ // This function causes I/O, it must not be executed on the main
+ // thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (NS_OutputStreamIsBuffered(mSink)) {
+ // Sink is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ return NS_OK;
+ }
+ if (NS_InputStreamIsBuffered(mSource)) {
+ // Source is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_READSEGMENTS;
+ return NS_OK;
+ }
+
+ // No buffering, let's buffer the sink
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> sink =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = sink->Init(mSink, mChunkSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ mSink = sink;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver *observer, nsISupports *ctx)
+{
+ LOG(("nsAsyncStreamCopier::AsyncCopy [this=%p observer=%x]\n", this, observer));
+
+ NS_ASSERTION(mSource && mSink, "not initialized");
+ nsresult rv;
+
+ if (observer) {
+ // build proxy for observer events
+ rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), observer, ctx);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // from this point forward, AsyncCopy is going to return NS_OK. any errors
+ // will be reported via OnStopRequest.
+ mIsPending = true;
+
+ if (mObserver) {
+ rv = mObserver->OnStartRequest(AsRequest(), nullptr);
+ if (NS_FAILED(rv))
+ Cancel(rv);
+ }
+
+ if (!mShouldSniffBuffering) {
+ // No buffer sniffing required, let's proceed
+ AsyncCopyInternal();
+ return NS_OK;
+ }
+
+ if (NS_IsMainThread()) {
+ // Don't perform buffer sniffing on the main thread
+ nsCOMPtr<nsIRunnable> event = new AsyncApplyBufferingPolicyEvent(this);
+ rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ return NS_OK;
+ }
+
+ // We're not going to block the main thread, so let's sniff here
+ rv = ApplyBufferingPolicy();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ AsyncCopyInternal();
+ return NS_OK;
+}
+
+// Launch async copy.
+// All errors are reported through the observer.
+void
+nsAsyncStreamCopier::AsyncCopyInternal()
+{
+ MOZ_ASSERT(mMode == NS_ASYNCCOPY_VIA_READSEGMENTS
+ || mMode == NS_ASYNCCOPY_VIA_WRITESEGMENTS);
+
+ nsresult rv;
+ // we want to receive progress notifications; release happens in
+ // OnAsyncCopyComplete.
+ NS_ADDREF_THIS();
+ {
+ MutexAutoLock lock(mLock);
+ rv = NS_AsyncCopy(mSource, mSink, mTarget, mMode, mChunkSize,
+ OnAsyncCopyComplete, this, mCloseSource, mCloseSink,
+ getter_AddRefs(mCopierCtx));
+ }
+ if (NS_FAILED(rv)) {
+ NS_RELEASE_THIS();
+ Cancel(rv);
+ }
+}
+
+
diff --git a/netwerk/base/nsAsyncStreamCopier.h b/netwerk/base/nsAsyncStreamCopier.h
new file mode 100644
index 0000000000..7529a327aa
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.h
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAsyncStreamCopier_h__
+#define nsAsyncStreamCopier_h__
+
+#include "nsIAsyncStreamCopier.h"
+#include "nsIAsyncStreamCopier2.h"
+#include "mozilla/Mutex.h"
+#include "nsStreamUtils.h"
+#include "nsCOMPtr.h"
+
+class nsIRequestObserver;
+
+//-----------------------------------------------------------------------------
+
+class nsAsyncStreamCopier final : public nsIAsyncStreamCopier,
+ nsIAsyncStreamCopier2
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIASYNCSTREAMCOPIER
+
+ // nsIAsyncStreamCopier2
+ // We declare it by hand instead of NS_DECL_NSIASYNCSTREAMCOPIER2
+ // as nsIAsyncStreamCopier2 duplicates methods of nsIAsyncStreamCopier
+ NS_IMETHOD Init(nsIInputStream *aSource,
+ nsIOutputStream *aSink,
+ nsIEventTarget *aTarget,
+ uint32_t aChunkSize,
+ bool aCloseSource,
+ bool aCloseSink) override;
+
+ nsAsyncStreamCopier();
+
+ //-------------------------------------------------------------------------
+ // these methods may be called on any thread
+
+ bool IsComplete(nsresult *status = nullptr);
+ void Complete(nsresult status);
+
+private:
+ virtual ~nsAsyncStreamCopier();
+
+ nsresult InitInternal(nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ uint32_t chunkSize,
+ bool closeSource,
+ bool closeSink);
+
+ static void OnAsyncCopyComplete(void *, nsresult);
+
+ void AsyncCopyInternal();
+ nsresult ApplyBufferingPolicy();
+ nsIRequest* AsRequest();
+
+ nsCOMPtr<nsIInputStream> mSource;
+ nsCOMPtr<nsIOutputStream> mSink;
+
+ nsCOMPtr<nsIRequestObserver> mObserver;
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+
+ nsCOMPtr<nsISupports> mCopierCtx;
+
+ mozilla::Mutex mLock;
+
+ nsAsyncCopyMode mMode;
+ uint32_t mChunkSize;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mCloseSource;
+ bool mCloseSink;
+ bool mShouldSniffBuffering;
+
+ friend class ProceedWithAsyncCopy;
+ friend class AsyncApplyBufferingPolicyEvent;
+};
+
+#endif // !nsAsyncStreamCopier_h__
diff --git a/netwerk/base/nsAuthInformationHolder.cpp b/netwerk/base/nsAuthInformationHolder.cpp
new file mode 100644
index 0000000000..f52ff5454a
--- /dev/null
+++ b/netwerk/base/nsAuthInformationHolder.cpp
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAuthInformationHolder.h"
+
+NS_IMPL_ISUPPORTS(nsAuthInformationHolder, nsIAuthInformation)
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetFlags(uint32_t* aFlags)
+{
+ *aFlags = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetRealm(nsAString& aRealm)
+{
+ aRealm = mRealm;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetAuthenticationScheme(nsACString& aScheme)
+{
+ aScheme = mAuthType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetUsername(nsAString& aUserName)
+{
+ aUserName = mUser;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetUsername(const nsAString& aUserName)
+{
+ if (!(mFlags & ONLY_PASSWORD))
+ mUser = aUserName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetPassword(nsAString& aPassword)
+{
+ aPassword = mPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetPassword(const nsAString& aPassword)
+{
+ mPassword = aPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetDomain(nsAString& aDomain)
+{
+ aDomain = mDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetDomain(const nsAString& aDomain)
+{
+ if (mFlags & NEED_DOMAIN)
+ mDomain = aDomain;
+ return NS_OK;
+}
+
+
diff --git a/netwerk/base/nsAuthInformationHolder.h b/netwerk/base/nsAuthInformationHolder.h
new file mode 100644
index 0000000000..b24bc743f8
--- /dev/null
+++ b/netwerk/base/nsAuthInformationHolder.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef NSAUTHINFORMATIONHOLDER_H_
+#define NSAUTHINFORMATIONHOLDER_H_
+
+#include "nsIAuthInformation.h"
+#include "nsString.h"
+
+class nsAuthInformationHolder : public nsIAuthInformation {
+
+protected:
+ virtual ~nsAuthInformationHolder() {}
+
+public:
+ // aAuthType must be ASCII
+ nsAuthInformationHolder(uint32_t aFlags, const nsString& aRealm,
+ const nsCString& aAuthType)
+ : mFlags(aFlags), mRealm(aRealm), mAuthType(aAuthType) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTHINFORMATION
+
+ const nsString& User() const { return mUser; }
+ const nsString& Password() const { return mPassword; }
+ const nsString& Domain() const { return mDomain; }
+
+ /**
+ * This method can be used to initialize the username when the
+ * ONLY_PASSWORD flag is set.
+ */
+ void SetUserInternal(const nsString& aUsername) {
+ mUser = aUsername;
+ }
+private:
+ nsString mUser;
+ nsString mPassword;
+ nsString mDomain;
+
+ uint32_t mFlags;
+ nsString mRealm;
+ nsCString mAuthType;
+};
+
+
+#endif
diff --git a/netwerk/base/nsBase64Encoder.cpp b/netwerk/base/nsBase64Encoder.cpp
new file mode 100644
index 0000000000..f112be7508
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.cpp
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBase64Encoder.h"
+
+#include "plbase64.h"
+#include "prmem.h"
+
+NS_IMPL_ISUPPORTS(nsBase64Encoder, nsIOutputStream)
+
+NS_IMETHODIMP
+nsBase64Encoder::Close()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval)
+{
+ mData.Append(aBuf, aCount);
+ *_retval = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::WriteFrom(nsIInputStream* aStream, uint32_t aCount,
+ uint32_t* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::IsNonBlocking(bool* aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+nsresult
+nsBase64Encoder::Finish(nsCSubstring& result)
+{
+ char* b64 = PL_Base64Encode(mData.get(), mData.Length(), nullptr);
+ if (!b64)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ result.Assign(b64);
+ PR_Free(b64);
+ // Free unneeded memory and allow reusing the object
+ mData.Truncate();
+ return NS_OK;
+}
diff --git a/netwerk/base/nsBase64Encoder.h b/netwerk/base/nsBase64Encoder.h
new file mode 100644
index 0000000000..ff61de51e8
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSBASE64ENCODER_H_
+#define NSBASE64ENCODER_H_
+
+#include "nsIOutputStream.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+/**
+ * A base64 encoder. Usage: Instantiate class, write to it using
+ * Write(), then call Finish() to get the base64-encoded data.
+ */
+class nsBase64Encoder final : public nsIOutputStream {
+ public:
+ nsBase64Encoder() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsresult Finish(nsCSubstring& _result);
+ private:
+ ~nsBase64Encoder() {}
+
+ /// The data written to this stream. nsCString can deal fine with
+ /// binary data.
+ nsCString mData;
+};
+
+#endif
diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp
new file mode 100644
index 0000000000..200804c1e6
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -0,0 +1,937 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBaseChannel.h"
+#include "nsContentUtils.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsMimeTypes.h"
+#include "nsIContentSniffer.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsMimeTypes.h"
+#include "nsIHttpEventSink.h"
+#include "nsIHttpChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIStreamConverterService.h"
+#include "nsChannelClassifier.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsProxyRelease.h"
+#include "nsXULAppAPI.h"
+#include "nsContentSecurityManager.h"
+#include "LoadInfo.h"
+#include "nsServiceManagerUtils.h"
+
+// This class is used to suspend a request across a function scope.
+class ScopedRequestSuspender {
+public:
+ explicit ScopedRequestSuspender(nsIRequest *request)
+ : mRequest(request) {
+ if (mRequest && NS_FAILED(mRequest->Suspend())) {
+ NS_WARNING("Couldn't suspend pump");
+ mRequest = nullptr;
+ }
+ }
+ ~ScopedRequestSuspender() {
+ if (mRequest)
+ mRequest->Resume();
+ }
+private:
+ nsIRequest *mRequest;
+};
+
+// Used to suspend data events from mPump within a function scope. This is
+// usually needed when a function makes callbacks that could process events.
+#define SUSPEND_PUMP_FOR_SCOPE() \
+ ScopedRequestSuspender pump_suspender__(mPump)
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel
+
+nsBaseChannel::nsBaseChannel()
+ : mLoadFlags(LOAD_NORMAL)
+ , mQueriedProgressSink(true)
+ , mSynthProgressEvents(false)
+ , mAllowThreadRetargeting(true)
+ , mWaitingOnAsyncRedirect(false)
+ , mStatus(NS_OK)
+ , mContentDispositionHint(UINT32_MAX)
+ , mContentLength(-1)
+ , mWasOpened(false)
+{
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+}
+
+nsBaseChannel::~nsBaseChannel()
+{
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+}
+
+nsresult
+nsBaseChannel::Redirect(nsIChannel *newChannel, uint32_t redirectFlags,
+ bool openNewChannel)
+{
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ // Transfer properties
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE);
+
+ // make a copy of the loadinfo, append to the redirectchain
+ // and set it on the new channel
+ if (mLoadInfo) {
+ nsSecurityFlags secFlags = mLoadInfo->GetSecurityFlags() &
+ ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->CloneWithNewSecFlags(secFlags);
+
+ nsCOMPtr<nsIPrincipal> uriPrincipal;
+ nsIScriptSecurityManager *sm = nsContentUtils::GetSecurityManager();
+ sm->GetChannelURIPrincipal(this, getter_AddRefs(uriPrincipal));
+ bool isInternalRedirect =
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+ newLoadInfo->AppendRedirectedPrincipal(uriPrincipal, isInternalRedirect);
+ newChannel->SetLoadInfo(newLoadInfo);
+ }
+ else {
+ // the newChannel was created with a dummy loadInfo, we should clear
+ // it in case the original channel does not have a loadInfo
+ newChannel->SetLoadInfo(nullptr);
+ }
+
+ // Preserve the privacy bit if it has been overridden
+ if (mPrivateBrowsingOverriden) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(mPrivateBrowsing);
+ }
+ }
+
+ nsCOMPtr<nsIWritablePropertyBag> bag = ::do_QueryInterface(newChannel);
+ if (bag) {
+ for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
+ bag->SetProperty(iter.Key(), iter.UserData());
+ }
+ }
+
+ // Notify consumer, giving chance to cancel redirect. For backwards compat,
+ // we support nsIHttpEventSink if we are an HTTP channel and if this is not
+ // an internal redirect.
+
+ RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
+ new nsAsyncRedirectVerifyHelper();
+
+ bool checkRedirectSynchronously = !openNewChannel;
+
+ mRedirectChannel = newChannel;
+ mRedirectFlags = redirectFlags;
+ mOpenRedirectChannel = openNewChannel;
+ nsresult rv = redirectCallbackHelper->Init(this, newChannel, redirectFlags,
+ checkRedirectSynchronously);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (checkRedirectSynchronously && NS_FAILED(mStatus))
+ return mStatus;
+
+ return NS_OK;
+}
+
+nsresult
+nsBaseChannel::ContinueRedirect()
+{
+ // Backwards compat for non-internal redirects from a HTTP channel.
+ // XXX Is our http channel implementation going to derive from nsBaseChannel?
+ // If not, this code can be removed.
+ if (!(mRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface();
+ if (httpChannel) {
+ nsCOMPtr<nsIHttpEventSink> httpEventSink;
+ GetCallback(httpEventSink);
+ if (httpEventSink) {
+ nsresult rv = httpEventSink->OnRedirect(httpChannel, mRedirectChannel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+ }
+
+ // Make sure to do this _after_ making all the OnChannelRedirect calls
+ mRedirectChannel->SetOriginalURI(OriginalURI());
+
+ // If we fail to open the new channel, then we want to leave this channel
+ // unaffected, so we defer tearing down our channel until we have succeeded
+ // with the redirect.
+
+ if (mOpenRedirectChannel) {
+ nsresult rv = NS_OK;
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mRedirectChannel = nullptr;
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+ ChannelDone();
+
+ return NS_OK;
+}
+
+bool
+nsBaseChannel::HasContentTypeHint() const
+{
+ NS_ASSERTION(!Pending(), "HasContentTypeHint called too late");
+ return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE);
+}
+
+nsresult
+nsBaseChannel::PushStreamConverter(const char *fromType,
+ const char *toType,
+ bool invalidatesContentLength,
+ nsIStreamListener **result)
+{
+ NS_ASSERTION(mListener, "no listener");
+
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> scs =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = scs->AsyncConvertData(fromType, toType, mListener, mListenerContext,
+ getter_AddRefs(converter));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = converter;
+ if (invalidatesContentLength)
+ mContentLength = -1;
+ if (result) {
+ *result = nullptr;
+ converter.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsBaseChannel::BeginPumpingData()
+{
+ nsCOMPtr<nsIInputStream> stream;
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = OpenContentStream(true, getter_AddRefs(stream),
+ getter_AddRefs(channel));
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?");
+
+ if (channel) {
+ rv = NS_DispatchToCurrentThread(new RedirectRunnable(this, channel));
+ if (NS_SUCCEEDED(rv))
+ mWaitingOnAsyncRedirect = true;
+ return rv;
+ }
+
+ // By assigning mPump, we flag this channel as pending (see Pending). It's
+ // important that the pending flag is set when we call into the stream (the
+ // call to AsyncRead results in the stream's AsyncWait method being called)
+ // and especially when we call into the loadgroup. Our caller takes care to
+ // release mPump if we return an error.
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, -1, -1, 0, 0,
+ true);
+ if (NS_SUCCEEDED(rv))
+ rv = mPump->AsyncRead(this, nullptr);
+
+ return rv;
+}
+
+void
+nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel)
+{
+ NS_ASSERTION(!mPump, "Shouldn't have gotten here");
+
+ nsresult rv = mStatus;
+ if (NS_SUCCEEDED(mStatus)) {
+ rv = Redirect(newChannel,
+ nsIChannelEventSink::REDIRECT_TEMPORARY,
+ true);
+ if (NS_SUCCEEDED(rv)) {
+ // OnRedirectVerifyCallback will be called asynchronously
+ return;
+ }
+ }
+
+ ContinueHandleAsyncRedirect(rv);
+}
+
+void
+nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result)
+{
+ mWaitingOnAsyncRedirect = false;
+
+ if (NS_FAILED(result))
+ Cancel(result);
+
+ if (NS_FAILED(result) && mListener) {
+ // Notify our consumer ourselves
+ mListener->OnStartRequest(this, mListenerContext);
+ mListener->OnStopRequest(this, mListenerContext, mStatus);
+ ChannelDone();
+ }
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ CallbacksChanged();
+}
+
+void
+nsBaseChannel::ClassifyURI()
+{
+ // For channels created in the child process, delegate to the parent to
+ // classify URIs.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (mLoadFlags & LOAD_CLASSIFY_URI) {
+ RefPtr<nsChannelClassifier> classifier = new nsChannelClassifier();
+ if (classifier) {
+ classifier->Start(this);
+ } else {
+ Cancel(NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsBaseChannel,
+ nsHashPropertyBag,
+ nsIRequest,
+ nsIChannel,
+ nsIThreadRetargetableRequest,
+ nsIInterfaceRequestor,
+ nsITransportEventSink,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIThreadRetargetableStreamListener,
+ nsIAsyncVerifyRedirectCallback,
+ nsIPrivateBrowsingChannel)
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIRequest
+
+NS_IMETHODIMP
+nsBaseChannel::GetName(nsACString &result)
+{
+ if (!mURI) {
+ result.Truncate();
+ return NS_OK;
+ }
+ return mURI->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::IsPending(bool *result)
+{
+ *result = Pending();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetStatus(nsresult *status)
+{
+ if (mPump && NS_SUCCEEDED(mStatus)) {
+ mPump->GetStatus(status);
+ } else {
+ *status = mStatus;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Cancel(nsresult status)
+{
+ // Ignore redundant cancelation
+ if (NS_FAILED(mStatus))
+ return NS_OK;
+
+ mStatus = status;
+
+ if (mPump)
+ mPump->Cancel(status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Suspend()
+{
+ NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
+ return mPump->Suspend();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Resume()
+{
+ NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
+ return mPump->Resume();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ CallbacksChanged();
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIChannel
+
+NS_IMETHODIMP
+nsBaseChannel::GetOriginalURI(nsIURI **aURI)
+{
+ *aURI = OriginalURI();
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetOriginalURI(nsIURI *aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetOwner(nsISupports **aOwner)
+{
+ NS_IF_ADDREF(*aOwner = mOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetOwner(nsISupports *aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
+{
+ NS_IF_ADDREF(*aCallbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ CallbacksChanged();
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentType(nsACString &aContentType)
+{
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentType(const nsACString &aContentType)
+{
+ // mContentCharset is unchanged if not parsed
+ bool dummy;
+ net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ aContentCharset = mContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ mContentCharset = aContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ // preserve old behavior, fail unless explicitly set.
+ if (mContentDispositionHint == UINT32_MAX) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ mContentDispositionHint = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ if (!mContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ mContentDispositionFilename = new nsString(aContentDispositionFilename);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentLength(int64_t *aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Open(nsIInputStream **result)
+{
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
+
+ nsCOMPtr<nsIChannel> chan;
+ nsresult rv = OpenContentStream(false, result, getter_AddRefs(chan));
+ NS_ASSERTION(!chan || !*result, "Got both a channel and a stream?");
+ if (NS_SUCCEEDED(rv) && chan) {
+ rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = chan->Open(result);
+ } else if (rv == NS_ERROR_NOT_IMPLEMENTED)
+ return NS_ImplementChannelOpen(this, result);
+
+ if (NS_SUCCEEDED(rv)) {
+ mWasOpened = true;
+ ClassifyURI();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+ NS_ENSURE_ARG(listener);
+
+ // Skip checking for chrome:// sub-resources.
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("file")) {
+ NS_CompareLoadInfoAndLoadContext(this);
+ }
+
+ // Ensure that this is an allowed port before proceeding.
+ nsresult rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ // Store the listener and context early so that OpenContentStream and the
+ // stream's AsyncWait method (called by AsyncRead) can have access to them
+ // via PushStreamConverter and the StreamListener methods. However, since
+ // this typically introduces a reference cycle between this and the listener,
+ // we need to be sure to break the reference if this method does not succeed.
+ mListener = listener;
+ mListenerContext = ctxt;
+
+ // This method assigns mPump as a side-effect. We need to clear mPump if
+ // this method fails.
+ rv = BeginPumpingData();
+ if (NS_FAILED(rv)) {
+ mPump = nullptr;
+ ChannelDone();
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ // At this point, we are going to return success no matter what.
+
+ mWasOpened = true;
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ ClassifyURI();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsITransportEventSink
+
+NS_IMETHODIMP
+nsBaseChannel::OnTransportStatus(nsITransport *transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+{
+ // In some cases, we may wish to suppress transport-layer status events.
+
+ if (!mPump || NS_FAILED(mStatus)) {
+ return NS_OK;
+ }
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ // Lazily fetch mProgressSink
+ if (!mProgressSink) {
+ if (mQueriedProgressSink) {
+ return NS_OK;
+ }
+ GetCallback(mProgressSink);
+ mQueriedProgressSink = true;
+ if (!mProgressSink) {
+ return NS_OK;
+ }
+ }
+
+ if (!HasLoadFlag(LOAD_BACKGROUND)) {
+ nsAutoString statusArg;
+ if (GetStatusArg(status, statusArg)) {
+ mProgressSink->OnStatus(this, mListenerContext, status, statusArg.get());
+ }
+ }
+
+ if (progress) {
+ mProgressSink->OnProgress(this, mListenerContext, progress, progressMax);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsBaseChannel::GetInterface(const nsIID &iid, void **result)
+{
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, iid, result);
+ return *result ? NS_OK : NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIRequestObserver
+
+static void
+CallTypeSniffers(void *aClosure, const uint8_t *aData, uint32_t aCount)
+{
+ nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
+
+ nsAutoCString newType;
+ NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
+ if (!newType.IsEmpty()) {
+ chan->SetContentType(newType);
+ }
+}
+
+static void
+CallUnknownTypeSniffer(void *aClosure, const uint8_t *aData, uint32_t aCount)
+{
+ nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
+
+ nsCOMPtr<nsIContentSniffer> sniffer =
+ do_CreateInstance(NS_GENERIC_CONTENT_SNIFFER);
+ if (!sniffer)
+ return;
+
+ nsAutoCString detected;
+ nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected);
+ if (NS_SUCCEEDED(rv))
+ chan->SetContentType(detected);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ MOZ_ASSERT(request == mPump);
+
+ // If our content type is unknown, use the content type
+ // sniffer. If the sniffer is not available for some reason, then we just keep
+ // going as-is.
+ if (NS_SUCCEEDED(mStatus) &&
+ mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+ mPump->PeekStream(CallUnknownTypeSniffer, static_cast<nsIChannel*>(this));
+ }
+
+ // Now, the general type sniffers. Skip this if we have none.
+ if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS)
+ mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ if (mListener) // null in case of redirect
+ return mListener->OnStartRequest(this, mListenerContext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
+ nsresult status)
+{
+ // If both mStatus and status are failure codes, we keep mStatus as-is since
+ // that is consistent with our GetStatus and Cancel methods.
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = status;
+
+ // Cause Pending to return false.
+ mPump = nullptr;
+
+ if (mListener) // null in case of redirect
+ mListener->OnStopRequest(this, mListenerContext, mStatus);
+ ChannelDone();
+
+ // No need to suspend pump in this scope since we will not be receiving
+ // any more events from it.
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ CallbacksChanged();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIStreamListener
+
+NS_IMETHODIMP
+nsBaseChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
+ nsIInputStream *stream, uint64_t offset,
+ uint32_t count)
+{
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ nsresult rv = mListener->OnDataAvailable(this, mListenerContext, stream,
+ offset, count);
+ if (mSynthProgressEvents && NS_SUCCEEDED(rv)) {
+ int64_t prog = offset + count;
+ if (NS_IsMainThread()) {
+ OnTransportStatus(nullptr, NS_NET_STATUS_READING, prog, mContentLength);
+ } else {
+ class OnTransportStatusAsyncEvent : public mozilla::Runnable
+ {
+ RefPtr<nsBaseChannel> mChannel;
+ int64_t mProgress;
+ int64_t mContentLength;
+ public:
+ OnTransportStatusAsyncEvent(nsBaseChannel* aChannel,
+ int64_t aProgress,
+ int64_t aContentLength)
+ : mChannel(aChannel),
+ mProgress(aProgress),
+ mContentLength(aContentLength)
+ { }
+
+ NS_IMETHOD Run() override
+ {
+ return mChannel->OnTransportStatus(nullptr, NS_NET_STATUS_READING,
+ mProgress, mContentLength);
+ }
+ };
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new OnTransportStatusAsyncEvent(this, prog, mContentLength);
+ NS_DispatchToMainThread(runnable);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnRedirectVerifyCallback(nsresult result)
+{
+ if (NS_SUCCEEDED(result))
+ result = ContinueRedirect();
+
+ if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) {
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = result;
+ return NS_OK;
+ }
+
+ if (mWaitingOnAsyncRedirect)
+ ContinueHandleAsyncRedirect(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::RetargetDeliveryTo(nsIEventTarget* aEventTarget)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
+
+ if (!mAllowThreadRetargeting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return mPump->RetargetDeliveryTo(aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::CheckListenerChain()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mAllowThreadRetargeting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h
new file mode 100644
index 0000000000..b98609e857
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.h
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseChannel_h__
+#define nsBaseChannel_h__
+
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsHashPropertyBag.h"
+#include "nsInputStreamPump.h"
+
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIProgressEventSink.h"
+#include "nsITransport.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "PrivateBrowsingChannel.h"
+#include "nsThreadUtils.h"
+
+class nsIInputStream;
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel is designed to be subclassed. The subclass is responsible for
+// implementing the OpenContentStream method, which will be called by the
+// nsIChannel::AsyncOpen and nsIChannel::Open implementations.
+//
+// nsBaseChannel implements nsIInterfaceRequestor to provide a convenient way
+// for subclasses to query both the nsIChannel::notificationCallbacks and
+// nsILoadGroup::notificationCallbacks for supported interfaces.
+//
+// nsBaseChannel implements nsITransportEventSink to support progress & status
+// notifications generated by the transport layer.
+
+class nsBaseChannel : public nsHashPropertyBag
+ , public nsIChannel
+ , public nsIThreadRetargetableRequest
+ , public nsIInterfaceRequestor
+ , public nsITransportEventSink
+ , public nsIAsyncVerifyRedirectCallback
+ , public mozilla::net::PrivateBrowsingChannel<nsBaseChannel>
+ , protected nsIStreamListener
+ , protected nsIThreadRetargetableStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsBaseChannel();
+
+ // This method must be called to initialize the basechannel instance.
+ nsresult Init() {
+ return NS_OK;
+ }
+
+protected:
+ // -----------------------------------------------
+ // Methods to be implemented by the derived class:
+
+ virtual ~nsBaseChannel();
+
+private:
+ // Implemented by subclass to supply data stream. The parameter, async, is
+ // true when called from nsIChannel::AsyncOpen and false otherwise. When
+ // async is true, the resulting stream will be used with a nsIInputStreamPump
+ // instance. This means that if it is a non-blocking stream that supports
+ // nsIAsyncInputStream that it will be read entirely on the main application
+ // thread, and its AsyncWait method will be called whenever ReadSegments
+ // returns NS_BASE_STREAM_WOULD_BLOCK. Otherwise, if the stream is blocking,
+ // then it will be read on one of the background I/O threads, and it does not
+ // need to implement ReadSegments. If async is false, this method may return
+ // NS_ERROR_NOT_IMPLEMENTED to cause the basechannel to implement Open in
+ // terms of AsyncOpen (see NS_ImplementChannelOpen).
+ // A callee is allowed to return an nsIChannel instead of an nsIInputStream.
+ // That case will be treated as a redirect to the new channel. By default
+ // *channel will be set to null by the caller, so callees who don't want to
+ // return one an just not touch it.
+ virtual nsresult OpenContentStream(bool async, nsIInputStream **stream,
+ nsIChannel** channel) = 0;
+
+ // The basechannel calls this method from its OnTransportStatus method to
+ // determine whether to call nsIProgressEventSink::OnStatus in addition to
+ // nsIProgressEventSink::OnProgress. This method may be overriden by the
+ // subclass to enable nsIProgressEventSink::OnStatus events. If this method
+ // returns true, then the statusArg out param specifies the "statusArg" value
+ // to pass to the OnStatus method. By default, OnStatus messages are
+ // suppressed. The status parameter passed to this method is the status value
+ // from the OnTransportStatus method.
+ virtual bool GetStatusArg(nsresult status, nsString &statusArg) {
+ return false;
+ }
+
+ // Called when the callbacks available to this channel may have changed.
+ virtual void OnCallbacksChanged() {
+ }
+
+ // Called when our channel is done, to allow subclasses to drop resources.
+ virtual void OnChannelDone() {
+ }
+
+public:
+ // ----------------------------------------------
+ // Methods provided for use by the derived class:
+
+ // Redirect to another channel. This method takes care of notifying
+ // observers of this redirect as well as of opening the new channel, if asked
+ // to do so. It also cancels |this| with the status code
+ // NS_BINDING_REDIRECTED. A failure return from this method means that the
+ // redirect could not be performed (no channel was opened; this channel
+ // wasn't canceled.) The redirectFlags parameter consists of the flag values
+ // defined on nsIChannelEventSink.
+ nsresult Redirect(nsIChannel *newChannel, uint32_t redirectFlags,
+ bool openNewChannel);
+
+ // Tests whether a type hint was set. Subclasses can use this to decide
+ // whether to call SetContentType.
+ // NOTE: This is only reliable if the subclass didn't itself call
+ // SetContentType, and should also not be called after OpenContentStream.
+ bool HasContentTypeHint() const;
+
+ // The URI member should be initialized before the channel is used, and then
+ // it should never be changed again until the channel is destroyed.
+ nsIURI *URI() {
+ return mURI;
+ }
+ void SetURI(nsIURI *uri) {
+ NS_ASSERTION(uri, "must specify a non-null URI");
+ NS_ASSERTION(!mURI, "must not modify URI");
+ NS_ASSERTION(!mOriginalURI, "how did that get set so early?");
+ mURI = uri;
+ mOriginalURI = uri;
+ }
+ nsIURI *OriginalURI() {
+ return mOriginalURI;
+ }
+
+ // The security info is a property of the transport-layer, which should be
+ // assigned by the subclass.
+ nsISupports *SecurityInfo() {
+ return mSecurityInfo;
+ }
+ void SetSecurityInfo(nsISupports *info) {
+ mSecurityInfo = info;
+ }
+
+ // Test the load flags
+ bool HasLoadFlag(uint32_t flag) {
+ return (mLoadFlags & flag) != 0;
+ }
+
+ // This is a short-cut to calling nsIRequest::IsPending()
+ virtual bool Pending() const {
+ return mPump || mWaitingOnAsyncRedirect;
+ }
+
+ // Helper function for querying the channel's notification callbacks.
+ template <class T> void GetCallback(nsCOMPtr<T> &result) {
+ GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result));
+ }
+
+ // Helper function for calling QueryInterface on this.
+ nsQueryInterface do_QueryInterface() {
+ return nsQueryInterface(static_cast<nsIChannel *>(this));
+ }
+ // MSVC needs this:
+ nsQueryInterface do_QueryInterface(nsISupports *obj) {
+ return nsQueryInterface(obj);
+ }
+
+ // If a subclass does not want to feed transport-layer progress events to the
+ // base channel via nsITransportEventSink, then it may set this flag to cause
+ // the base channel to synthesize progress events when it receives data from
+ // the content stream. By default, progress events are not synthesized.
+ void EnableSynthesizedProgressEvents(bool enable) {
+ mSynthProgressEvents = enable;
+ }
+
+ // Some subclasses may wish to manually insert a stream listener between this
+ // and the channel's listener. The following methods make that possible.
+ void SetStreamListener(nsIStreamListener *listener) {
+ mListener = listener;
+ }
+ nsIStreamListener *StreamListener() {
+ return mListener;
+ }
+
+ // Pushes a new stream converter in front of the channel's stream listener.
+ // The fromType and toType values are passed to nsIStreamConverterService's
+ // AsyncConvertData method. If invalidatesContentLength is true, then the
+ // channel's content-length property will be assigned a value of -1. This is
+ // necessary when the converter changes the length of the resulting data
+ // stream, which is almost always the case for a "stream converter" ;-)
+ // This function optionally returns a reference to the new converter.
+ nsresult PushStreamConverter(const char *fromType, const char *toType,
+ bool invalidatesContentLength = true,
+ nsIStreamListener **converter = nullptr);
+
+protected:
+ void DisallowThreadRetargeting() {
+ mAllowThreadRetargeting = false;
+ }
+
+private:
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // Called to setup mPump and call AsyncRead on it.
+ nsresult BeginPumpingData();
+
+ // Called when the callbacks available to this channel may have changed.
+ void CallbacksChanged() {
+ mProgressSink = nullptr;
+ mQueriedProgressSink = false;
+ OnCallbacksChanged();
+ }
+
+ // Called when our channel is done. This should drop no-longer-needed pointers.
+ void ChannelDone() {
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ OnChannelDone();
+ }
+
+ // Handle an async redirect callback. This will only be called if we
+ // returned success from AsyncOpen while posting a redirect runnable.
+ void HandleAsyncRedirect(nsIChannel* newChannel);
+ void ContinueHandleAsyncRedirect(nsresult result);
+ nsresult ContinueRedirect();
+
+ // start URI classifier if requested
+ void ClassifyURI();
+
+ class RedirectRunnable : public mozilla::Runnable
+ {
+ public:
+ RedirectRunnable(nsBaseChannel* chan, nsIChannel* newChannel)
+ : mChannel(chan), mNewChannel(newChannel)
+ {
+ NS_PRECONDITION(newChannel, "Must have channel to redirect to");
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mChannel->HandleAsyncRedirect(mNewChannel);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsBaseChannel> mChannel;
+ nsCOMPtr<nsIChannel> mNewChannel;
+ };
+ friend class RedirectRunnable;
+
+ RefPtr<nsInputStreamPump> mPump;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCString mContentType;
+ nsCString mContentCharset;
+ uint32_t mLoadFlags;
+ bool mQueriedProgressSink;
+ bool mSynthProgressEvents;
+ bool mAllowThreadRetargeting;
+ bool mWaitingOnAsyncRedirect;
+ bool mOpenRedirectChannel;
+ uint32_t mRedirectFlags;
+
+protected:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsresult mStatus;
+ uint32_t mContentDispositionHint;
+ nsAutoPtr<nsString> mContentDispositionFilename;
+ int64_t mContentLength;
+ bool mWasOpened;
+
+ friend class mozilla::net::PrivateBrowsingChannel<nsBaseChannel>;
+};
+
+#endif // !nsBaseChannel_h__
diff --git a/netwerk/base/nsBaseContentStream.cpp b/netwerk/base/nsBaseContentStream.cpp
new file mode 100644
index 0000000000..ee5a8ef3cf
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBaseContentStream.h"
+#include "nsStreamUtils.h"
+
+//-----------------------------------------------------------------------------
+
+void
+nsBaseContentStream::DispatchCallback(bool async)
+{
+ if (!mCallback)
+ return;
+
+ // It's important to clear mCallback and mCallbackTarget up-front because the
+ // OnInputStreamReady implementation may call our AsyncWait method.
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ if (async) {
+ callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
+ mCallback = nullptr;
+ } else {
+ callback.swap(mCallback);
+ }
+ mCallbackTarget = nullptr;
+
+ callback->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsISupports
+
+NS_IMPL_ADDREF(nsBaseContentStream)
+NS_IMPL_RELEASE(nsBaseContentStream)
+
+// We only support nsIAsyncInputStream when we are in non-blocking mode.
+NS_INTERFACE_MAP_BEGIN(nsBaseContentStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mNonBlocking)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsIInputStream
+
+NS_IMETHODIMP
+nsBaseContentStream::Close()
+{
+ return IsClosed() ? NS_OK : CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::Available(uint64_t *result)
+{
+ *result = 0;
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::Read(char *buf, uint32_t count, uint32_t *result)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::ReadSegments(nsWriteSegmentFun fun, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ *result = 0;
+
+ if (mStatus == NS_BASE_STREAM_CLOSED)
+ return NS_OK;
+
+ // No data yet
+ if (!IsClosed() && IsNonBlocking())
+ return NS_BASE_STREAM_WOULD_BLOCK;
+
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::IsNonBlocking(bool *result)
+{
+ *result = mNonBlocking;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsIAsyncInputStream
+
+NS_IMETHODIMP
+nsBaseContentStream::CloseWithStatus(nsresult status)
+{
+ if (IsClosed())
+ return NS_OK;
+
+ NS_ENSURE_ARG(NS_FAILED(status));
+ mStatus = status;
+
+ DispatchCallback();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::AsyncWait(nsIInputStreamCallback *callback,
+ uint32_t flags, uint32_t requestedCount,
+ nsIEventTarget *target)
+{
+ // Our _only_ consumer is nsInputStreamPump, so we simplify things here by
+ // making assumptions about how we will be called.
+ NS_ASSERTION(target, "unexpected parameter");
+ NS_ASSERTION(flags == 0, "unexpected parameter");
+ NS_ASSERTION(requestedCount == 0, "unexpected parameter");
+
+#ifdef DEBUG
+ bool correctThread;
+ target->IsOnCurrentThread(&correctThread);
+ NS_ASSERTION(correctThread, "event target must be on the current thread");
+#endif
+
+ mCallback = callback;
+ mCallbackTarget = target;
+
+ if (!mCallback)
+ return NS_OK;
+
+ // If we're already closed, then dispatch this callback immediately.
+ if (IsClosed()) {
+ DispatchCallback();
+ return NS_OK;
+ }
+
+ OnCallbackPending();
+ return NS_OK;
+}
diff --git a/netwerk/base/nsBaseContentStream.h b/netwerk/base/nsBaseContentStream.h
new file mode 100644
index 0000000000..992c8733ec
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseContentStream_h__
+#define nsBaseContentStream_h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsIEventTarget.h"
+#include "nsCOMPtr.h"
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream is designed to be subclassed with the intention of being
+// used to satisfy the nsBaseChannel::OpenContentStream method.
+//
+// The subclass typically overrides the default Available, ReadSegments and
+// CloseWithStatus methods. By default, Read is implemented in terms of
+// ReadSegments, and Close is implemented in terms of CloseWithStatus. If
+// CloseWithStatus is overriden, then the subclass will usually want to call
+// the base class' CloseWithStatus method before returning.
+//
+// If the stream is non-blocking, then readSegments may return the exception
+// NS_BASE_STREAM_WOULD_BLOCK if there is no data available and the stream is
+// not at the "end-of-file" or already closed. This error code must not be
+// returned from the Available implementation. When the caller receives this
+// error code, he may choose to call the stream's AsyncWait method, in which
+// case the base stream will have a non-null PendingCallback. When the stream
+// has data or encounters an error, it should be sure to dispatch a pending
+// callback if one exists (see DispatchCallback). The implementation of the
+// base stream's CloseWithStatus (and Close) method will ensure that any
+// pending callback is dispatched. It is the responsibility of the subclass
+// to ensure that the pending callback is dispatched when it wants to have its
+// ReadSegments method called again.
+
+class nsBaseContentStream : public nsIAsyncInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsBaseContentStream(bool nonBlocking)
+ : mStatus(NS_OK)
+ , mNonBlocking(nonBlocking) {
+ }
+
+ nsresult Status() { return mStatus; }
+ bool IsNonBlocking() { return mNonBlocking; }
+ bool IsClosed() { return NS_FAILED(mStatus); }
+
+ // Called to test if the stream has a pending callback.
+ bool HasPendingCallback() { return mCallback != nullptr; }
+
+ // The current dispatch target (may be null) for the pending callback if any.
+ nsIEventTarget *CallbackTarget() { return mCallbackTarget; }
+
+ // Called to dispatch a pending callback. If there is no pending callback,
+ // then this function does nothing. Pass true to this function to cause the
+ // callback to occur asynchronously; otherwise, the callback will happen
+ // before this function returns.
+ void DispatchCallback(bool async = true);
+
+ // Helper function to make code more self-documenting.
+ void DispatchCallbackSync() { DispatchCallback(false); }
+
+protected:
+ virtual ~nsBaseContentStream() {}
+
+private:
+ // Called from the base stream's AsyncWait method when a pending callback
+ // is installed on the stream.
+ virtual void OnCallbackPending() {}
+
+private:
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ nsresult mStatus;
+ bool mNonBlocking;
+};
+
+#endif // nsBaseContentStream_h__
diff --git a/netwerk/base/nsBufferedStreams.cpp b/netwerk/base/nsBufferedStreams.cpp
new file mode 100644
index 0000000000..e67c3009be
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.cpp
@@ -0,0 +1,832 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsBufferedStreams.h"
+#include "nsStreamUtils.h"
+#include "nsNetCID.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include <algorithm>
+
+#ifdef DEBUG_brendan
+# define METERING
+#endif
+
+#ifdef METERING
+# include <stdio.h>
+# define METER(x) x
+# define MAX_BIG_SEEKS 20
+
+static struct {
+ uint32_t mSeeksWithinBuffer;
+ uint32_t mSeeksOutsideBuffer;
+ uint32_t mBufferReadUponSeek;
+ uint32_t mBufferUnreadUponSeek;
+ uint32_t mBytesReadFromBuffer;
+ uint32_t mBigSeekIndex;
+ struct {
+ int64_t mOldOffset;
+ int64_t mNewOffset;
+ } mBigSeek[MAX_BIG_SEEKS];
+} bufstats;
+#else
+# define METER(x) /* nothing */
+#endif
+
+using namespace mozilla::ipc;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedStream
+
+nsBufferedStream::nsBufferedStream()
+ : mBuffer(nullptr),
+ mBufferStartOffset(0),
+ mCursor(0),
+ mFillPoint(0),
+ mStream(nullptr),
+ mBufferDisabled(false),
+ mEOF(false),
+ mGetBufferCount(0)
+{
+}
+
+nsBufferedStream::~nsBufferedStream()
+{
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsBufferedStream, nsISeekableStream)
+
+nsresult
+nsBufferedStream::Init(nsISupports* stream, uint32_t bufferSize)
+{
+ NS_ASSERTION(stream, "need to supply a stream");
+ NS_ASSERTION(mStream == nullptr, "already inited");
+ mStream = stream;
+ NS_IF_ADDREF(mStream);
+ mBufferSize = bufferSize;
+ mBufferStartOffset = 0;
+ mCursor = 0;
+ mBuffer = new (mozilla::fallible) char[bufferSize];
+ if (mBuffer == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+nsresult
+nsBufferedStream::Close()
+{
+ NS_IF_RELEASE(mStream);
+ if (mBuffer) {
+ delete[] mBuffer;
+ mBuffer = nullptr;
+ mBufferSize = 0;
+ mBufferStartOffset = 0;
+ mCursor = 0;
+ mFillPoint = 0;
+ }
+#ifdef METERING
+ {
+ static FILE *tfp;
+ if (!tfp) {
+ tfp = fopen("/tmp/bufstats", "w");
+ if (tfp)
+ setvbuf(tfp, nullptr, _IOLBF, 0);
+ }
+ if (tfp) {
+ fprintf(tfp, "seeks within buffer: %u\n",
+ bufstats.mSeeksWithinBuffer);
+ fprintf(tfp, "seeks outside buffer: %u\n",
+ bufstats.mSeeksOutsideBuffer);
+ fprintf(tfp, "buffer read on seek: %u\n",
+ bufstats.mBufferReadUponSeek);
+ fprintf(tfp, "buffer unread on seek: %u\n",
+ bufstats.mBufferUnreadUponSeek);
+ fprintf(tfp, "bytes read from buffer: %u\n",
+ bufstats.mBytesReadFromBuffer);
+ for (uint32_t i = 0; i < bufstats.mBigSeekIndex; i++) {
+ fprintf(tfp, "bigseek[%u] = {old: %u, new: %u}\n",
+ i,
+ bufstats.mBigSeek[i].mOldOffset,
+ bufstats.mBigSeek[i].mNewOffset);
+ }
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedStream::Seek(int32_t whence, int64_t offset)
+{
+ if (mStream == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ // If the underlying stream isn't a random access store, then fail early.
+ // We could possibly succeed for the case where the seek position denotes
+ // something that happens to be read into the buffer, but that would make
+ // the failure data-dependent.
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ int64_t absPos = 0;
+ switch (whence) {
+ case nsISeekableStream::NS_SEEK_SET:
+ absPos = offset;
+ break;
+ case nsISeekableStream::NS_SEEK_CUR:
+ absPos = mBufferStartOffset;
+ absPos += mCursor;
+ absPos += offset;
+ break;
+ case nsISeekableStream::NS_SEEK_END:
+ absPos = -1;
+ break;
+ default:
+ NS_NOTREACHED("bogus seek whence parameter");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Let mCursor point into the existing buffer if the new position is
+ // between the current cursor and the mFillPoint "fencepost" -- the
+ // client may never get around to a Read or Write after this Seek.
+ // Read and Write worry about flushing and filling in that event.
+ // But if we're at EOF, make sure to pass the seek through to the
+ // underlying stream, because it may have auto-closed itself and
+ // needs to reopen.
+ uint32_t offsetInBuffer = uint32_t(absPos - mBufferStartOffset);
+ if (offsetInBuffer <= mFillPoint && !mEOF) {
+ METER(bufstats.mSeeksWithinBuffer++);
+ mCursor = offsetInBuffer;
+ return NS_OK;
+ }
+
+ METER(bufstats.mSeeksOutsideBuffer++);
+ METER(bufstats.mBufferReadUponSeek += mCursor);
+ METER(bufstats.mBufferUnreadUponSeek += mFillPoint - mCursor);
+ rv = Flush();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ras->Seek(whence, offset);
+ if (NS_FAILED(rv)) return rv;
+
+ mEOF = false;
+
+ // Recompute whether the offset we're seeking to is in our buffer.
+ // Note that we need to recompute because Flush() might have
+ // changed mBufferStartOffset.
+ offsetInBuffer = uint32_t(absPos - mBufferStartOffset);
+ if (offsetInBuffer <= mFillPoint) {
+ // It's safe to just set mCursor to offsetInBuffer. In particular, we
+ // want to avoid calling Fill() here since we already have the data that
+ // was seeked to and calling Fill() might auto-close our underlying
+ // stream in some cases.
+ mCursor = offsetInBuffer;
+ return NS_OK;
+ }
+
+ METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS)
+ bufstats.mBigSeek[bufstats.mBigSeekIndex].mOldOffset =
+ mBufferStartOffset + int64_t(mCursor));
+ const int64_t minus1 = -1;
+ if (absPos == minus1) {
+ // then we had the SEEK_END case, above
+ int64_t tellPos;
+ rv = ras->Tell(&tellPos);
+ mBufferStartOffset = tellPos;
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ mBufferStartOffset = absPos;
+ }
+ METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS)
+ bufstats.mBigSeek[bufstats.mBigSeekIndex++].mNewOffset =
+ mBufferStartOffset);
+
+ mFillPoint = mCursor = 0;
+ return Fill();
+}
+
+NS_IMETHODIMP
+nsBufferedStream::Tell(int64_t *result)
+{
+ if (mStream == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int64_t result64 = mBufferStartOffset;
+ result64 += mCursor;
+ *result = result64;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedStream::SetEOF()
+{
+ if (mStream == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ras->SetEOF();
+ if (NS_SUCCEEDED(rv))
+ mEOF = true;
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsBufferedInputStream, nsBufferedStream)
+NS_IMPL_RELEASE_INHERITED(nsBufferedInputStream, nsBufferedStream)
+
+NS_IMPL_CLASSINFO(nsBufferedInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_BUFFEREDINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream)
+NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream,
+ nsIInputStream,
+ nsIBufferedInputStream,
+ nsISeekableStream,
+ nsIStreamBufferAccess)
+
+nsresult
+nsBufferedInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsBufferedInputStream* stream = new nsBufferedInputStream();
+ if (stream == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Init(nsIInputStream* stream, uint32_t bufferSize)
+{
+ return nsBufferedStream::Init(stream, bufferSize);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Close()
+{
+ nsresult rv1 = NS_OK, rv2;
+ if (mStream) {
+ rv1 = Source()->Close();
+ NS_RELEASE(mStream);
+ }
+ rv2 = nsBufferedStream::Close();
+ if (NS_FAILED(rv1)) return rv1;
+ return rv2;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Available(uint64_t *result)
+{
+ nsresult rv = NS_OK;
+ *result = 0;
+ if (mStream) {
+ rv = Source()->Available(result);
+ }
+ *result += (mFillPoint - mCursor);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Read(char * buf, uint32_t count, uint32_t *result)
+{
+ if (mBufferDisabled) {
+ if (!mStream) {
+ *result = 0;
+ return NS_OK;
+ }
+ nsresult rv = Source()->Read(buf, count, result);
+ if (NS_SUCCEEDED(rv)) {
+ mBufferStartOffset += *result; // so nsBufferedStream::Tell works
+ if (*result == 0) {
+ mEOF = true;
+ }
+ }
+ return rv;
+ }
+
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ *result = 0;
+
+ if (!mStream)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+ while (count > 0) {
+ uint32_t amt = std::min(count, mFillPoint - mCursor);
+ if (amt > 0) {
+ uint32_t read = 0;
+ rv = writer(this, closure, mBuffer + mCursor, *result, amt, &read);
+ if (NS_FAILED(rv)) {
+ // errors returned from the writer end here!
+ rv = NS_OK;
+ break;
+ }
+ *result += read;
+ count -= read;
+ mCursor += read;
+ }
+ else {
+ rv = Fill();
+ if (NS_FAILED(rv) || mFillPoint == mCursor)
+ break;
+ }
+ }
+ return (*result > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::IsNonBlocking(bool *aNonBlocking)
+{
+ if (mStream)
+ return Source()->IsNonBlocking(aNonBlocking);
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Fill()
+{
+ if (mBufferDisabled)
+ return NS_OK;
+ NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+ int32_t rem = int32_t(mFillPoint - mCursor);
+ if (rem > 0) {
+ // slide the remainder down to the start of the buffer
+ // |<------------->|<--rem-->|<--->|
+ // b c f s
+ memcpy(mBuffer, mBuffer + mCursor, rem);
+ }
+ mBufferStartOffset += mCursor;
+ mFillPoint = rem;
+ mCursor = 0;
+
+ uint32_t amt;
+ rv = Source()->Read(mBuffer + mFillPoint, mBufferSize - mFillPoint, &amt);
+ if (NS_FAILED(rv)) return rv;
+
+ if (amt == 0)
+ mEOF = true;
+
+ mFillPoint += amt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask)
+{
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0)
+ return nullptr;
+
+ if (mBufferDisabled)
+ return nullptr;
+
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mFillPoint - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Fill()))
+ return nullptr;
+ buf = mBuffer + mCursor;
+ rem = mFillPoint - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem)
+ return nullptr;
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem)
+ return nullptr;
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedInputStream::PutBuffer(char* aBuffer, uint32_t aLength)
+{
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0)
+ return;
+
+ NS_ASSERTION(mCursor + aLength <= mFillPoint, "PutBuffer botch");
+ mCursor += aLength;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::DisableBuffering()
+{
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0)
+ return NS_ERROR_UNEXPECTED;
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::EnableBuffering()
+{
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetUnbufferedStream(nsISupports* *aStream)
+{
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+
+ *aStream = mStream;
+ NS_IF_ADDREF(*aStream);
+ return NS_OK;
+}
+
+void
+nsBufferedInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ BufferedInputStreamParams params;
+
+ if (mStream) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream);
+ MOZ_ASSERT(stream);
+
+ InputStreamParams wrappedParams;
+ SerializeInputStream(stream, wrappedParams, aFileDescriptors);
+
+ params.optionalStream() = wrappedParams;
+ }
+ else {
+ params.optionalStream() = mozilla::void_t();
+ }
+
+ params.bufferSize() = mBufferSize;
+
+ aParams = params;
+}
+
+bool
+nsBufferedInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const BufferedInputStreamParams& params =
+ aParams.get_BufferedInputStreamParams();
+ const OptionalInputStreamParams& wrappedParams = params.optionalStream();
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) {
+ stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(),
+ aFileDescriptors);
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+ }
+ else {
+ NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t,
+ "Unknown type for OptionalInputStreamParams!");
+ }
+
+ nsresult rv = Init(stream, params.bufferSize());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+Maybe<uint64_t>
+nsBufferedInputStream::ExpectedSerializedLength()
+{
+ nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStream);
+ if (stream) {
+ return stream->ExpectedSerializedLength();
+ }
+ return Nothing();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedOutputStream
+
+NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream, nsBufferedStream)
+NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream, nsBufferedStream)
+// This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
+// non-nullness of mSafeStream.
+NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISafeOutputStream, mSafeStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
+NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
+
+nsresult
+nsBufferedOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsBufferedOutputStream* stream = new nsBufferedOutputStream();
+ if (stream == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize)
+{
+ // QI stream to an nsISafeOutputStream, to see if we should support it
+ mSafeStream = do_QueryInterface(stream);
+
+ return nsBufferedStream::Init(stream, bufferSize);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Close()
+{
+ nsresult rv1, rv2 = NS_OK, rv3;
+ rv1 = Flush();
+ // If we fail to Flush all the data, then we close anyway and drop the
+ // remaining data in the buffer. We do this because it's what Unix does
+ // for fclose and close. However, we report the error from Flush anyway.
+ if (mStream) {
+ rv2 = Sink()->Close();
+ NS_RELEASE(mStream);
+ }
+ rv3 = nsBufferedStream::Close();
+ if (NS_FAILED(rv1)) return rv1;
+ if (NS_FAILED(rv2)) return rv2;
+ return rv3;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ nsresult rv = NS_OK;
+ uint32_t written = 0;
+ while (count > 0) {
+ uint32_t amt = std::min(count, mBufferSize - mCursor);
+ if (amt > 0) {
+ memcpy(mBuffer + mCursor, buf + written, amt);
+ written += amt;
+ count -= amt;
+ mCursor += amt;
+ if (mFillPoint < mCursor)
+ mFillPoint = mCursor;
+ }
+ else {
+ NS_ASSERTION(mFillPoint, "iloop in nsBufferedOutputStream::Write!");
+ rv = Flush();
+ if (NS_FAILED(rv)) break;
+ }
+ }
+ *result = written;
+ return (written > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Flush()
+{
+ nsresult rv;
+ uint32_t amt;
+ if (!mStream) {
+ // Stream already cancelled/flushed; probably because of previous error.
+ return NS_OK;
+ }
+ rv = Sink()->Write(mBuffer, mFillPoint, &amt);
+ if (NS_FAILED(rv)) return rv;
+ mBufferStartOffset += amt;
+ if (amt == mFillPoint) {
+ mFillPoint = mCursor = 0;
+ return NS_OK; // flushed everything
+ }
+
+ // slide the remainder down to the start of the buffer
+ // |<-------------->|<---|----->|
+ // b a c s
+ uint32_t rem = mFillPoint - amt;
+ memmove(mBuffer, mBuffer + amt, rem);
+ mFillPoint = mCursor = rem;
+ return NS_ERROR_FAILURE; // didn't flush all
+}
+
+// nsISafeOutputStream
+NS_IMETHODIMP
+nsBufferedOutputStream::Finish()
+{
+ // flush the stream, to write out any buffered data...
+ nsresult rv = nsBufferedOutputStream::Flush();
+ if (NS_FAILED(rv))
+ NS_WARNING("failed to flush buffered data! possible dataloss");
+
+ // ... and finish the underlying stream...
+ if (NS_SUCCEEDED(rv))
+ rv = mSafeStream->Finish();
+ else
+ Sink()->Close();
+
+ // ... and close the buffered stream, so any further attempts to flush/close
+ // the buffered stream won't cause errors.
+ nsBufferedStream::Close();
+
+ return rv;
+}
+
+static nsresult
+nsReadFromInputStream(nsIOutputStream* outStr,
+ void* closure,
+ char* toRawSegment,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *readCount)
+{
+ nsIInputStream* fromStream = (nsIInputStream*)closure;
+ return fromStream->Read(toRawSegment, count, readCount);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ return WriteSegments(nsReadFromInputStream, inStr, count, _retval);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ *_retval = 0;
+ nsresult rv;
+ while (count > 0) {
+ uint32_t left = std::min(count, mBufferSize - mCursor);
+ if (left == 0) {
+ rv = Flush();
+ if (NS_FAILED(rv))
+ return (*_retval > 0) ? NS_OK : rv;
+
+ continue;
+ }
+
+ uint32_t read = 0;
+ rv = reader(this, closure, mBuffer + mCursor, *_retval, left, &read);
+
+ if (NS_FAILED(rv)) // If we have written some data, return ok
+ return (*_retval > 0) ? NS_OK : rv;
+ mCursor += read;
+ *_retval += read;
+ count -= read;
+ mFillPoint = std::max(mFillPoint, mCursor);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::IsNonBlocking(bool *aNonBlocking)
+{
+ if (mStream)
+ return Sink()->IsNonBlocking(aNonBlocking);
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask)
+{
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0)
+ return nullptr;
+
+ if (mBufferDisabled)
+ return nullptr;
+
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mBufferSize - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Flush()))
+ return nullptr;
+ buf = mBuffer + mCursor;
+ rem = mBufferSize - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem)
+ return nullptr;
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem)
+ return nullptr;
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedOutputStream::PutBuffer(char* aBuffer, uint32_t aLength)
+{
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0)
+ return;
+
+ NS_ASSERTION(mCursor + aLength <= mBufferSize, "PutBuffer botch");
+ mCursor += aLength;
+ if (mFillPoint < mCursor)
+ mFillPoint = mCursor;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::DisableBuffering()
+{
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0)
+ return NS_ERROR_UNEXPECTED;
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ nsresult rv = Flush();
+ if (NS_FAILED(rv))
+ return rv;
+
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::EnableBuffering()
+{
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::GetUnbufferedStream(nsISupports* *aStream)
+{
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ if (mFillPoint) {
+ nsresult rv = Flush();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ *aStream = mStream;
+ NS_IF_ADDREF(*aStream);
+ return NS_OK;
+}
+
+#undef METER
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsBufferedStreams.h b/netwerk/base/nsBufferedStreams.h
new file mode 100644
index 0000000000..93a770bebf
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBufferedStreams_h__
+#define nsBufferedStreams_h__
+
+#include "nsIBufferedStreams.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIStreamBufferAccess.h"
+#include "nsCOMPtr.h"
+#include "nsIIPCSerializableInputStream.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedStream : public nsISeekableStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+
+ nsBufferedStream();
+
+ nsresult Close();
+
+protected:
+ virtual ~nsBufferedStream();
+
+ nsresult Init(nsISupports* stream, uint32_t bufferSize);
+ NS_IMETHOD Fill() = 0;
+ NS_IMETHOD Flush() = 0;
+
+ uint32_t mBufferSize;
+ char* mBuffer;
+
+ // mBufferStartOffset is the offset relative to the start of mStream.
+ int64_t mBufferStartOffset;
+
+ // mCursor is the read cursor for input streams, or write cursor for
+ // output streams, and is relative to mBufferStartOffset.
+ uint32_t mCursor;
+
+ // mFillPoint is the amount available in the buffer for input streams,
+ // or the high watermark of bytes written into the buffer, and therefore
+ // is relative to mBufferStartOffset.
+ uint32_t mFillPoint;
+
+ nsISupports* mStream; // cast to appropriate subclass
+
+ bool mBufferDisabled;
+ bool mEOF; // True if mStream is at EOF
+ uint8_t mGetBufferCount;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedInputStream : public nsBufferedStream,
+ public nsIBufferedInputStream,
+ public nsIStreamBufferAccess,
+ public nsIIPCSerializableInputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIBUFFEREDINPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ nsBufferedInputStream() : nsBufferedStream() {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+ nsIInputStream* Source() {
+ return (nsIInputStream*)mStream;
+ }
+
+protected:
+ virtual ~nsBufferedInputStream() {}
+
+ NS_IMETHOD Fill() override;
+ NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedOutputStream final : public nsBufferedStream,
+ public nsISafeOutputStream,
+ public nsIBufferedOutputStream,
+ public nsIStreamBufferAccess
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSISAFEOUTPUTSTREAM
+ NS_DECL_NSIBUFFEREDOUTPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+
+ nsBufferedOutputStream() : nsBufferedStream() {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+ nsIOutputStream* Sink() {
+ return (nsIOutputStream*)mStream;
+ }
+
+protected:
+ virtual ~nsBufferedOutputStream() { nsBufferedOutputStream::Close(); }
+
+ NS_IMETHOD Fill() override { return NS_OK; } // no-op for output streams
+
+ nsCOMPtr<nsISafeOutputStream> mSafeStream; // QI'd from mStream
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsBufferedStreams_h__
diff --git a/netwerk/base/nsChannelClassifier.cpp b/netwerk/base/nsChannelClassifier.cpp
new file mode 100644
index 0000000000..6b9f9ede31
--- /dev/null
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -0,0 +1,696 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsChannelClassifier.h"
+
+#include "mozIThirdPartyUtil.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsICacheEntry.h"
+#include "nsICachingChannel.h"
+#include "nsIChannel.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIParentChannel.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrivateBrowsingTrackingProtectionWhitelist.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISecureBrowserUI.h"
+#include "nsISecurityEventSink.h"
+#include "nsIURL.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+namespace net {
+
+//
+// MOZ_LOG=nsChannelClassifier:5
+//
+static LazyLogModule gChannelClassifierLog("nsChannelClassifier");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(gChannelClassifierLog, LogLevel::Debug)
+
+NS_IMPL_ISUPPORTS(nsChannelClassifier,
+ nsIURIClassifierCallback)
+
+nsChannelClassifier::nsChannelClassifier()
+ : mIsAllowListed(false),
+ mSuspendedChannel(false)
+{
+}
+
+nsresult
+nsChannelClassifier::ShouldEnableTrackingProtection(nsIChannel *aChannel,
+ bool *result)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ NS_ENSURE_ARG(result);
+ *result = false;
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ if (!loadContext || !(loadContext->UseTrackingProtection())) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> topWinURI;
+ rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!topWinURI) {
+ LOG(("nsChannelClassifier[%p]: No window URI\n", this));
+ }
+
+ nsCOMPtr<nsIURI> chanURI;
+ rv = aChannel->GetURI(getter_AddRefs(chanURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Third party checks don't work for chrome:// URIs in mochitests, so just
+ // default to isThirdParty = true. We check isThirdPartyWindow to expand
+ // the list of domains that are considered first party (e.g., if
+ // facebook.com includes an iframe from fatratgames.com, all subsources
+ // included in that iframe are considered third-party with
+ // isThirdPartyChannel, even if they are not third-party w.r.t.
+ // facebook.com), and isThirdPartyChannel to prevent top-level navigations
+ // from being detected as third-party.
+ bool isThirdPartyChannel = true;
+ bool isThirdPartyWindow = true;
+ thirdPartyUtil->IsThirdPartyURI(chanURI, topWinURI, &isThirdPartyWindow);
+ thirdPartyUtil->IsThirdPartyChannel(aChannel, nullptr, &isThirdPartyChannel);
+ if (!isThirdPartyWindow || !isThirdPartyChannel) {
+ *result = false;
+ if (LOG_ENABLED()) {
+ LOG(("nsChannelClassifier[%p]: Skipping tracking protection checks "
+ "for first party or top-level load channel[%p] with uri %s",
+ this, aChannel, chanURI->GetSpecOrDefault().get()));
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char ALLOWLIST_EXAMPLE_PREF[] = "channelclassifier.allowlist_example";
+ if (!topWinURI && Preferences::GetBool(ALLOWLIST_EXAMPLE_PREF, false)) {
+ LOG(("nsChannelClassifier[%p]: Allowlisting test domain\n", this));
+ rv = ios->NewURI(NS_LITERAL_CSTRING("http://allowlisted.example.com"),
+ nullptr, nullptr, getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Take the host/port portion so we can allowlist by site. Also ignore the
+ // scheme, since users who put sites on the allowlist probably don't expect
+ // allowlisting to depend on scheme.
+ nsCOMPtr<nsIURL> url = do_QueryInterface(topWinURI, &rv);
+ if (NS_FAILED(rv)) {
+ return rv; // normal for some loads, no need to print a warning
+ }
+
+ nsCString escaped(NS_LITERAL_CSTRING("https://"));
+ nsAutoCString temp;
+ rv = url->GetHostPort(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ escaped.Append(temp);
+
+ // Stuff the whole thing back into a URI for the permission manager.
+ rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
+ rv = permMgr->TestPermission(topWinURI, "trackingprotection", &permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (permissions == nsIPermissionManager::ALLOW_ACTION) {
+ LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] for %s", this,
+ aChannel, escaped.get()));
+ mIsAllowListed = true;
+ *result = false;
+ } else {
+ *result = true;
+ }
+
+ // In Private Browsing Mode we also check against an in-memory list.
+ if (NS_UsePrivateBrowsing(aChannel)) {
+ nsCOMPtr<nsIPrivateBrowsingTrackingProtectionWhitelist> pbmtpWhitelist =
+ do_GetService(NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = pbmtpWhitelist->ExistsInAllowList(topWinURI, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ mIsAllowListed = true;
+ LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] in PBM for %s",
+ this, aChannel, escaped.get()));
+ }
+
+ *result = !exists;
+ }
+
+ // Tracking protection will be enabled so return without updating
+ // the security state. If any channels are subsequently cancelled
+ // (page elements blocked) the state will be then updated.
+ if (*result) {
+ if (LOG_ENABLED()) {
+ LOG(("nsChannelClassifier[%p]: Enabling tracking protection checks on "
+ "channel[%p] with uri %s for toplevel window %s", this, aChannel,
+ chanURI->GetSpecOrDefault().get(),
+ topWinURI->GetSpecOrDefault().get()));
+ }
+ return NS_OK;
+ }
+
+ // Tracking protection will be disabled so update the security state
+ // of the document and fire a secure change event. If we can't get the
+ // window for the channel, then the shield won't show up so we can't send
+ // an event to the securityUI anyway.
+ return NotifyTrackingProtectionDisabled(aChannel);
+}
+
+// static
+nsresult
+nsChannelClassifier::NotifyTrackingProtectionDisabled(nsIChannel *aChannel)
+{
+ // Can be called in EITHER the parent or child process.
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(aChannel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process request.
+ // Tell the child process channel to do this instead.
+ parentChannel->NotifyTrackingProtectionDisabled();
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, getter_AddRefs(win));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto* pwin = nsPIDOMWindowOuter::From(win);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ if (!docShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocument> doc = docShell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_OK);
+
+ // Notify nsIWebProgressListeners of this security event.
+ // Can be used to change the UI state.
+ nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ uint32_t state = 0;
+ nsCOMPtr<nsISecureBrowserUI> securityUI;
+ docShell->GetSecurityUI(getter_AddRefs(securityUI));
+ if (!securityUI) {
+ return NS_OK;
+ }
+ doc->SetHasTrackingContentLoaded(true);
+ securityUI->GetState(&state);
+ state |= nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT;
+ eventSink->OnSecurityChange(nullptr, state);
+
+ return NS_OK;
+}
+
+void
+nsChannelClassifier::Start(nsIChannel *aChannel)
+{
+ mChannel = aChannel;
+
+ nsresult rv = StartInternal();
+ if (NS_FAILED(rv)) {
+ // If we aren't getting a callback for any reason, assume a good verdict and
+ // make sure we resume the channel if necessary.
+ OnClassifyComplete(NS_OK);
+ }
+}
+
+nsresult
+nsChannelClassifier::StartInternal()
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Don't bother to run the classifier on a load that has already failed.
+ // (this might happen after a redirect)
+ nsresult status;
+ mChannel->GetStatus(&status);
+ if (NS_FAILED(status))
+ return status;
+
+ // Don't bother to run the classifier on a cached load that was
+ // previously classified as good.
+ if (HasBeenClassified(mChannel)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't bother checking certain types of URIs.
+ bool hasFlags;
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_LOCAL_FILE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ // Skip whitelisted hostnames.
+ nsAutoCString whitelisted;
+ Preferences::GetCString("urlclassifier.skipHostnames", &whitelisted);
+ if (!whitelisted.IsEmpty()) {
+ ToLowerCase(whitelisted);
+ LOG(("nsChannelClassifier[%p]:StartInternal whitelisted hostnames = %s",
+ this, whitelisted.get()));
+ if (IsHostnameWhitelisted(uri, whitelisted)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED ||
+ rv == NS_ERROR_NOT_AVAILABLE) {
+ // no URI classifier, ignore this failure.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = securityManager->GetChannelURIPrincipal(mChannel, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool expectCallback;
+ bool trackingProtectionEnabled = false;
+ (void)ShouldEnableTrackingProtection(mChannel, &trackingProtectionEnabled);
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> principalURI;
+ principal->GetURI(getter_AddRefs(principalURI));
+ LOG(("nsChannelClassifier[%p]: Classifying principal %s on channel with "
+ "uri %s", this, principalURI->GetSpecOrDefault().get(),
+ uri->GetSpecOrDefault().get()));
+ }
+ rv = uriClassifier->Classify(principal, trackingProtectionEnabled, this,
+ &expectCallback);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (expectCallback) {
+ // Suspend the channel, it will be resumed when we get the classifier
+ // callback.
+ rv = mChannel->Suspend();
+ if (NS_FAILED(rv)) {
+ // Some channels (including nsJSChannel) fail on Suspend. This
+ // shouldn't be fatal, but will prevent malware from being
+ // blocked on these channels.
+ LOG(("nsChannelClassifier[%p]: Couldn't suspend channel", this));
+ return rv;
+ }
+
+ mSuspendedChannel = true;
+ LOG(("nsChannelClassifier[%p]: suspended channel %p",
+ this, mChannel.get()));
+ } else {
+ LOG(("nsChannelClassifier[%p]: not expecting callback", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsChannelClassifier::IsHostnameWhitelisted(nsIURI *aUri,
+ const nsACString &aWhitelisted)
+{
+ nsAutoCString host;
+ nsresult rv = aUri->GetHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return false;
+ }
+ ToLowerCase(host);
+
+ nsCCharSeparatedTokenizer tokenizer(aWhitelisted, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsCSubstring& token = tokenizer.nextToken();
+ if (token.Equals(host)) {
+ LOG(("nsChannelClassifier[%p]:StartInternal skipping %s (whitelisted)",
+ this, host.get()));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Note in the cache entry that this URL was classified, so that future
+// cached loads don't need to be checked.
+void
+nsChannelClassifier::MarkEntryClassified(nsresult status)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Don't cache tracking classifications because we support allowlisting.
+ if (status == NS_ERROR_TRACKING_URI || mIsAllowListed) {
+ return;
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString errorName;
+ GetErrorName(status, errorName);
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ LOG(("nsChannelClassifier::MarkEntryClassified[%s] %s",
+ errorName.get(), spec.get()));
+ }
+
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
+ if (!cachingChannel) {
+ return;
+ }
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (!cacheToken) {
+ return;
+ }
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ do_QueryInterface(cacheToken);
+ if (!cacheEntry) {
+ return;
+ }
+
+ cacheEntry->SetMetaDataElement("necko:classified",
+ NS_SUCCEEDED(status) ? "1" : nullptr);
+}
+
+bool
+nsChannelClassifier::HasBeenClassified(nsIChannel *aChannel)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsICachingChannel> cachingChannel =
+ do_QueryInterface(aChannel);
+ if (!cachingChannel) {
+ return false;
+ }
+
+ // Only check the tag if we are loading from the cache without
+ // validation.
+ bool fromCache;
+ if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
+ return false;
+ }
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (!cacheToken) {
+ return false;
+ }
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ do_QueryInterface(cacheToken);
+ if (!cacheEntry) {
+ return false;
+ }
+
+ nsXPIDLCString tag;
+ cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag));
+ return tag.EqualsLiteral("1");
+}
+
+//static
+bool
+nsChannelClassifier::SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel)
+{
+ nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo();
+ if (!channelLoadInfo || !docURI) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> channelLoadingPrincipal = channelLoadInfo->LoadingPrincipal();
+ if (!channelLoadingPrincipal) {
+ // TYPE_DOCUMENT loads will not have a channelLoadingPrincipal. But top level
+ // loads should not be blocked by Tracking Protection, so we will return
+ // false
+ return false;
+ }
+ nsCOMPtr<nsIURI> channelLoadingURI;
+ channelLoadingPrincipal->GetURI(getter_AddRefs(channelLoadingURI));
+ if (!channelLoadingURI) {
+ return false;
+ }
+ bool equals = false;
+ nsresult rv = docURI->EqualsExceptRef(channelLoadingURI, &equals);
+ return NS_SUCCEEDED(rv) && equals;
+}
+
+// static
+nsresult
+nsChannelClassifier::SetBlockedTrackingContent(nsIChannel *channel)
+{
+ // Can be called in EITHER the parent or child process.
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(channel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process request. The
+ // actual channel will be notified via the status passed to
+ // nsIRequest::Cancel and do this for us.
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ rv = thirdPartyUtil->GetTopWindowForChannel(channel, getter_AddRefs(win));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ auto* pwin = nsPIDOMWindowOuter::From(win);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ if (!docShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocument> doc = docShell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_OK);
+
+ // This event might come after the user has navigated to another page.
+ // To prevent showing the TrackingProtection UI on the wrong page, we need to
+ // check that the loading URI for the channel is the same as the URI currently
+ // loaded in the document.
+ if (!SameLoadingURI(doc, channel)) {
+ return NS_OK;
+ }
+
+ // Notify nsIWebProgressListeners of this security event.
+ // Can be used to change the UI state.
+ nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ uint32_t state = 0;
+ nsCOMPtr<nsISecureBrowserUI> securityUI;
+ docShell->GetSecurityUI(getter_AddRefs(securityUI));
+ if (!securityUI) {
+ return NS_OK;
+ }
+ doc->SetHasTrackingContentBlocked(true);
+ securityUI->GetState(&state);
+ state |= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT;
+ eventSink->OnSecurityChange(nullptr, state);
+
+ // Log a warning to the web console.
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ NS_ConvertUTF8toUTF16 spec(uri->GetSpecOrDefault());
+ const char16_t* params[] = { spec.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Tracking Protection"),
+ doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "TrackingUriBlocked",
+ params, ArrayLength(params));
+
+ return NS_OK;
+}
+
+nsresult
+nsChannelClassifier::IsTrackerWhitelisted()
+{
+ nsresult rv;
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tables;
+ Preferences::GetCString("urlclassifier.trackingWhitelistTable", &tables);
+
+ if (tables.IsEmpty()) {
+ LOG(("nsChannelClassifier[%p]:IsTrackerWhitelisted whitelist disabled",
+ this));
+ return NS_ERROR_TRACKING_URI;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(mChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> topWinURI;
+ rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!topWinURI) {
+ LOG(("nsChannelClassifier[%p]: No window URI", this));
+ return NS_ERROR_TRACKING_URI;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrincipal> chanPrincipal;
+ rv = securityManager->GetChannelURIPrincipal(mChannel,
+ getter_AddRefs(chanPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Craft a whitelist URL like "toplevel.page/?resource=third.party.domain"
+ nsAutoCString pageHostname, resourceDomain;
+ rv = topWinURI->GetHost(pageHostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = chanPrincipal->GetBaseDomain(resourceDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString whitelistEntry = NS_LITERAL_CSTRING("http://") +
+ pageHostname + NS_LITERAL_CSTRING("/?resource=") + resourceDomain;
+ LOG(("nsChannelClassifier[%p]: Looking for %s in the whitelist",
+ this, whitelistEntry.get()));
+
+ nsCOMPtr<nsIURI> whitelistURI;
+ rv = NS_NewURI(getter_AddRefs(whitelistURI), whitelistEntry);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check whether or not the tracker is in the entity whitelist
+ nsAutoCString results;
+ rv = uriClassifier->ClassifyLocalWithTables(whitelistURI, tables, results);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!results.IsEmpty()) {
+ return NS_OK; // found it on the whitelist, must not be blocked
+ }
+
+ LOG(("nsChannelClassifier[%p]: %s is not in the whitelist",
+ this, whitelistEntry.get()));
+ return NS_ERROR_TRACKING_URI;
+}
+
+NS_IMETHODIMP
+nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (aErrorCode == NS_ERROR_TRACKING_URI &&
+ NS_SUCCEEDED(IsTrackerWhitelisted())) {
+ LOG(("nsChannelClassifier[%p]:OnClassifyComplete tracker found "
+ "in whitelist so we won't block it", this));
+ aErrorCode = NS_OK;
+ }
+
+ if (mSuspendedChannel) {
+ nsAutoCString errorName;
+ if (LOG_ENABLED()) {
+ GetErrorName(aErrorCode, errorName);
+ LOG(("nsChannelClassifier[%p]:OnClassifyComplete %s (suspended channel)",
+ this, errorName.get()));
+ }
+ MarkEntryClassified(aErrorCode);
+
+ if (NS_FAILED(aErrorCode)) {
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ LOG(("nsChannelClassifier[%p]: cancelling channel %p for %s "
+ "with error code %s", this, mChannel.get(),
+ uri->GetSpecOrDefault().get(), errorName.get()));
+ }
+
+ // Channel will be cancelled (page element blocked) due to tracking.
+ // Do update the security state of the document and fire a security
+ // change event.
+ if (aErrorCode == NS_ERROR_TRACKING_URI) {
+ SetBlockedTrackingContent(mChannel);
+ }
+
+ mChannel->Cancel(aErrorCode);
+ }
+ LOG(("nsChannelClassifier[%p]: resuming channel %p from "
+ "OnClassifyComplete", this, mChannel.get()));
+ mChannel->Resume();
+ }
+
+ mChannel = nullptr;
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsChannelClassifier.h b/netwerk/base/nsChannelClassifier.h
new file mode 100644
index 0000000000..20575f3c12
--- /dev/null
+++ b/netwerk/base/nsChannelClassifier.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsChannelClassifier_h__
+#define nsChannelClassifier_h__
+
+#include "nsIURIClassifier.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsIChannel;
+class nsIHttpChannelInternal;
+class nsIDocument;
+
+namespace mozilla {
+namespace net {
+
+class nsChannelClassifier final : public nsIURIClassifierCallback
+{
+public:
+ nsChannelClassifier();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURICLASSIFIERCALLBACK
+
+ // Calls nsIURIClassifier.Classify with the principal of the given channel,
+ // and cancels the channel on a bad verdict.
+ void Start(nsIChannel *aChannel);
+ // Whether or not tracking protection should be enabled on this channel.
+ nsresult ShouldEnableTrackingProtection(nsIChannel *aChannel, bool *result);
+
+private:
+ // True if the channel is on the allow list.
+ bool mIsAllowListed;
+ // True if the channel has been suspended.
+ bool mSuspendedChannel;
+ nsCOMPtr<nsIChannel> mChannel;
+
+ ~nsChannelClassifier() {}
+ // Caches good classifications for the channel principal.
+ void MarkEntryClassified(nsresult status);
+ bool HasBeenClassified(nsIChannel *aChannel);
+ // Helper function so that we ensure we call ContinueBeginConnect once
+ // Start is called. Returns NS_OK if and only if we will get a callback
+ // from the classifier service.
+ nsresult StartInternal();
+ // Helper function to check a tracking URI against the whitelist
+ nsresult IsTrackerWhitelisted();
+ // Helper function to check a URI against the hostname whitelist
+ bool IsHostnameWhitelisted(nsIURI *aUri, const nsACString &aWhitelisted);
+ // Checks that the channel was loaded by the URI currently loaded in aDoc
+ static bool SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel);
+
+public:
+ // If we are blocking tracking content, update the corresponding flag in
+ // the respective docshell and call nsISecurityEventSink::onSecurityChange.
+ static nsresult SetBlockedTrackingContent(nsIChannel *channel);
+ static nsresult NotifyTrackingProtectionDisabled(nsIChannel *aChannel);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsDNSPrefetch.cpp b/netwerk/base/nsDNSPrefetch.cpp
new file mode 100644
index 0000000000..e09315ed1f
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDNSPrefetch.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+#include "nsIURI.h"
+
+static nsIDNSService *sDNSService = nullptr;
+
+nsresult
+nsDNSPrefetch::Initialize(nsIDNSService *aDNSService)
+{
+ NS_IF_RELEASE(sDNSService);
+ sDNSService = aDNSService;
+ NS_IF_ADDREF(sDNSService);
+ return NS_OK;
+}
+
+nsresult
+nsDNSPrefetch::Shutdown()
+{
+ NS_IF_RELEASE(sDNSService);
+ return NS_OK;
+}
+
+nsDNSPrefetch::nsDNSPrefetch(nsIURI *aURI,
+ nsIDNSListener *aListener,
+ bool storeTiming)
+ : mStoreTiming(storeTiming)
+ , mListener(do_GetWeakReference(aListener))
+{
+ aURI->GetAsciiHost(mHostname);
+}
+
+nsresult
+nsDNSPrefetch::Prefetch(uint16_t flags)
+{
+ if (mHostname.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!sDNSService)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ if (mStoreTiming)
+ mStartTimestamp = mozilla::TimeStamp::Now();
+ // If AsyncResolve fails, for example because prefetching is disabled,
+ // then our timing will be useless. However, in such a case,
+ // mEndTimestamp will be a null timestamp and callers should check
+ // TimingsValid() before using the timing.
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ return sDNSService->AsyncResolve(mHostname,
+ flags | nsIDNSService::RESOLVE_SPECULATE,
+ this, mainThread,
+ getter_AddRefs(tmpOutstanding));
+}
+
+nsresult
+nsDNSPrefetch::PrefetchLow(bool refreshDNS)
+{
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW |
+ (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0));
+}
+
+nsresult
+nsDNSPrefetch::PrefetchMedium(bool refreshDNS)
+{
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0));
+}
+
+nsresult
+nsDNSPrefetch::PrefetchHigh(bool refreshDNS)
+{
+ return Prefetch(refreshDNS ?
+ nsIDNSService::RESOLVE_BYPASS_CACHE : 0);
+}
+
+
+NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener)
+
+NS_IMETHODIMP
+nsDNSPrefetch::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
+ if (mStoreTiming) {
+ mEndTimestamp = mozilla::TimeStamp::Now();
+ }
+ nsCOMPtr<nsIDNSListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ listener->OnLookupComplete(request, rec, status);
+ }
+ return NS_OK;
+}
diff --git a/netwerk/base/nsDNSPrefetch.h b/netwerk/base/nsDNSPrefetch.h
new file mode 100644
index 0000000000..3ad6d4bf0f
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDNSPrefetch_h___
+#define nsDNSPrefetch_h___
+
+#include "nsWeakReference.h"
+#include "nsString.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+
+#include "nsIDNSListener.h"
+
+class nsIURI;
+class nsIDNSService;
+
+class nsDNSPrefetch final : public nsIDNSListener
+{
+ ~nsDNSPrefetch() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ nsDNSPrefetch(nsIURI *aURI, nsIDNSListener *aListener, bool storeTiming);
+ bool TimingsValid() const {
+ return !mStartTimestamp.IsNull() && !mEndTimestamp.IsNull();
+ }
+ // Only use the two timings if TimingsValid() returns true
+ const mozilla::TimeStamp& StartTimestamp() const { return mStartTimestamp; }
+ const mozilla::TimeStamp& EndTimestamp() const { return mEndTimestamp; }
+
+ static nsresult Initialize(nsIDNSService *aDNSService);
+ static nsresult Shutdown();
+
+ // Call one of the following methods to start the Prefetch.
+ nsresult PrefetchHigh(bool refreshDNS = false);
+ nsresult PrefetchMedium(bool refreshDNS = false);
+ nsresult PrefetchLow(bool refreshDNS = false);
+
+private:
+ nsCString mHostname;
+ bool mStoreTiming;
+ mozilla::TimeStamp mStartTimestamp;
+ mozilla::TimeStamp mEndTimestamp;
+ nsWeakPtr mListener;
+
+ nsresult Prefetch(uint16_t flags);
+};
+
+#endif
diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp
new file mode 100644
index 0000000000..87a57fd579
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/*
+
+ The converts a filesystem directory into an "HTTP index" stream per
+ Lou Montulli's original spec:
+
+ http://www.mozilla.org/projects/netlib/dirindexformat.html
+
+ */
+
+#include "nsEscape.h"
+#include "nsDirectoryIndexStream.h"
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "nsISimpleEnumerator.h"
+#ifdef THREADSAFE_I18N
+#include "nsCollationCID.h"
+#include "nsICollation.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
+#endif
+#include "nsIFile.h"
+#include "nsURLHelper.h"
+#include "nsNativeCharsetUtils.h"
+
+// NOTE: This runs on the _file transport_ thread.
+// The problem is that now that we're actually doing something with the data,
+// we want to do stuff like i18n sorting. However, none of the collation stuff
+// is threadsafe.
+// So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current
+// behaviour, though. See bug 99382.
+// When this is fixed, #define THREADSAFE_I18N to get this code working
+
+//#define THREADSAFE_I18N
+
+using namespace mozilla;
+static LazyLogModule gLog("nsDirectoryIndexStream");
+
+nsDirectoryIndexStream::nsDirectoryIndexStream()
+ : mOffset(0), mStatus(NS_OK), mPos(0)
+{
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: created", this));
+}
+
+static int compare(nsIFile* aElement1, nsIFile* aElement2, void* aData)
+{
+ if (!NS_IsNativeUTF8()) {
+ // don't check for errors, because we can't report them anyway
+ nsAutoString name1, name2;
+ aElement1->GetLeafName(name1);
+ aElement2->GetLeafName(name2);
+
+ // Note - we should do the collation to do sorting. Why don't we?
+ // Because that is _slow_. Using TestProtocols to list file:///dev/
+ // goes from 3 seconds to 22. (This may be why nsXULSortService is
+ // so slow as well).
+ // Does this have bad effects? Probably, but since nsXULTree appears
+ // to use the raw RDF literal value as the sort key (which ammounts to an
+ // strcmp), it won't be any worse, I think.
+ // This could be made faster, by creating the keys once,
+ // but CompareString could still be smarter - see bug 99383 - bbaetz
+ // NB - 99393 has been WONTFIXed. So if the I18N code is ever made
+ // threadsafe so that this matters, we'd have to pass through a
+ // struct { nsIFile*, uint8_t* } with the pre-calculated key.
+ return Compare(name1, name2);
+ }
+
+ nsAutoCString name1, name2;
+ aElement1->GetNativeLeafName(name1);
+ aElement2->GetNativeLeafName(name2);
+
+ return Compare(name1, name2);
+}
+
+nsresult
+nsDirectoryIndexStream::Init(nsIFile* aDir)
+{
+ nsresult rv;
+ bool isDir;
+ rv = aDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ NS_PRECONDITION(isDir, "not a directory");
+ if (!isDir)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ nsAutoCString path;
+ aDir->GetNativePath(path);
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: initialized on %s",
+ this, path.get()));
+ }
+
+ // Sigh. We have to allocate on the heap because there are no
+ // assignment operators defined.
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ rv = aDir->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv)) return rv;
+
+ // Now lets sort, because clients expect it that way
+ // XXX - should we do so here, or when the first item is requested?
+ // XXX - use insertion sort instead?
+
+ bool more;
+ nsCOMPtr<nsISupports> elem;
+ while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+ rv = iter->GetNext(getter_AddRefs(elem));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
+ if (file)
+ mArray.AppendObject(file); // addrefs
+ }
+ }
+
+#ifdef THREADSAFE_I18N
+ nsCOMPtr<nsILocaleService> ls = do_GetService(NS_LOCALESERVICE_CONTRACTID,
+ &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsILocale> locale;
+ rv = ls->GetApplicationLocale(getter_AddRefs(locale));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsICollationFactory> cf = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID,
+ &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsICollation> coll;
+ rv = cf->CreateCollation(locale, getter_AddRefs(coll));
+ if (NS_FAILED(rv)) return rv;
+
+ mArray.Sort(compare, coll);
+#else
+ mArray.Sort(compare, nullptr);
+#endif
+
+ mBuf.AppendLiteral("300: ");
+ nsAutoCString url;
+ rv = net_GetURLSpecFromFile(aDir, url);
+ if (NS_FAILED(rv)) return rv;
+ mBuf.Append(url);
+ mBuf.Append('\n');
+
+ mBuf.AppendLiteral("200: filename content-length last-modified file-type\n");
+
+ return NS_OK;
+}
+
+nsDirectoryIndexStream::~nsDirectoryIndexStream()
+{
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: destroyed", this));
+}
+
+nsresult
+nsDirectoryIndexStream::Create(nsIFile* aDir, nsIInputStream** aResult)
+{
+ RefPtr<nsDirectoryIndexStream> result = new nsDirectoryIndexStream();
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = result->Init(aDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream)
+
+// The below routines are proxied to the UI thread!
+NS_IMETHODIMP
+nsDirectoryIndexStream::Close()
+{
+ mStatus = NS_BASE_STREAM_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Available(uint64_t* aLength)
+{
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ // If there's data in our buffer, use that
+ if (mOffset < (int32_t)mBuf.Length()) {
+ *aLength = mBuf.Length() - mOffset;
+ return NS_OK;
+ }
+
+ // Returning one byte is not ideal, but good enough
+ *aLength = (mPos < mArray.Count()) ? 1 : 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount, uint32_t* aReadCount)
+{
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *aReadCount = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ uint32_t nread = 0;
+
+ // If anything is enqueued (or left-over) in mBuf, then feed it to
+ // the reader first.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+
+ // Room left?
+ if (aCount > 0) {
+ mOffset = 0;
+ mBuf.Truncate();
+
+ // Okay, now we'll suck stuff off of our iterator into the mBuf...
+ while (uint32_t(mBuf.Length()) < aCount) {
+ bool more = mPos < mArray.Count();
+ if (!more) break;
+
+ // don't addref, for speed - an addref happened when it
+ // was placed in the array, so it's not going to go stale
+ nsIFile* current = mArray.ObjectAt(mPos);
+ ++mPos;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ nsAutoCString path;
+ current->GetNativePath(path);
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: iterated %s",
+ this, path.get()));
+ }
+
+ // rjc: don't return hidden files/directories!
+ // bbaetz: why not?
+ nsresult rv;
+#ifndef XP_UNIX
+ bool hidden = false;
+ current->IsHidden(&hidden);
+ if (hidden) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: skipping hidden file/directory",
+ this));
+ continue;
+ }
+#endif
+
+ int64_t fileSize = 0;
+ current->GetFileSize( &fileSize );
+
+ PRTime fileInfoModifyTime = 0;
+ current->GetLastModifiedTime( &fileInfoModifyTime );
+ fileInfoModifyTime *= PR_USEC_PER_MSEC;
+
+ mBuf.AppendLiteral("201: ");
+
+ // The "filename" field
+ if (!NS_IsNativeUTF8()) {
+ nsAutoString leafname;
+ rv = current->GetLeafName(leafname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escaped;
+ if (!leafname.IsEmpty() &&
+ NS_Escape(NS_ConvertUTF16toUTF8(leafname), escaped, url_Path)) {
+ mBuf.Append(escaped);
+ mBuf.Append(' ');
+ }
+ } else {
+ nsAutoCString leafname;
+ rv = current->GetNativeLeafName(leafname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escaped;
+ if (!leafname.IsEmpty() &&
+ NS_Escape(leafname, escaped, url_Path)) {
+ mBuf.Append(escaped);
+ mBuf.Append(' ');
+ }
+ }
+
+ // The "content-length" field
+ mBuf.AppendInt(fileSize, 10);
+ mBuf.Append(' ');
+
+ // The "last-modified" field
+ PRExplodedTime tm;
+ PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm);
+ {
+ char buf[64];
+ PR_FormatTimeUSEnglish(buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
+ mBuf.Append(buf);
+ }
+
+ // The "file-type" field
+ bool isFile = true;
+ current->IsFile(&isFile);
+ if (isFile) {
+ mBuf.AppendLiteral("FILE ");
+ }
+ else {
+ bool isDir;
+ rv = current->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ if (isDir) {
+ mBuf.AppendLiteral("DIRECTORY ");
+ }
+ else {
+ bool isLink;
+ rv = current->IsSymlink(&isLink);
+ if (NS_FAILED(rv)) return rv;
+ if (isLink) {
+ mBuf.AppendLiteral("SYMBOLIC-LINK ");
+ }
+ }
+ }
+
+ mBuf.Append('\n');
+ }
+
+ // ...and once we've either run out of directory entries, or
+ // filled up the buffer, then we'll push it to the reader.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+ }
+
+ *aReadCount = nread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsDirectoryIndexStream.h b/netwerk/base/nsDirectoryIndexStream.h
new file mode 100644
index 0000000000..5403c7af2d
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDirectoryIndexStream_h__
+#define nsDirectoryIndexStream_h__
+
+#include "mozilla/Attributes.h"
+
+#include "nsString.h"
+#include "nsIInputStream.h"
+#include "nsCOMArray.h"
+
+class nsIFile;
+
+class nsDirectoryIndexStream final : public nsIInputStream
+{
+private:
+ nsCString mBuf;
+ int32_t mOffset;
+ nsresult mStatus;
+
+ int32_t mPos; // position within mArray
+ nsCOMArray<nsIFile> mArray; // file objects within the directory
+
+ nsDirectoryIndexStream();
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ nsresult Init(nsIFile* aDir);
+ ~nsDirectoryIndexStream();
+
+public:
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ static nsresult
+ Create(nsIFile* aDir, nsIInputStream** aStreamResult);
+
+ // nsISupportsInterface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIInputStream interface
+ NS_DECL_NSIINPUTSTREAM
+};
+
+#endif // nsDirectoryIndexStream_h__
+
diff --git a/netwerk/base/nsDownloader.cpp b/netwerk/base/nsDownloader.cpp
new file mode 100644
index 0000000000..6248be8f1e
--- /dev/null
+++ b/netwerk/base/nsDownloader.cpp
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDownloader.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNetUtil.h"
+#include "nsCRTGlue.h"
+
+nsDownloader::~nsDownloader()
+{
+ if (mLocation && mLocationIsTemp) {
+ // release the sink first since it may still hold an open file
+ // descriptor to mLocation. this needs to happen before the
+ // file can be removed otherwise the Remove call will fail.
+ if (mSink) {
+ mSink->Close();
+ mSink = nullptr;
+ }
+
+ nsresult rv = mLocation->Remove(false);
+ if (NS_FAILED(rv))
+ NS_ERROR("unable to remove temp file");
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsDownloader,
+ nsIDownloader,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+nsDownloader::Init(nsIDownloadObserver *observer, nsIFile *location)
+{
+ mObserver = observer;
+ mLocation = location;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ nsresult rv;
+ if (!mLocation) {
+ nsCOMPtr<nsIFile> location;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(location));
+ if (NS_FAILED(rv)) return rv;
+
+ char buf[13];
+ NS_MakeRandomString(buf, 8);
+ memcpy(buf+8, ".tmp", 5);
+ rv = location->AppendNative(nsDependentCString(buf, 12));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = location->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv)) return rv;
+
+ location.swap(mLocation);
+ mLocationIsTemp = true;
+ }
+
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(mSink), mLocation);
+ if (NS_FAILED(rv)) return rv;
+
+ // we could wrap this output stream with a buffered output stream,
+ // but it shouldn't be necessary since we will be writing large
+ // chunks given to us via OnDataAvailable.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnStopRequest(nsIRequest *request,
+ nsISupports *ctxt,
+ nsresult status)
+{
+ if (mSink) {
+ mSink->Close();
+ mSink = nullptr;
+ }
+
+ mObserver->OnDownloadComplete(this, request, ctxt, status, mLocation);
+ mObserver = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloader::ConsumeData(nsIInputStream* in,
+ void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ nsDownloader *self = (nsDownloader *) closure;
+ if (self->mSink)
+ return self->mSink->Write(fromRawSegment, count, writeCount);
+
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ uint32_t n;
+ return inStr->ReadSegments(ConsumeData, this, count, &n);
+}
diff --git a/netwerk/base/nsDownloader.h b/netwerk/base/nsDownloader.h
new file mode 100644
index 0000000000..b5c22393d5
--- /dev/null
+++ b/netwerk/base/nsDownloader.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDownloader_h__
+#define nsDownloader_h__
+
+#include "nsIDownloader.h"
+#include "nsCOMPtr.h"
+
+class nsIFile;
+class nsIOutputStream;
+
+class nsDownloader : public nsIDownloader
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOWNLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsDownloader() : mLocationIsTemp(false) {}
+
+protected:
+ virtual ~nsDownloader();
+
+ static nsresult ConsumeData(nsIInputStream *in,
+ void *closure,
+ const char *fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount);
+
+ nsCOMPtr<nsIDownloadObserver> mObserver;
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIOutputStream> mSink;
+ bool mLocationIsTemp;
+};
+
+#endif // nsDownloader_h__
diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp
new file mode 100644
index 0000000000..2ddb7ae983
--- /dev/null
+++ b/netwerk/base/nsFileStreams.cpp
@@ -0,0 +1,1153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/IPCMessageUtils.h"
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+#include <unistd.h>
+#elif defined(XP_WIN)
+#include <windows.h>
+#include "nsILocalFileWin.h"
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+#include "private/pprio.h"
+
+#include "nsFileStreams.h"
+#include "nsIFile.h"
+#include "nsReadLine.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/FileUtils.h"
+#include "nsNetCID.h"
+#include "nsXULAppAPI.h"
+
+#define NS_NO_INPUT_BUFFERING 1 // see http://bugzilla.mozilla.org/show_bug.cgi?id=41067
+
+typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType;
+
+using namespace mozilla::ipc;
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileStreamBase
+
+nsFileStreamBase::nsFileStreamBase()
+ : mFD(nullptr)
+ , mBehaviorFlags(0)
+ , mDeferredOpen(false)
+{
+}
+
+nsFileStreamBase::~nsFileStreamBase()
+{
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsFileStreamBase,
+ nsISeekableStream,
+ nsIFileMetadata)
+
+NS_IMETHODIMP
+nsFileStreamBase::Seek(int32_t whence, int64_t offset)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence);
+ if (cnt == int64_t(-1)) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::Tell(int64_t *result)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR);
+ if (cnt == int64_t(-1)) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::SetEOF()
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+ // Some system calls require an EOF offset.
+ int64_t offset;
+ rv = Tell(&offset);
+ if (NS_FAILED(rv)) return rv;
+#endif
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+ if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_WIN)
+ if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(mFD))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+#else
+ // XXX not implemented
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetSize(int64_t* _retval)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFD) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ PRFileInfo64 info;
+ if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ *_retval = int64_t(info.size);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetLastModified(int64_t* _retval)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFD) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ PRFileInfo64 info;
+ if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ int64_t modTime = int64_t(info.modifyTime);
+ if (modTime == 0) {
+ *_retval = 0;
+ }
+ else {
+ *_retval = modTime / int64_t(PR_USEC_PER_MSEC);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval)
+{
+ nsresult rv = DoPendingOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mFD) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *_retval = mFD;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::Close()
+{
+ CleanUpOpen();
+
+ nsresult rv = NS_OK;
+ if (mFD) {
+ if (PR_Close(mFD) == PR_FAILURE)
+ rv = NS_BASE_STREAM_OSERROR;
+ mFD = nullptr;
+ }
+ return rv;
+}
+
+nsresult
+nsFileStreamBase::Available(uint64_t* aResult)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFD) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ // PR_Available with files over 4GB returns an error, so we have to
+ // use the 64-bit version of PR_Available.
+ int64_t avail = PR_Available64(mFD);
+ if (avail == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ // If available is greater than 4GB, return 4GB
+ *aResult = (uint64_t)avail;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
+{
+ nsresult rv = DoPendingOpen();
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ // Don't warn if this is just a deferred file not found.
+ return rv;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFD) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ int32_t bytesRead = PR_Read(mFD, aBuf, aCount);
+ if (bytesRead == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ *aResult = bytesRead;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ // ReadSegments is not implemented because it would be inefficient when
+ // the writer does not consume all data. If you want to call ReadSegments,
+ // wrap a BufferedInputStream around the file stream. That will call
+ // Read().
+
+ // If this is ever implemented you might need to modify
+ // nsPartialFileInputStream::ReadSegments
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsFileStreamBase::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::Flush(void)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int32_t cnt = PR_Sync(mFD);
+ if (cnt == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int32_t cnt = PR_Write(mFD, buf, count);
+ if (cnt == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ NS_NOTREACHED("WriteFrom (see source comment)");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+nsresult
+nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+nsresult
+nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags,
+ int32_t aPerm, bool aDeferred)
+{
+ NS_ENSURE_STATE(aFile);
+
+ mOpenParams.ioFlags = aIoFlags;
+ mOpenParams.perm = aPerm;
+
+ if (aDeferred) {
+ // Clone the file, as it may change between now and the deferred open
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOpenParams.localFile = do_QueryInterface(file);
+ NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED);
+
+ mDeferredOpen = true;
+ return NS_OK;
+ }
+
+ mOpenParams.localFile = aFile;
+
+ // Following call open() at main thread.
+ // Main thread might be blocked, while open a remote file.
+ return DoOpen();
+}
+
+void
+nsFileStreamBase::CleanUpOpen()
+{
+ mOpenParams.localFile = nullptr;
+ mDeferredOpen = false;
+}
+
+nsresult
+nsFileStreamBase::DoOpen()
+{
+ NS_ASSERTION(!mFD, "Already have a file descriptor!");
+ NS_ASSERTION(mOpenParams.localFile, "Must have a file to open");
+
+ PRFileDesc* fd;
+ nsresult rv;
+
+#ifdef XP_WIN
+ if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) {
+ nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(mOpenParams.localFile);
+ MOZ_ASSERT(file);
+
+ rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags,
+ mOpenParams.perm,
+ &fd);
+ } else
+#endif // XP_WIN
+ {
+ rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags,
+ mOpenParams.perm,
+ &fd);
+ }
+
+ CleanUpOpen();
+ if (NS_FAILED(rv))
+ return rv;
+ mFD = fd;
+
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::DoPendingOpen()
+{
+ if (!mDeferredOpen) {
+ return NS_OK;
+ }
+
+ return DoOpen();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase)
+NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase)
+
+NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_LOCALFILEINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_IMPL_QUERY_CLASSINFO(nsFileInputStream)
+NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream,
+ nsIInputStream,
+ nsIFileInputStream,
+ nsISeekableStream,
+ nsILineInputStream)
+
+nsresult
+nsFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsFileInputStream* stream = new nsFileInputStream();
+ if (stream == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+nsresult
+nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm)
+{
+ nsresult rv = NS_OK;
+
+ // If the previous file is open, close it
+ if (mFD) {
+ rv = Close();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Open the file
+ if (aIOFlags == -1)
+ aIOFlags = PR_RDONLY;
+ if (aPerm == -1)
+ aPerm = 0;
+
+ rv = MaybeOpen(aFile, aIOFlags, aPerm,
+ mBehaviorFlags & nsIFileInputStream::DEFER_OPEN);
+
+ if (NS_FAILED(rv)) return rv;
+
+ // if defer open is set, do not remove the file here.
+ // remove the file while Close() is called.
+ if ((mBehaviorFlags & DELETE_ON_CLOSE) &&
+ !(mBehaviorFlags & nsIFileInputStream::DEFER_OPEN)) {
+ // POSIX compatible filesystems allow a file to be unlinked while a
+ // file descriptor is still referencing the file. since we've already
+ // opened the file descriptor, we'll try to remove the file. if that
+ // fails, then we'll just remember the nsIFile and remove it after we
+ // close the file descriptor.
+ rv = aFile->Remove(false);
+ if (NS_SUCCEEDED(rv)) {
+ // No need to remove it later. Clear the flag.
+ mBehaviorFlags &= ~DELETE_ON_CLOSE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
+ int32_t aBehaviorFlags)
+{
+ NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = aBehaviorFlags;
+
+ mFile = aFile;
+ mIOFlags = aIOFlags;
+ mPerm = aPerm;
+
+ return Open(aFile, aIOFlags, aPerm);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Close()
+{
+ // Get the cache position at the time the file was close. This allows
+ // NS_SEEK_CUR on a closed file that has been opened with
+ // REOPEN_ON_REWIND.
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ // Get actual position. Not one modified by subclasses
+ nsFileStreamBase::Tell(&mCachedPosition);
+ }
+
+ // null out mLineBuffer in case Close() is called again after failing
+ mLineBuffer = nullptr;
+ nsresult rv = nsFileStreamBase::Close();
+ if (NS_FAILED(rv)) return rv;
+ if (mFile && (mBehaviorFlags & DELETE_ON_CLOSE)) {
+ rv = mFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to delete file");
+ // If we don't need to save the file for reopening, free it up
+ if (!(mBehaviorFlags & REOPEN_ON_REWIND)) {
+ mFile = nullptr;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
+{
+ nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ // Don't warn if this is a deffered file not found.
+ return rv;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if we're at the end of file and need to close
+ if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult)
+{
+ if (!mLineBuffer) {
+ mLineBuffer = new nsLineBuffer<char>;
+ }
+ return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ return SeekInternal(aWhence, aOffset);
+}
+
+nsresult
+nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset, bool aClearBuf)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aClearBuf) {
+ mLineBuffer = nullptr;
+ }
+ if (!mFD) {
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ rv = Open(mFile, mIOFlags, mPerm);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the file was closed, and we do a relative seek, use the
+ // position we cached when we closed the file to seek to the right
+ // location.
+ if (aWhence == NS_SEEK_CUR) {
+ aWhence = NS_SEEK_SET;
+ aOffset += mCachedPosition;
+ }
+ } else {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ }
+
+ return nsFileStreamBase::Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Tell(int64_t *aResult)
+{
+ return nsFileStreamBase::Tell(aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Available(uint64_t *aResult)
+{
+ return nsFileStreamBase::Available(aResult);
+}
+
+void
+nsFileInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ FileInputStreamParams params;
+
+ if (NS_SUCCEEDED(DoPendingOpen()) && mFD) {
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
+ NS_ASSERTION(fd, "This should never be null!");
+
+ DebugOnly<FileDescriptor*> dbgFD = aFileDescriptors.AppendElement(fd);
+ NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!");
+
+ params.fileDescriptorIndex() = aFileDescriptors.Length() - 1;
+
+ Close();
+ } else {
+ NS_WARNING("This file has not been opened (or could not be opened). "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptorIndex() = UINT32_MAX;
+ }
+
+ int32_t behaviorFlags = mBehaviorFlags;
+
+ // The receiving process (or thread) is going to have an open file
+ // descriptor automatically so transferring this flag is meaningless.
+ behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
+
+ params.behaviorFlags() = behaviorFlags;
+ params.ioFlags() = mIOFlags;
+
+ aParams = params;
+}
+
+bool
+nsFileInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ NS_ASSERTION(!mFD, "Already have a file descriptor?!");
+ NS_ASSERTION(!mDeferredOpen, "Deferring open?!");
+ NS_ASSERTION(!mFile, "Should never have a file here!");
+ NS_ASSERTION(!mPerm, "This should always be 0!");
+
+ if (aParams.type() != InputStreamParams::TFileInputStreamParams) {
+ NS_WARNING("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const FileInputStreamParams& params = aParams.get_FileInputStreamParams();
+
+ uint32_t fileDescriptorIndex = params.fileDescriptorIndex();
+
+ FileDescriptor fd;
+ if (fileDescriptorIndex < aFileDescriptors.Length()) {
+ fd = aFileDescriptors[fileDescriptorIndex];
+ NS_WARNING_ASSERTION(fd.IsValid(),
+ "Received an invalid file descriptor!");
+ } else {
+ NS_WARNING("Received a bad file descriptor index!");
+ }
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFD = fileDesc;
+ }
+
+ mBehaviorFlags = params.behaviorFlags();
+
+ if (!XRE_IsParentProcess()) {
+ // A child process shouldn't close when it reads the end because it will
+ // not be able to reopen the file later.
+ mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF;
+
+ // A child process will not be able to reopen the file so this flag is
+ // meaningless.
+ mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
+ }
+
+ mIOFlags = params.ioFlags();
+
+ return true;
+}
+
+Maybe<uint64_t>
+nsFileInputStream::ExpectedSerializedLength()
+{
+ return Nothing();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsPartialFileInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsPartialFileInputStream, nsFileStreamBase)
+NS_IMPL_RELEASE_INHERITED(nsPartialFileInputStream, nsFileStreamBase)
+
+NS_IMPL_CLASSINFO(nsPartialFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_PARTIALLOCALFILEINPUTSTREAM_CID)
+
+// Don't forward to nsFileInputStream as we don't want to QI to
+// nsIFileInputStream
+NS_INTERFACE_MAP_BEGIN(nsPartialFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIPartialFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_IMPL_QUERY_CLASSINFO(nsPartialFileInputStream)
+NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsPartialFileInputStream,
+ nsIInputStream,
+ nsIPartialFileInputStream,
+ nsISeekableStream,
+ nsILineInputStream)
+
+nsresult
+nsPartialFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsPartialFileInputStream* stream = new nsPartialFileInputStream();
+
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Init(nsIFile* aFile, uint64_t aStart,
+ uint64_t aLength, int32_t aIOFlags,
+ int32_t aPerm, int32_t aBehaviorFlags)
+{
+ mStart = aStart;
+ mLength = aLength;
+ mPosition = 0;
+
+ nsresult rv = nsFileInputStream::Init(aFile, aIOFlags, aPerm,
+ aBehaviorFlags);
+
+ // aFile is a partial file, it must exist.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDeferredSeek = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Tell(int64_t *aResult)
+{
+ int64_t tell = 0;
+
+ nsresult rv = DoPendingSeek();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsFileInputStream::Tell(&tell);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ *aResult = tell - mStart;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Available(uint64_t* aResult)
+{
+ uint64_t available = 0;
+
+ nsresult rv = DoPendingSeek();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsFileInputStream::Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = TruncateSize(available);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
+{
+ nsresult rv = DoPendingSeek();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t readsize = (uint32_t) TruncateSize(aCount);
+ if (readsize == 0 && mBehaviorFlags & CLOSE_ON_EOF) {
+ Close();
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ rv = nsFileInputStream::Read(aBuf, readsize, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPosition += readsize;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ nsresult rv = DoPendingSeek();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t offset;
+ switch (aWhence) {
+ case NS_SEEK_SET:
+ offset = mStart + aOffset;
+ break;
+ case NS_SEEK_CUR:
+ offset = mStart + mPosition + aOffset;
+ break;
+ case NS_SEEK_END:
+ offset = mStart + mLength + aOffset;
+ break;
+ default:
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (offset < (int64_t)mStart || offset > (int64_t)(mStart + mLength)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = nsFileInputStream::Seek(NS_SEEK_SET, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPosition = offset - mStart;
+ return rv;
+}
+
+void
+nsPartialFileInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ // Serialize the base class first.
+ InputStreamParams fileParams;
+ nsFileInputStream::Serialize(fileParams, aFileDescriptors);
+
+ PartialFileInputStreamParams params;
+
+ params.fileStreamParams() = fileParams.get_FileInputStreamParams();
+ params.begin() = mStart;
+ params.length() = mLength;
+
+ aParams = params;
+}
+
+bool
+nsPartialFileInputStream::Deserialize(
+ const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ NS_ASSERTION(!mFD, "Already have a file descriptor?!");
+ NS_ASSERTION(!mStart, "Already have a start?!");
+ NS_ASSERTION(!mLength, "Already have a length?!");
+ NS_ASSERTION(!mPosition, "Already have a position?!");
+
+ if (aParams.type() != InputStreamParams::TPartialFileInputStreamParams) {
+ NS_WARNING("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const PartialFileInputStreamParams& params =
+ aParams.get_PartialFileInputStreamParams();
+
+ // Deserialize the base class first.
+ InputStreamParams fileParams(params.fileStreamParams());
+ if (!nsFileInputStream::Deserialize(fileParams, aFileDescriptors)) {
+ NS_WARNING("Base class deserialize failed!");
+ return false;
+ }
+
+ NS_ASSERTION(mFD, "Must have a file descriptor now!");
+
+ mStart = params.begin();
+ mLength = params.length();
+ mPosition = 0;
+
+ if (!mStart) {
+ return true;
+ }
+
+ // XXX This is so broken. Main thread IO alert.
+ return NS_SUCCEEDED(nsFileInputStream::Seek(NS_SEEK_SET, mStart));
+}
+
+Maybe<uint64_t>
+nsPartialFileInputStream::ExpectedSerializedLength()
+{
+ return Some(mLength);
+}
+
+
+nsresult
+nsPartialFileInputStream::DoPendingSeek()
+{
+ if (!mDeferredSeek) {
+ return NS_OK;
+ }
+
+ mDeferredSeek = false;
+
+ // This is the first time to open the file, don't clear mLinebuffer.
+ // mLineBuffer might be already initialized by ReadLine().
+ return nsFileInputStream::SeekInternal(NS_SEEK_SET, mStart, false);
+}
+////////////////////////////////////////////////////////////////////////////////
+// nsFileOutputStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream,
+ nsFileStreamBase,
+ nsIOutputStream,
+ nsIFileOutputStream)
+
+nsresult
+nsFileOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsFileOutputStream* stream = new nsFileOutputStream();
+ if (stream == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags)
+{
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = behaviorFlags;
+
+ if (ioFlags == -1)
+ ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
+ if (perm <= 0)
+ perm = 0664;
+
+ return MaybeOpen(file, ioFlags, perm,
+ mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN);
+}
+
+NS_IMETHODIMP
+nsFileOutputStream::Preallocate(int64_t aLength)
+{
+ if (!mFD) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mozilla::fallocate(mFD, aLength)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAtomicFileOutputStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream,
+ nsFileOutputStream,
+ nsISafeOutputStream,
+ nsIOutputStream,
+ nsIFileOutputStream)
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags)
+{
+ // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter
+ // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending
+ // to existing file. So, throw an exception only if `PR_APPEND` is
+ // explicitly specified without `PR_TRUNCATE`.
+ if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
+}
+
+nsresult
+nsAtomicFileOutputStream::DoOpen()
+{
+ // Make sure mOpenParams.localFile will be empty if we bail somewhere in
+ // this function
+ nsCOMPtr<nsIFile> file;
+ file.swap(mOpenParams.localFile);
+
+ if (!file) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ nsresult rv = file->Exists(&mTargetFileExists);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Can't tell if target file exists");
+ mTargetFileExists = true; // Safer to assume it exists - we just do more work.
+ }
+
+ // follow symlinks, for two reasons:
+ // 1) if a user has deliberately set up a profile file as a symlink, we honor it
+ // 2) to make the MoveToNative() in Finish() an atomic operation (which may not
+ // be the case if moving across directories on different filesystems).
+ nsCOMPtr<nsIFile> tempResult;
+ rv = file->Clone(getter_AddRefs(tempResult));
+ if (NS_SUCCEEDED(rv)) {
+ tempResult->SetFollowLinks(true);
+
+ // XP_UNIX ignores SetFollowLinks(), so we have to normalize.
+ if (mTargetFileExists) {
+ tempResult->Normalize();
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && mTargetFileExists) {
+ uint32_t origPerm;
+ if (NS_FAILED(file->GetPermissions(&origPerm))) {
+ NS_ERROR("Can't get permissions of target file");
+ origPerm = mOpenParams.perm;
+ }
+ // XXX What if |perm| is more restrictive then |origPerm|?
+ // This leaves the user supplied permissions as they were.
+ rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ // nsFileOutputStream::DoOpen will work on the temporary file, so we
+ // prepare it and place it in mOpenParams.localFile.
+ mOpenParams.localFile = tempResult;
+ mTempFile = tempResult;
+ mTargetFile = file;
+ rv = nsFileOutputStream::DoOpen();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Close()
+{
+ nsresult rv = nsFileOutputStream::Close();
+
+ // the consumer doesn't want the original file overwritten -
+ // so clean up by removing the temp file.
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Finish()
+{
+ nsresult rv = nsFileOutputStream::Close();
+
+ // if there is no temp file, don't try to move it over the original target.
+ // It would destroy the targetfile if close() is called twice.
+ if (!mTempFile)
+ return rv;
+
+ // Only overwrite if everything was ok, and the temp file could be closed.
+ if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) {
+ NS_ENSURE_STATE(mTargetFile);
+
+ if (!mTargetFileExists) {
+ // If the target file did not exist when we were initialized, then the
+ // temp file we gave out was actually a reference to the target file.
+ // since we succeeded in writing to the temp file (and hence succeeded
+ // in writing to the target file), there is nothing more to do.
+#ifdef DEBUG
+ bool equal;
+ if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal)
+ NS_WARNING("mTempFile not equal to mTargetFile");
+#endif
+ }
+ else {
+ nsAutoString targetFilename;
+ rv = mTargetFile->GetLeafName(targetFilename);
+ if (NS_SUCCEEDED(rv)) {
+ // This will replace target.
+ rv = mTempFile->MoveTo(nullptr, targetFilename);
+ if (NS_FAILED(rv))
+ mTempFile->Remove(false);
+ }
+ }
+ }
+ else {
+ mTempFile->Remove(false);
+
+ // if writing failed, propagate the failure code to the caller.
+ if (NS_FAILED(mWriteResult))
+ rv = mWriteResult;
+ }
+ mTempFile = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ nsresult rv = nsFileOutputStream::Write(buf, count, result);
+ if (NS_SUCCEEDED(mWriteResult)) {
+ if (NS_FAILED(rv))
+ mWriteResult = rv;
+ else if (count != *result)
+ mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+
+ if (NS_FAILED(mWriteResult) && count > 0)
+ NS_WARNING("writing to output stream failed! data may be lost");
+ }
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsSafeFileOutputStream
+
+NS_IMETHODIMP
+nsSafeFileOutputStream::Finish()
+{
+ (void) Flush();
+ return nsAtomicFileOutputStream::Finish();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileStream,
+ nsFileStreamBase,
+ nsIInputStream,
+ nsIOutputStream,
+ nsIFileStream)
+
+NS_IMETHODIMP
+nsFileStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags)
+{
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = behaviorFlags;
+
+ if (ioFlags == -1)
+ ioFlags = PR_RDWR;
+ if (perm <= 0)
+ perm = 0;
+
+ return MaybeOpen(file, ioFlags, perm,
+ mBehaviorFlags & nsIFileStream::DEFER_OPEN);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h
new file mode 100644
index 0000000000..22ef91770c
--- /dev/null
+++ b/netwerk/base/nsFileStreams.h
@@ -0,0 +1,333 @@
+// /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileStreams_h__
+#define nsFileStreams_h__
+
+#include "nsAutoPtr.h"
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsILineInputStream.h"
+#include "nsCOMPtr.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsReadLine.h"
+#include <algorithm>
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileStreamBase : public nsISeekableStream,
+ public nsIFileMetadata
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIFILEMETADATA
+
+ nsFileStreamBase();
+
+protected:
+ virtual ~nsFileStreamBase();
+
+ nsresult Close();
+ nsresult Available(uint64_t* _retval);
+ nsresult Read(char* aBuf, uint32_t aCount, uint32_t* _retval);
+ nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+ nsresult IsNonBlocking(bool* _retval);
+ nsresult Flush();
+ nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* _retval);
+ nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval);
+ nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+
+ PRFileDesc* mFD;
+
+ /**
+ * Flags describing our behavior. See the IDL file for possible values.
+ */
+ int32_t mBehaviorFlags;
+
+ /**
+ * Whether we have a pending open (see DEFER_OPEN in the IDL file).
+ */
+ bool mDeferredOpen;
+
+ struct OpenParams {
+ nsCOMPtr<nsIFile> localFile;
+ int32_t ioFlags;
+ int32_t perm;
+ };
+
+ /**
+ * Data we need to do an open.
+ */
+ OpenParams mOpenParams;
+
+ /**
+ * Prepares the data we need to open the file, and either does the open now
+ * by calling DoOpen(), or leaves it to be opened later by a call to
+ * DoPendingOpen().
+ */
+ nsresult MaybeOpen(nsIFile* aFile, int32_t aIoFlags, int32_t aPerm,
+ bool aDeferred);
+
+ /**
+ * Cleans up data prepared in MaybeOpen.
+ */
+ void CleanUpOpen();
+
+ /**
+ * Open the file. This is called either from MaybeOpen (during Init)
+ * or from DoPendingOpen (if DEFER_OPEN is used when initializing this
+ * stream). The default behavior of DoOpen is to open the file and save the
+ * file descriptor.
+ */
+ virtual nsresult DoOpen();
+
+ /**
+ * If there is a pending open, do it now. It's important for this to be
+ * inline since we do it in almost every stream API call.
+ */
+ inline nsresult DoPendingOpen();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileInputStream : public nsFileStreamBase,
+ public nsIFileInputStream,
+ public nsILineInputStream,
+ public nsIIPCSerializableInputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEINPUTSTREAM
+ NS_DECL_NSILINEINPUTSTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Tell(int64_t *aResult) override;
+ NS_IMETHOD Available(uint64_t* _retval) override;
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
+ uint32_t aCount, uint32_t* _retval) override
+ {
+ return nsFileStreamBase::ReadSegments(aWriter, aClosure, aCount,
+ _retval);
+ }
+ NS_IMETHOD IsNonBlocking(bool* _retval) override
+ {
+ return nsFileStreamBase::IsNonBlocking(_retval);
+ }
+
+ // Overrided from nsFileStreamBase
+ NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override;
+
+ nsFileInputStream()
+ : mLineBuffer(nullptr), mIOFlags(0), mPerm(0), mCachedPosition(0)
+ {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ virtual ~nsFileInputStream()
+ {
+ Close();
+ }
+
+ nsresult SeekInternal(int32_t aWhence, int64_t aOffset, bool aClearBuf=true);
+
+ nsAutoPtr<nsLineBuffer<char> > mLineBuffer;
+
+ /**
+ * The file being opened.
+ */
+ nsCOMPtr<nsIFile> mFile;
+ /**
+ * The IO flags passed to Init() for the file open.
+ */
+ int32_t mIOFlags;
+ /**
+ * The permissions passed to Init() for the file open.
+ */
+ int32_t mPerm;
+
+ /**
+ * Cached position for Tell for automatically reopening streams.
+ */
+ int64_t mCachedPosition;
+
+protected:
+ /**
+ * Internal, called to open a file. Parameters are the same as their
+ * Init() analogues.
+ */
+ nsresult Open(nsIFile* file, int32_t ioFlags, int32_t perm);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsPartialFileInputStream : public nsFileInputStream,
+ public nsIPartialFileInputStream
+{
+public:
+ using nsFileInputStream::Init;
+ using nsFileInputStream::Read;
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPARTIALFILEINPUTSTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ nsPartialFileInputStream()
+ : mStart(0), mLength(0), mPosition(0), mDeferredSeek(false)
+ { }
+
+ NS_IMETHOD Tell(int64_t *aResult) override;
+ NS_IMETHOD Available(uint64_t *aResult) override;
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* aResult) override;
+ NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override;
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ ~nsPartialFileInputStream()
+ { }
+
+ inline nsresult DoPendingSeek();
+
+private:
+ uint64_t TruncateSize(uint64_t aSize) {
+ return std::min<uint64_t>(mLength - mPosition, aSize);
+ }
+
+ uint64_t mStart;
+ uint64_t mLength;
+ uint64_t mPosition;
+ bool mDeferredSeek;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileOutputStream : public nsFileStreamBase,
+ public nsIFileOutputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEOUTPUTSTREAM
+ NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::)
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ virtual ~nsFileOutputStream()
+ {
+ Close();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing is complete. This protects against incomplete writes
+ * due to the process or the thread being interrupted or crashed.
+ */
+class nsAtomicFileOutputStream : public nsFileOutputStream,
+ public nsISafeOutputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISAFEOUTPUTSTREAM
+
+ nsAtomicFileOutputStream() :
+ mTargetFileExists(true),
+ mWriteResult(NS_OK) {}
+
+ virtual nsresult DoOpen() override;
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Write(const char *buf, uint32_t count, uint32_t *result) override;
+ NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm, int32_t behaviorFlags) override;
+
+protected:
+ virtual ~nsAtomicFileOutputStream()
+ {
+ Close();
+ }
+
+ nsCOMPtr<nsIFile> mTargetFile;
+ nsCOMPtr<nsIFile> mTempFile;
+
+ bool mTargetFileExists;
+ nsresult mWriteResult; // Internally set in Write()
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing + flushing is complete. This protects against more
+ * classes of software/hardware errors than nsAtomicFileOutputStream,
+ * at the expense of being more costly to the disk, OS and battery.
+ */
+class nsSafeFileOutputStream : public nsAtomicFileOutputStream
+{
+public:
+
+ NS_IMETHOD Finish() override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileStream : public nsFileStreamBase,
+ public nsIInputStream,
+ public nsIOutputStream,
+ public nsIFileStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILESTREAM
+ NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::)
+
+ // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods
+ // Close() and IsNonBlocking()
+ NS_IMETHOD Flush() override
+ {
+ return nsFileStreamBase::Flush();
+ }
+ NS_IMETHOD Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) override
+ {
+ return nsFileStreamBase::Write(aBuf, aCount, _retval);
+ }
+ NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval) override
+ {
+ return nsFileStreamBase::WriteFrom(aFromStream, aCount, _retval);
+ }
+ NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override
+ {
+ return nsFileStreamBase::WriteSegments(aReader, aClosure, aCount,
+ _retval);
+ }
+
+protected:
+ virtual ~nsFileStream()
+ {
+ Close();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsFileStreams_h__
diff --git a/netwerk/base/nsIApplicationCache.idl b/netwerk/base/nsIApplicationCache.idl
new file mode 100644
index 0000000000..9922feb597
--- /dev/null
+++ b/netwerk/base/nsIApplicationCache.idl
@@ -0,0 +1,205 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIFile;
+interface nsIURI;
+
+/**
+ * Application caches can store a set of namespace entries that affect
+ * loads from the application cache. If a load from the cache fails
+ * to match an exact cache entry, namespaces entries will be searched
+ * for a substring match, and should be applied appropriately.
+ */
+[scriptable, uuid(96e4c264-2065-4ce9-93bb-43734c62c4eb)]
+interface nsIApplicationCacheNamespace : nsISupports
+{
+ /**
+ * Items matching this namespace can be fetched from the network
+ * when loading from this cache. The "data" attribute is unused.
+ */
+ const unsigned long NAMESPACE_BYPASS = 1 << 0;
+
+ /**
+ * Items matching this namespace can be fetched from the network
+ * when loading from this cache. If the load fails, the cache entry
+ * specified by the "data" attribute should be loaded instead.
+ */
+ const unsigned long NAMESPACE_FALLBACK = 1 << 1;
+
+ /**
+ * Items matching this namespace should be cached
+ * opportunistically. Successful toplevel loads of documents
+ * in this namespace should be placed in the application cache.
+ * Namespaces specifying NAMESPACE_OPPORTUNISTIC may also specify
+ * NAMESPACE_FALLBACK to supply a fallback entry.
+ */
+ const unsigned long NAMESPACE_OPPORTUNISTIC = 1 << 2;
+
+ /**
+ * Initialize the namespace.
+ */
+ void init(in unsigned long itemType,
+ in ACString namespaceSpec,
+ in ACString data);
+
+ /**
+ * The namespace type.
+ */
+ readonly attribute unsigned long itemType;
+
+ /**
+ * The prefix of this namespace. This should be the asciiSpec of the
+ * URI prefix.
+ */
+ readonly attribute ACString namespaceSpec;
+
+ /**
+ * Data associated with this namespace, such as a fallback. URI data should
+ * use the asciiSpec of the URI.
+ */
+ readonly attribute ACString data;
+};
+
+/**
+ * Application caches store resources for offline use. Each
+ * application cache has a unique client ID for use with
+ * nsICacheService::openSession() to access the cache's entries.
+ *
+ * Each entry in the application cache can be marked with a set of
+ * types, as discussed in the WHAT-WG offline applications
+ * specification.
+ *
+ * All application caches with the same group ID belong to a cache
+ * group. Each group has one "active" cache that will service future
+ * loads. Inactive caches will be removed from the cache when they are
+ * no longer referenced.
+ */
+[scriptable, uuid(06568DAE-C374-4383-A122-0CC96C7177F2)]
+interface nsIApplicationCache : nsISupports
+{
+ /**
+ * Init this application cache instance to just hold the group ID and
+ * the client ID to work just as a handle to the real cache. Used on
+ * content process to simplify the application cache code.
+ */
+ void initAsHandle(in ACString groupId, in ACString clientId);
+
+ /**
+ * Entries in an application cache can be marked as one or more of
+ * the following types.
+ */
+
+ /* This item is the application manifest. */
+ const unsigned long ITEM_MANIFEST = 1 << 0;
+
+ /* This item was explicitly listed in the application manifest. */
+ const unsigned long ITEM_EXPLICIT = 1 << 1;
+
+ /* This item was navigated in a toplevel browsing context, and
+ * named this cache's group as its manifest. */
+ const unsigned long ITEM_IMPLICIT = 1 << 2;
+
+ /* This item was added by the dynamic scripting API */
+ const unsigned long ITEM_DYNAMIC = 1 << 3;
+
+ /* This item was listed in the application manifest, but named a
+ * different cache group as its manifest. */
+ const unsigned long ITEM_FOREIGN = 1 << 4;
+
+ /* This item was listed as a fallback entry. */
+ const unsigned long ITEM_FALLBACK = 1 << 5;
+
+ /* This item matched an opportunistic cache namespace and was
+ * cached accordingly. */
+ const unsigned long ITEM_OPPORTUNISTIC = 1 << 6;
+
+ /**
+ * URI of the manfiest specifying this application cache.
+ **/
+ readonly attribute nsIURI manifestURI;
+
+ /**
+ * The group ID for this cache group. It is an internally generated string
+ * and cannot be used as manifest URL spec.
+ **/
+ readonly attribute ACString groupID;
+
+ /**
+ * The client ID for this application cache. Clients can open a
+ * session with nsICacheService::createSession() using this client
+ * ID and a storage policy of STORE_OFFLINE to access this cache.
+ */
+ readonly attribute ACString clientID;
+
+ /**
+ * TRUE if the cache is the active cache for this group.
+ */
+ readonly attribute boolean active;
+
+ /**
+ * The disk usage of the application cache, in bytes.
+ */
+ readonly attribute unsigned long usage;
+
+ /**
+ * Makes this cache the active application cache for this group.
+ * Future loads associated with this group will come from this
+ * cache. Other caches from this cache group will be deactivated.
+ */
+ void activate();
+
+ /**
+ * Discard this application cache. Removes all cached resources
+ * for this cache. If this is the active application cache for the
+ * group, the group will be removed.
+ */
+ void discard();
+
+ /**
+ * Adds item types to a given entry.
+ */
+ void markEntry(in ACString key, in unsigned long typeBits);
+
+ /**
+ * Removes types from a given entry. If the resulting entry has
+ * no types left, the entry is removed.
+ */
+ void unmarkEntry(in ACString key, in unsigned long typeBits);
+
+ /**
+ * Gets the types for a given entry.
+ */
+ unsigned long getTypes(in ACString key);
+
+ /**
+ * Returns any entries in the application cache whose type matches
+ * one or more of the bits in typeBits.
+ */
+ void gatherEntries(in uint32_t typeBits,
+ out unsigned long count,
+ [array, size_is(count)] out string keys);
+
+ /**
+ * Add a set of namespace entries to the application cache.
+ * @param namespaces
+ * An nsIArray of nsIApplicationCacheNamespace entries.
+ */
+ void addNamespaces(in nsIArray namespaces);
+
+ /**
+ * Get the most specific namespace matching a given key.
+ */
+ nsIApplicationCacheNamespace getMatchingNamespace(in ACString key);
+
+ /**
+ * If set, this offline cache is placed in a different directory
+ * than the current application profile.
+ */
+ readonly attribute nsIFile profileDirectory;
+};
diff --git a/netwerk/base/nsIApplicationCacheChannel.idl b/netwerk/base/nsIApplicationCacheChannel.idl
new file mode 100644
index 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..9b2b16955e
--- /dev/null
+++ b/netwerk/base/nsIApplicationCacheService.idl
@@ -0,0 +1,110 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIApplicationCache;
+interface nsIFile;
+interface nsIURI;
+interface nsILoadContextInfo;
+
+/**
+ * The application cache service manages the set of application cache
+ * groups.
+ */
+[scriptable, uuid(b8b6546c-6cec-4bda-82df-08e006a97b56)]
+interface nsIApplicationCacheService : nsISupports
+{
+ /**
+ * Create group string identifying cache group according the manifest
+ * URL and the given principal.
+ */
+ ACString buildGroupIDForInfo(in nsIURI aManifestURL,
+ in nsILoadContextInfo aLoadContextInfo);
+ ACString buildGroupIDForSuffix(in nsIURI aManifestURL,
+ in ACString aOriginSuffix);
+
+ /**
+ * Create a new, empty application cache for the given cache
+ * group.
+ */
+ nsIApplicationCache createApplicationCache(in ACString group);
+
+ /**
+ * Create a new, empty application cache for the given cache
+ * group residing in a custom directory with a custom quota.
+ *
+ * @param group
+ * URL of the manifest
+ * @param directory
+ * Actually a reference to a profile directory where to
+ * create the OfflineCache sub-dir.
+ * @param quota
+ * Optional override of the default quota.
+ */
+ nsIApplicationCache createCustomApplicationCache(in ACString group,
+ in nsIFile profileDir,
+ in int32_t quota);
+
+ /**
+ * Get an application cache object for the given client ID.
+ */
+ nsIApplicationCache getApplicationCache(in ACString clientID);
+
+ /**
+ * Get the currently active cache object for a cache group.
+ */
+ nsIApplicationCache getActiveCache(in ACString group);
+
+ /**
+ * Deactivate the currently-active cache object for a cache group.
+ */
+ void deactivateGroup(in ACString group);
+
+ /**
+ * Evict offline cache entries, either all of them or those belonging
+ * to the given origin.
+ */
+ void evict(in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Delete caches whom origin attributes matches the given pattern.
+ */
+ void evictMatchingOriginAttributes(in AString aPattern);
+
+ /**
+ * Try to find the best application cache to serve a resource.
+ */
+ nsIApplicationCache chooseApplicationCache(in ACString key,
+ [optional] in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Flags the key as being opportunistically cached.
+ *
+ * This method should also propagate the entry to other
+ * application caches with the same opportunistic namespace, but
+ * this is not currently implemented.
+ *
+ * @param cache
+ * The cache in which the entry is cached now.
+ * @param key
+ * The cache entry key.
+ */
+ void cacheOpportunistically(in nsIApplicationCache cache, in ACString key);
+
+ /**
+ * Get the list of application cache groups.
+ */
+ void getGroups([optional] out unsigned long count,
+ [array, size_is(count), retval] out string groupIDs);
+
+ /**
+ * Get the list of application cache groups in the order of
+ * activating time.
+ */
+ void getGroupsTimeOrdered([optional] out unsigned long count,
+ [array, size_is(count), retval] out string groupIDs);
+};
diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl
new file mode 100644
index 0000000000..430f63b2ef
--- /dev/null
+++ b/netwerk/base/nsIArrayBufferInputStream.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+/**
+ * nsIArrayBufferInputStream
+ *
+ * Provides scriptable methods for initializing a nsIInputStream
+ * implementation with an ArrayBuffer.
+ */
+[scriptable, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)]
+interface nsIArrayBufferInputStream : nsIInputStream
+{
+ /**
+ * SetData - assign an ArrayBuffer to the input stream.
+ *
+ * @param buffer - stream data
+ * @param byteOffset - stream data offset
+ * @param byteLen - stream data length
+ */
+ [implicit_jscontext]
+ void setData(in jsval buffer, in unsigned long byteOffset, in unsigned long byteLen);
+};
diff --git a/netwerk/base/nsIAsyncStreamCopier.idl b/netwerk/base/nsIAsyncStreamCopier.idl
new file mode 100644
index 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..484d59a8c4
--- /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..8a446cb219
--- /dev/null
+++ b/netwerk/base/nsIAuthModule.idl
@@ -0,0 +1,145 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+[uuid(6e35dbc0-49ef-4e2c-b1ea-b72ec64450a2)]
+interface nsIAuthModule : nsISupports
+{
+ /**
+ * Default behavior.
+ */
+ const unsigned long REQ_DEFAULT = 0;
+
+ /**
+ * Client and server will be authenticated.
+ */
+ const unsigned long REQ_MUTUAL_AUTH = (1 << 0);
+
+ /**
+ * The server is allowed to impersonate the client. The REQ_MUTUAL_AUTH
+ * flag may also need to be specified in order for this flag to take
+ * effect.
+ */
+ const unsigned long REQ_DELEGATE = (1 << 1);
+
+ /**
+ * The authentication is required for a proxy connection.
+ */
+ const unsigned long REQ_PROXY_AUTH = (1 << 2);
+
+ /**
+ * Flags used for telemetry.
+ */
+ const unsigned long NTLM_MODULE_SAMBA_AUTH_PROXY = 0;
+ const unsigned long NTLM_MODULE_SAMBA_AUTH_DIRECT = 1;
+ const unsigned long NTLM_MODULE_WIN_API_PROXY = 2;
+ const unsigned long NTLM_MODULE_WIN_API_DIRECT = 3;
+ const unsigned long NTLM_MODULE_GENERIC_PROXY = 4;
+ const unsigned long NTLM_MODULE_GENERIC_DIRECT = 5;
+ const unsigned long NTLM_MODULE_KERBEROS_PROXY = 6;
+ const unsigned long NTLM_MODULE_KERBEROS_DIRECT = 7;
+
+ /** Other flags may be defined in the future */
+
+ /**
+ * Called to initialize an auth module. The other methods cannot be called
+ * unless this method succeeds.
+ *
+ * @param aServiceName
+ * the service name, which may be null if not applicable (e.g., for
+ * NTLM, this parameter should be null).
+ * @param aServiceFlags
+ * a bitwise-or of the REQ_ flags defined above (pass REQ_DEFAULT
+ * for default behavior).
+ * @param aDomain
+ * the authentication domain, which may be null if not applicable.
+ * @param aUsername
+ * the user's login name
+ * @param aPassword
+ * the user's password
+ */
+ void init(in string aServiceName,
+ in unsigned long aServiceFlags,
+ in wstring aDomain,
+ in wstring aUsername,
+ in wstring aPassword);
+
+ /**
+ * Called to get the next token in a sequence of authentication steps.
+ *
+ * @param aInToken
+ * A buffer containing the input token (e.g., a challenge from a
+ * server). This may be null.
+ * @param aInTokenLength
+ * The length of the input token.
+ * @param aOutToken
+ * If getNextToken succeeds, then aOutToken will point to a buffer
+ * to be sent in response to the server challenge. The length of
+ * this buffer is given by aOutTokenLength. The buffer at aOutToken
+ * must be recycled with a call to free.
+ * @param aOutTokenLength
+ * If getNextToken succeeds, then aOutTokenLength contains the
+ * length of the buffer (number of bytes) pointed to by aOutToken.
+ */
+ void getNextToken([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+ /**
+ * Once a security context has been established through calls to GetNextToken()
+ * it may be used to protect data exchanged between client and server. Calls
+ * to Wrap() are used to protect items of data to be sent to the server.
+ *
+ * @param aInToken
+ * A buffer containing the data to be sent to the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param confidential
+ * If set to true, Wrap() will encrypt the data, otherwise data will
+ * just be integrity protected (checksummed)
+ * @param aOutToken
+ * A buffer containing the resulting data to be sent to the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Wrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying authentication
+ * mechanism does not support security layers.
+ */
+ void wrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ in boolean confidential,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+
+ /**
+ * Unwrap() is used to unpack, decrypt, and verify the checksums on data
+ * returned by a server when security layers are in use.
+ *
+ * @param aInToken
+ * A buffer containing the data received from the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param aOutToken
+ * A buffer containing the plaintext data from the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Unwrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying
+ * authentication mechanism does not support security layers.
+ */
+ void unwrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+};
+
+%{C++
+/**
+ * nsIAuthModule implementations are registered under the following contract
+ * ID prefix:
+ */
+#define NS_AUTH_MODULE_CONTRACTID_PREFIX \
+ "@mozilla.org/network/auth-module;1?name="
+%}
diff --git a/netwerk/base/nsIAuthPrompt.idl b/netwerk/base/nsIAuthPrompt.idl
new file mode 100644
index 0000000000..bb9f88f60f
--- /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..23c27c3d1c
--- /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..df560d02f9
--- /dev/null
+++ b/netwerk/base/nsIBackgroundFileSaver.idl
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIBackgroundFileSaverObserver;
+interface nsIFile;
+
+/**
+ * Allows saving data to a file, while handling all the input/output on a
+ * background thread, including the initial file name assignment and any
+ * subsequent renaming of the target file.
+ *
+ * This interface is designed for file downloads. Generally, they start in the
+ * temporary directory, while the user is selecting the final name. Then, they
+ * are moved to the chosen target directory with a ".part" extension appended to
+ * the file name. Finally, they are renamed when the download is completed.
+ *
+ * Components implementing both nsIBackgroundFileSaver and nsIStreamListener
+ * allow data to be fed using an implementation of OnDataAvailable that never
+ * blocks the calling thread. They suspend the request that drives the stream
+ * listener in case too much data is being fed, and resume it when the data has
+ * been written. Calling OnStopRequest does not necessarily close the target
+ * file, and the Finish method must be called to complete the operation.
+ *
+ * Components implementing both nsIBackgroundFileSaver and nsIAsyncOutputStream
+ * allow data to be fed directly to the non-blocking output stream, that however
+ * may return NS_BASE_STREAM_WOULD_BLOCK in case too much data is being fed.
+ * Closing the output stream does not necessarily close the target file, and the
+ * Finish method must be called to complete the operation.
+ *
+ * @remarks Implementations may require the consumer to always call Finish. If
+ * the object reference is released without calling Finish, a memory
+ * leak may occur, and the target file might be kept locked. All
+ * public methods of the interface may only be called from the main
+ * thread.
+ */
+[scriptable, uuid(c43544a4-682c-4262-b407-2453d26e660d)]
+interface nsIBackgroundFileSaver : nsISupports
+{
+ /**
+ * This observer receives notifications when the target file name changes and
+ * when the operation completes, successfully or not.
+ *
+ * @remarks A strong reference to the observer is held. Notification events
+ * are dispatched to the thread that created the object that
+ * implements nsIBackgroundFileSaver.
+ */
+ attribute nsIBackgroundFileSaverObserver observer;
+
+ /**
+ * An nsIArray of nsIX509CertList, representing a chain of X.509 signatures on
+ * the downloaded file. Each list may belong to a different signer and contain
+ * certificates all the way up to the root.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In case this is called before the onSaveComplete method has been
+ * called to notify success, or enableSignatureInfo has not been
+ * called.
+ */
+ readonly attribute nsIArray signatureInfo;
+
+ /**
+ * The SHA-256 hash, in raw bytes, associated with the data that was saved.
+ *
+ * In case the enableAppend method has been called, the hash computation
+ * includes the contents of the existing file, if any.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In case the enableSha256 method has not been called, or before the
+ * onSaveComplete method has been called to notify success.
+ */
+ readonly attribute ACString sha256Hash;
+
+ /**
+ * Instructs the component to compute the signatureInfo of the target file,
+ * and make it available in the signatureInfo property.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableSignatureInfo();
+
+ /**
+ * Instructs the component to compute the SHA-256 hash of the target file, and
+ * make it available in the sha256Hash property.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableSha256();
+
+ /**
+ * Instructs the component to append data to the initial target file, that
+ * will be specified by the first call to the setTarget method, instead of
+ * overwriting the file.
+ *
+ * If the initial target file does not exist, this method has no effect.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableAppend();
+
+ /**
+ * Sets the name of the output file to be written. The target can be changed
+ * after data has already been fed, in which case the existing file will be
+ * moved to the new destination.
+ *
+ * In case the specified file already exists, and this method is called for
+ * the first time, the file may be either overwritten or appended to, based on
+ * whether the enableAppend method was called. Subsequent calls always
+ * overwrite the specified target file with the previously saved data.
+ *
+ * No file will be written until this function is called at least once. It's
+ * recommended not to feed any data until the output file is set.
+ *
+ * If an input/output error occurs with the specified file, the save operation
+ * fails. Failure is notified asynchronously through the observer.
+ *
+ * @param aTarget
+ * New output file to be written.
+ * @param aKeepPartial
+ * Indicates whether aFile should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled. This is
+ * generally set for downloads that use temporary ".part" files.
+ */
+ void setTarget(in nsIFile aTarget, in bool aKeepPartial);
+
+ /**
+ * Terminates access to the output file, then notifies the observer with the
+ * specified status code. A failure code will force the operation to be
+ * canceled, in which case the output file will be deleted if requested.
+ *
+ * This forces the involved streams to be closed, thus no more data should be
+ * fed to the component after this method has been called.
+ *
+ * This is the last method that should be called on this object, and the
+ * target file name cannot be changed anymore after this method has been
+ * called. Conversely, before calling this method, the file can still be
+ * renamed even if all the data has been fed.
+ *
+ * @param aStatus
+ * Result code that determines whether the operation should succeed or
+ * be canceled, and is notified to the observer. If the operation
+ * fails meanwhile for other reasons, or the observer has been already
+ * notified of completion, this status code is ignored.
+ */
+ void finish(in nsresult aStatus);
+};
+
+[scriptable, uuid(ee7058c3-6e54-4411-b76b-3ce87b76fcb6)]
+interface nsIBackgroundFileSaverObserver : nsISupports
+{
+ /**
+ * Called when the name of the output file has been determined. This function
+ * may be called more than once if the target file is renamed while saving.
+ *
+ * @param aSaver
+ * Reference to the object that raised the notification.
+ * @param aTarget
+ * Name of the file that is being written.
+ */
+ void onTargetChange(in nsIBackgroundFileSaver aSaver, in nsIFile aTarget);
+
+ /**
+ * Called when the operation completed, and the target file has been closed.
+ * If the operation succeeded, the target file is ready to be used, otherwise
+ * it might have been already deleted.
+ *
+ * @param aSaver
+ * Reference to the object that raised the notification.
+ * @param aStatus
+ * Result code that determines whether the operation succeeded or
+ * failed, as well as the failure reason.
+ */
+ void onSaveComplete(in nsIBackgroundFileSaver aSaver, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIBrowserSearchService.idl b/netwerk/base/nsIBrowserSearchService.idl
new file mode 100644
index 0000000000..045973e0c0
--- /dev/null
+++ b/netwerk/base/nsIBrowserSearchService.idl
@@ -0,0 +1,530 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIInputStream;
+
+[scriptable, uuid(5799251f-5b55-4df7-a9e7-0c27812c469a)]
+interface nsISearchSubmission : nsISupports
+{
+ /**
+ * The POST data associated with a search submission, wrapped in a MIME
+ * input stream. May be null.
+ */
+ readonly attribute nsIInputStream postData;
+
+ /**
+ * The URI to submit a search to.
+ */
+ readonly attribute nsIURI uri;
+};
+
+[scriptable, uuid(620bd920-0491-48c8-99a8-d6047e64802d)]
+interface nsISearchEngine : nsISupports
+{
+ /**
+ * Gets a nsISearchSubmission object that contains information about what to
+ * send to the search engine, including the URI and postData, if applicable.
+ *
+ * @param data
+ * Data to add to the submission object.
+ * i.e. the search terms.
+ *
+ * @param responseType [optional]
+ * The MIME type that we'd like to receive in response
+ * to this submission. If null, will default to "text/html".
+ *
+ * @param purpose [optional]
+ * A string meant to indicate the context of the search request. This
+ * allows the search service to provide a different nsISearchSubmission
+ * depending on e.g. where the search is triggered in the UI.
+ *
+ * @returns A nsISearchSubmission object that contains information about what
+ * to send to the search engine. If no submission can be
+ * obtained for the given responseType, returns null.
+ */
+ nsISearchSubmission getSubmission(in AString data,
+ [optional] in AString responseType,
+ [optional] in AString purpose);
+
+ /**
+ * Adds a parameter to the search engine's submission data. This should only
+ * be called on engines created via addEngineWithDetails.
+ *
+ * @param name
+ * The parameter's name. Must not be null.
+ *
+ * @param value
+ * The value to pass. If value is "{searchTerms}", it will be
+ * substituted with the user-entered data when retrieving the
+ * submission. Must not be null.
+ *
+ * @param responseType
+ * Since an engine can have several different request URLs,
+ * differentiated by response types, this parameter selects
+ * a request to add parameters to. If null, will default
+ * to "text/html"
+ *
+ * @throws NS_ERROR_FAILURE if the search engine is read-only.
+ * @throws NS_ERROR_INVALID_ARG if name or value are null.
+ */
+ void addParam(in AString name, in AString value, in AString responseType);
+
+ /**
+ * Determines whether the engine can return responses in the given
+ * MIME type. Returns true if the engine spec has a URL with the
+ * given responseType, false otherwise.
+ *
+ * @param responseType
+ * The MIME type to check for
+ */
+ boolean supportsResponseType(in AString responseType);
+
+ /**
+ * Returns a string with the URL to an engine's icon matching both width and
+ * height. Returns null if icon with specified dimensions is not found.
+ *
+ * @param width
+ * Width of the requested icon.
+ * @param height
+ * Height of the requested icon.
+ */
+ AString getIconURLBySize(in long width, in long height);
+
+ /**
+ * Gets an array of all available icons. Each entry is an object with
+ * width, height and url properties. width and height are numeric and
+ * represent the icon's dimensions. url is a string with the URL for
+ * the icon.
+ */
+ jsval getIcons();
+
+ /**
+ * Opens a speculative connection to the engine's search URI
+ * (and suggest URI, if different) to reduce request latency
+ *
+ * @param options
+ * An object that must contain the following fields:
+ * {window} the content window for the window performing the search
+ *
+ * @throws NS_ERROR_INVALID_ARG if options is omitted or lacks required
+ * elemeents
+ */
+ void speculativeConnect(in jsval options);
+
+ /**
+ * An optional shortcut alias for the engine.
+ * When non-null, this is a unique identifier.
+ */
+ attribute AString alias;
+
+ /**
+ * A text description describing the engine.
+ */
+ readonly attribute AString description;
+
+ /**
+ * Whether the engine should be hidden from the user.
+ */
+ attribute boolean hidden;
+
+ /**
+ * A nsIURI corresponding to the engine's icon, stored locally. May be null.
+ */
+ readonly attribute nsIURI iconURI;
+
+ /**
+ * The display name of the search engine. This is a unique identifier.
+ */
+ readonly attribute AString name;
+
+ /**
+ * A URL string pointing to the engine's search form.
+ */
+ readonly attribute AString searchForm;
+
+ /**
+ * An optional unique identifier for this search engine within the context of
+ * the distribution, as provided by the distributing entity.
+ */
+ readonly attribute AString identifier;
+
+ /**
+ * Gets a string representing the hostname from which search results for a
+ * given responseType are returned, minus the leading "www." (if present).
+ * This can be specified as an url attribute in the engine description file,
+ * but will default to host from the <Url>'s template otherwise.
+ *
+ * @param responseType [optional]
+ * The MIME type to get resultDomain for. Defaults to "text/html".
+ *
+ * @return the resultDomain for the given responseType.
+ */
+ AString getResultDomain([optional] in AString responseType);
+};
+
+[scriptable, uuid(0dc93e51-a7bf-4a16-862d-4b3469ff6206)]
+interface nsISearchParseSubmissionResult : nsISupports
+{
+ /**
+ * The search engine associated with the URL passed in to
+ * nsISearchEngine::parseSubmissionURL, or null if the URL does not represent
+ * a search submission.
+ */
+ readonly attribute nsISearchEngine engine;
+
+ /**
+ * String containing the sought terms. This can be an empty string in case no
+ * terms were specified or the URL does not represent a search submission.
+ */
+ readonly attribute AString terms;
+
+ /**
+ * The offset of the string |terms| in the URL passed in to
+ * nsISearchEngine::parseSubmissionURL, or -1 if the URL does not represent
+ * a search submission.
+ */
+ readonly attribute long termsOffset;
+
+ /**
+ * The length of the |terms| in the original encoding of the URL passed in to
+ * nsISearchEngine::parseSubmissionURL. If the search term in the original
+ * URL is encoded then this will be bigger than |terms.length|.
+ */
+ readonly attribute long termsLength;
+};
+
+[scriptable, uuid(9fc39136-f08b-46d3-b232-96f4b7b0e235)]
+interface nsISearchInstallCallback : nsISupports
+{
+ const unsigned long ERROR_UNKNOWN_FAILURE = 0x1;
+ const unsigned long ERROR_DUPLICATE_ENGINE = 0x2;
+
+ /**
+ * Called to indicate that the engine addition process succeeded.
+ *
+ * @param engine
+ * The nsISearchEngine object that was added (will not be null).
+ */
+ void onSuccess(in nsISearchEngine engine);
+
+ /**
+ * Called to indicate that the engine addition process failed.
+ *
+ * @param errorCode
+ * One of the ERROR_* values described above indicating the cause of
+ * the failure.
+ */
+ void onError(in unsigned long errorCode);
+};
+
+/**
+ * Callback for asynchronous initialization of nsIBrowserSearchService
+ */
+[scriptable, function, uuid(02256156-16e4-47f1-9979-76ff98ceb590)]
+interface nsIBrowserSearchInitObserver : nsISupports
+{
+ /**
+ * Called once initialization of the browser search service is complete.
+ *
+ * @param aStatus The status of that service.
+ */
+ void onInitComplete(in nsresult aStatus);
+};
+
+[scriptable, uuid(150ef720-bbe2-4169-b9f3-ef7ec0654ced)]
+interface nsIBrowserSearchService : nsISupports
+{
+ /**
+ * Start asynchronous initialization.
+ *
+ * The callback is triggered once initialization is complete, which may be
+ * immediately, if initialization has already been completed by some previous
+ * call to this method. The callback is always invoked asynchronously.
+ *
+ * @param aObserver An optional object observing the end of initialization.
+ */
+ void init([optional] in nsIBrowserSearchInitObserver aObserver);
+
+ /**
+ * Determine whether initialization has been completed.
+ *
+ * Clients of the service can use this attribute to quickly determine whether
+ * initialization is complete, and decide to trigger some immediate treatment,
+ * to launch asynchronous initialization or to bailout.
+ *
+ * Note that this attribute does not indicate that initialization has succeeded.
+ *
+ * @return |true| if the search service is now initialized, |false| if
+ * initialization has not been triggered yet.
+ */
+ readonly attribute bool isInitialized;
+
+ /**
+ * Resets the default engine to its original value.
+ */
+ void resetToOriginalDefaultEngine();
+
+ /**
+ * Checks if an EngineURL of type URLTYPE_SEARCH_HTML exists for
+ * any engine, with a matching method, template URL, and query params.
+ *
+ * @param method
+ * The HTTP request method used when submitting a search query.
+ * Must be a case insensitive value of either "get" or "post".
+ *
+ * @param url
+ * The URL to which search queries should be sent.
+ * Must not be null.
+ *
+ * @param formData
+ * The un-sorted form data used as query params.
+ */
+ boolean hasEngineWithURL(in AString method, in AString url, in jsval formData);
+
+ /**
+ * Adds a new search engine from the file at the supplied URI, optionally
+ * asking the user for confirmation first. If a confirmation dialog is
+ * shown, it will offer the option to begin using the newly added engine
+ * right away.
+ *
+ * @param engineURL
+ * The URL to the search engine's description file.
+ *
+ * @param dataType
+ * Obsolete, the value is ignored.
+ *
+ * @param iconURL
+ * A URL string to an icon file to be used as the search engine's
+ * icon. This value may be overridden by an icon specified in the
+ * engine description file.
+ *
+ * @param confirm
+ * A boolean value indicating whether the user should be asked for
+ * confirmation before this engine is added to the list. If this
+ * value is false, the engine will be added to the list upon successful
+ * load, but it will not be selected as the current engine.
+ *
+ * @param callback
+ * A nsISearchInstallCallback that will be notified when the
+ * addition is complete, or if the addition fails. It will not be
+ * called if addEngine throws an exception.
+ *
+ * @throws NS_ERROR_FAILURE if the description file cannot be successfully
+ * loaded.
+ */
+ void addEngine(in AString engineURL, in long dataType, in AString iconURL,
+ in boolean confirm, [optional] in nsISearchInstallCallback callback);
+
+ /**
+ * Adds a new search engine, without asking the user for confirmation and
+ * without starting to use it right away.
+ *
+ * @param name
+ * The search engine's name. Must be unique. Must not be null.
+ *
+ * @param iconURL
+ * Optional: A URL string pointing to the icon to be used to represent
+ * the engine.
+ *
+ * @param alias
+ * Optional: A unique shortcut that can be used to retrieve the
+ * search engine.
+ *
+ * @param description
+ * Optional: a description of the search engine.
+ *
+ * @param method
+ * The HTTP request method used when submitting a search query.
+ * Must be a case insensitive value of either "get" or "post".
+ *
+ * @param url
+ * The URL to which search queries should be sent.
+ * Must not be null.
+ *
+ * @param extensionID [optional]
+ * Optional: The correct extensionID if called by an add-on.
+ */
+ void addEngineWithDetails(in AString name,
+ in AString iconURL,
+ in AString alias,
+ in AString description,
+ in AString method,
+ in AString url,
+ [optional] in AString extensionID);
+
+ /**
+ * Un-hides all engines installed in the directory corresponding to
+ * the directory service's NS_APP_SEARCH_DIR key. (i.e. the set of
+ * engines returned by getDefaultEngines)
+ */
+ void restoreDefaultEngines();
+
+ /**
+ * Returns an engine with the specified alias.
+ *
+ * @param alias
+ * The search engine's alias.
+ * @returns The corresponding nsISearchEngine object, or null if it doesn't
+ * exist.
+ */
+ nsISearchEngine getEngineByAlias(in AString alias);
+
+ /**
+ * Returns an engine with the specified name.
+ *
+ * @param aEngineName
+ * The name of the engine.
+ * @returns The corresponding nsISearchEngine object, or null if it doesn't
+ * exist.
+ */
+ nsISearchEngine getEngineByName(in AString aEngineName);
+
+ /**
+ * Returns an array of all installed search engines.
+ *
+ * @returns an array of nsISearchEngine objects.
+ */
+ void getEngines(
+ [optional] out unsigned long engineCount,
+ [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+
+ /**
+ * Returns an array of all installed search engines whose hidden attribute is
+ * false.
+ *
+ * @returns an array of nsISearchEngine objects.
+ */
+ void getVisibleEngines(
+ [optional] out unsigned long engineCount,
+ [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+
+ /**
+ * Returns an array of all default search engines. This includes all loaded
+ * engines that aren't in the user's profile directory
+ * (NS_APP_USER_SEARCH_DIR).
+ *
+ * @returns an array of nsISearchEngine objects.
+ */
+ void getDefaultEngines(
+ [optional] out unsigned long engineCount,
+ [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+
+ /**
+ * Moves a visible search engine.
+ *
+ * @param engine
+ * The engine to move.
+ * @param newIndex
+ * The engine's new index in the set of visible engines.
+ *
+ * @throws NS_ERROR_FAILURE if newIndex is out of bounds, or if engine is
+ * hidden.
+ */
+ void moveEngine(in nsISearchEngine engine, in long newIndex);
+
+ /**
+ * Removes the search engine. If the search engine is installed in a global
+ * location, this will just hide the engine. If the engine is in the user's
+ * profile directory, it will be removed from disk.
+ *
+ * @param engine
+ * The engine to remove.
+ */
+ void removeEngine(in nsISearchEngine engine);
+
+ /**
+ * The original Engine object that is the default for this region,
+ * ignoring changes the user may have subsequently made.
+ */
+ readonly attribute nsISearchEngine originalDefaultEngine;
+
+ /**
+ * Alias for the currentEngine attribute, kept for add-on compatibility.
+ */
+ attribute nsISearchEngine defaultEngine;
+
+ /**
+ * The currently active search engine.
+ * Unless the application doesn't ship any search plugin, this should never
+ * be null. If the currently active engine is removed, this attribute will
+ * fallback first to the original default engine if it's not hidden, then to
+ * the first visible engine, and as a last resort it will unhide the original
+ * default engine.
+ */
+ attribute nsISearchEngine currentEngine;
+
+ /**
+ * Gets a representation of the default engine in an anonymized JSON
+ * string suitable for recording in the Telemetry environment.
+ *
+ * @return an object containing anonymized info about the default engine:
+ * name, loadPath, submissionURL (for default engines).
+ */
+ jsval getDefaultEngineInfo();
+
+ /**
+ * Determines if the provided URL represents results from a search engine, and
+ * provides details about the match.
+ *
+ * The lookup mechanism checks whether the domain name and path of the
+ * provided HTTP or HTTPS URL matches one of the known values for the visible
+ * search engines. The match does not depend on which of the schemes is used.
+ * The expected URI parameter for the search terms must exist in the query
+ * string, but other parameters are ignored.
+ *
+ * @param url
+ * String containing the URL to parse, for example
+ * "https://www.google.com/search?q=terms".
+ */
+ nsISearchParseSubmissionResult parseSubmissionURL(in AString url);
+};
+
+%{ C++
+/**
+ * The observer topic to listen to for actions performed on installed
+ * search engines.
+ */
+#define SEARCH_ENGINE_TOPIC "browser-search-engine-modified"
+
+/**
+ * Sent when an engine is removed from the data store.
+ */
+#define SEARCH_ENGINE_REMOVED "engine-removed"
+
+/**
+ * Sent when an engine is changed. This includes when the engine's "hidden"
+ * property is changed.
+ */
+#define SEARCH_ENGINE_CHANGED "engine-changed"
+
+/**
+ * Sent when an engine is added to the list of available engines.
+ */
+#define SEARCH_ENGINE_ADDED "engine-added"
+
+/**
+ * Sent when a search engine being installed from a remote plugin description
+ * file is completely loaded. This is used internally by the search service as
+ * an indication of when the engine can be added to the internal store, and
+ * therefore should not be used to detect engine availability. It is always
+ * followed by an "added" notification.
+ */
+#define SEARCH_ENGINE_LOADED "engine-loaded"
+
+/**
+ * Sent when the "current" engine is changed.
+ */
+#define SEARCH_ENGINE_CURRENT "engine-current";
+
+/**
+ * Sent when the "default" engine is changed.
+ */
+#define SEARCH_ENGINE_DEFAULT "engine-default";
+
+
+
+%}
diff --git a/netwerk/base/nsIBufferedStreams.idl b/netwerk/base/nsIBufferedStreams.idl
new file mode 100644
index 0000000000..60b9768b92
--- /dev/null
+++ b/netwerk/base/nsIBufferedStreams.idl
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+
+/**
+ * An input stream that reads ahead and keeps a buffer coming from another input
+ * stream so that fewer accesses to the underlying stream are necessary.
+ */
+[scriptable, uuid(616f5b48-da09-11d3-8cda-0060b0fc14a3)]
+interface nsIBufferedInputStream : nsIInputStream
+{
+ /**
+ * @param fillFromStream - add buffering to this stream
+ * @param bufferSize - specifies the maximum buffer size
+ */
+ void init(in nsIInputStream fillFromStream,
+ in unsigned long bufferSize);
+};
+
+/**
+ * An output stream that stores up data to write out to another output stream
+ * and does the entire write only when the buffer is full, so that fewer writes
+ * to the underlying output stream are necessary.
+ */
+[scriptable, uuid(6476378a-da09-11d3-8cda-0060b0fc14a3)]
+interface nsIBufferedOutputStream : nsIOutputStream
+{
+ /**
+ * @param sinkToStream - add buffering to this stream
+ * @param bufferSize - specifies the maximum buffer size
+ */
+ void init(in nsIOutputStream sinkToStream,
+ in unsigned long bufferSize);
+};
diff --git a/netwerk/base/nsIByteRangeRequest.idl b/netwerk/base/nsIByteRangeRequest.idl
new file mode 100644
index 0000000000..b558016a51
--- /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..f6d3c7b735
--- /dev/null
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIOutputStream;
+
+[scriptable, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)]
+interface nsICacheInfoChannel : nsISupports
+{
+ /**
+ * Get expiration time from cache token. This attribute is equivalent to
+ * nsICachingChannel.cacheToken.expirationTime.
+ */
+ readonly attribute uint32_t cacheTokenExpirationTime;
+
+ /**
+ * Set/get charset of cache entry. Accessing this attribute is equivalent to
+ * calling nsICachingChannel.cacheToken.getMetaDataElement("charset") and
+ * nsICachingChannel.cacheToken.setMetaDataElement("charset").
+ */
+ attribute ACString cacheTokenCachedCharset;
+
+ /**
+ * TRUE if this channel's data is being loaded from the cache. This value
+ * is undefined before the channel fires its OnStartRequest notification
+ * and after the channel fires its OnStopRequest notification.
+ */
+ boolean isFromCache();
+
+ /**
+ * Set/get the cache key... uniquely identifies the data in the cache
+ * for this channel. Holding a reference to this key does NOT prevent
+ * the cached data from being removed.
+ *
+ * A cache key retrieved from a particular instance of nsICacheInfoChannel
+ * could be set on another instance of nsICacheInfoChannel provided the
+ * underlying implementations are compatible and provided the new
+ * channel instance was created with the same URI. The implementation of
+ * nsICacheInfoChannel would be expected to use the cache entry identified
+ * by the cache token. Depending on the value of nsIRequest::loadFlags,
+ * the cache entry may be validated, overwritten, or simply read.
+ *
+ * The cache key may be NULL indicating that the URI of the channel is
+ * sufficient to locate the same cache entry. Setting a NULL cache key
+ * is likewise valid.
+ */
+ attribute nsISupports cacheKey;
+
+ /**
+ * Tells the channel to behave as if the LOAD_FROM_CACHE flag has been set,
+ * but without affecting the loads for the entire loadGroup in case of this
+ * channel being the default load group's channel.
+ */
+ attribute boolean allowStaleCacheContent;
+
+ /**
+ * Calling this method instructs the channel to serve the alternative data
+ * if that was previously saved in the cache, otherwise it will serve the
+ * real data.
+ * Must be called before AsyncOpen.
+ */
+ void preferAlternativeDataType(in ACString type);
+
+ /**
+ * Holds the type of the alternative data representation that the channel
+ * is returning.
+ * Is empty string if no alternative data representation was requested, or
+ * if the requested representation wasn't found in the cache.
+ * Can only be called during or after OnStartRequest.
+ */
+ readonly attribute ACString alternativeDataType;
+
+ /**
+ * Opens and returns an output stream that a consumer may use to save an
+ * alternate representation of the data.
+ * Must be called after the OnStopRequest that delivered the real data.
+ * The consumer may choose to replace the saved alt representation.
+ * Opening the output stream will fail if there are any open input streams
+ * reading the already saved alt representation.
+ */
+ nsIOutputStream openAlternativeOutputStream(in ACString type);
+};
diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl
new file mode 100644
index 0000000000..63f65b1a44
--- /dev/null
+++ b/netwerk/base/nsICachingChannel.idl
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICacheInfoChannel.idl"
+
+interface nsIFile;
+
+/**
+ * A channel may optionally implement this interface to allow clients
+ * to affect its behavior with respect to how it uses the cache service.
+ *
+ * This interface provides:
+ * 1) Support for "stream as file" semantics (for JAR and plugins).
+ * 2) Support for "pinning" cached data in the cache (for printing and save-as).
+ * 3) Support for uniquely identifying cached data in cases when the URL
+ * is insufficient (e.g., HTTP form submission).
+ */
+[scriptable, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)]
+interface nsICachingChannel : nsICacheInfoChannel
+{
+ /**
+ * Set/get the cache token... uniquely identifies the data in the cache.
+ * Holding a reference to this token prevents the cached data from being
+ * removed.
+ *
+ * A cache token retrieved from a particular instance of nsICachingChannel
+ * could be set on another instance of nsICachingChannel provided the
+ * underlying implementations are compatible. The implementation of
+ * nsICachingChannel would be expected to only read from the cache entry
+ * identified by the cache token and not try to validate it.
+ *
+ * The cache token can be QI'd to a nsICacheEntryInfo if more detail
+ * about the cache entry is needed (e.g., expiration time).
+ */
+ attribute nsISupports cacheToken;
+
+ /**
+ * The same as above but accessing the offline app cache token if there
+ * is any.
+ *
+ * @throws
+ * NS_ERROR_NOT_AVAILABLE when there is not offline cache token
+ */
+ attribute nsISupports offlineCacheToken;
+
+ /**
+ * Instructs the channel to only store the metadata of the entry, and not
+ * the content. When reading an existing entry, this automatically sets
+ * LOAD_ONLY_IF_MODIFIED flag.
+ * Must be called before asyncOpen().
+ */
+ attribute boolean cacheOnlyMetadata;
+
+ /**
+ * Tells the channel to use the pinning storage.
+ */
+ attribute boolean pin;
+
+ /**
+ * Overrides cache validation for a time specified in seconds.
+ *
+ * @param aSecondsToTheFuture
+ *
+ */
+ void forceCacheEntryValidFor(in unsigned long aSecondsToTheFuture);
+
+ /**************************************************************************
+ * Caching channel specific load flags:
+ */
+
+ /**
+ * This load flag inhibits fetching from the net. An error of
+ * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's
+ * onStopRequest if network IO is necessary to complete the request.
+ *
+ * This flag can be used to find out whether fetching this URL would
+ * cause validation of the cache entry via the network.
+ *
+ * Combining this flag with LOAD_BYPASS_LOCAL_CACHE will cause all
+ * loads to fail. This flag differs from LOAD_ONLY_FROM_CACHE in that
+ * this flag fails the load if validation is required while
+ * LOAD_ONLY_FROM_CACHE skips validation where possible.
+ */
+ const unsigned long LOAD_NO_NETWORK_IO = 1 << 26;
+
+ /**
+ * This load flag causes the offline cache to be checked when fetching
+ * a request. It will be set automatically if the browser is offline.
+ *
+ * This flag will not be transferred through a redirect.
+ */
+ const unsigned long LOAD_CHECK_OFFLINE_CACHE = 1 << 27;
+
+ /**
+ * This load flag causes the local cache to be skipped when fetching a
+ * request. Unlike LOAD_BYPASS_CACHE, it does not force an end-to-end load
+ * (i.e., it does not affect proxy caches).
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE = 1 << 28;
+
+ /**
+ * This load flag causes the local cache to be skipped if the request
+ * would otherwise block waiting to access the cache.
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = 1 << 29;
+
+ /**
+ * This load flag inhibits fetching from the net if the data in the cache
+ * has been evicted. An error of NS_ERROR_DOCUMENT_NOT_CACHED will be sent
+ * to the listener's onStopRequest in this case. This flag is set
+ * automatically when the application is offline.
+ */
+ const unsigned long LOAD_ONLY_FROM_CACHE = 1 << 30;
+
+ /**
+ * This load flag controls what happens when a document would be loaded
+ * from the cache to satisfy a call to AsyncOpen. If this attribute is
+ * set to TRUE, then the document will not be loaded from the cache. A
+ * stream listener can check nsICachingChannel::isFromCache to determine
+ * if the AsyncOpen will actually result in data being streamed.
+ *
+ * If this flag has been set, and the request can be satisfied via the
+ * cache, then the OnDataAvailable events will be skipped. The listener
+ * will only see OnStartRequest followed by OnStopRequest.
+ */
+ const unsigned long LOAD_ONLY_IF_MODIFIED = 1 << 31;
+};
diff --git a/netwerk/base/nsICancelable.idl b/netwerk/base/nsICancelable.idl
new file mode 100644
index 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..94d9d6e9a8
--- /dev/null
+++ b/netwerk/base/nsICaptivePortalService.idl
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)]
+interface nsICaptivePortalServiceCallback : nsISupports
+{
+ /**
+ * Invoke callbacks after captive portal detection finished.
+ */
+ void complete(in bool success, in nsresult error);
+};
+
+/**
+ * Service used for captive portal detection.
+ * The service is only active in the main process. It is also available in the
+ * content process, but only to mirror the captive portal state from the main
+ * process.
+ */
+[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)]
+interface nsICaptivePortalService : nsISupports
+{
+ const long UNKNOWN = 0;
+ const long NOT_CAPTIVE = 1;
+ const long UNLOCKED_PORTAL = 2;
+ const long LOCKED_PORTAL = 3;
+
+ /**
+ * Called from XPCOM to trigger a captive portal recheck.
+ * A network request will only be performed if no other checks are currently
+ * ongoing.
+ * Will not do anything if called in the content process.
+ */
+ void recheckCaptivePortal();
+
+ /**
+ * Returns the state of the captive portal.
+ */
+ readonly attribute long state;
+
+ /**
+ * Returns the time difference between NOW and the last time a request was
+ * completed in milliseconds.
+ */
+ readonly attribute unsigned long long lastChecked;
+};
+
+%{C++
+/**
+ * This observer notification will be emitted when the captive portal state
+ * changes. After receiving it, the ContentParent will send an IPC message
+ * to the ContentChild, which will set the state in the captive portal service
+ * in the child.
+ */
+#define NS_IPC_CAPTIVE_PORTAL_SET_STATE "ipc:network:captive-portal-set-state"
+
+%}
diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl
new file mode 100644
index 0000000000..743e942920
--- /dev/null
+++ b/netwerk/base/nsIChannel.idl
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+#include "nsILoadInfo.idl"
+
+interface nsIURI;
+interface nsIInterfaceRequestor;
+interface nsIInputStream;
+interface nsIStreamListener;
+
+%{C++
+#include "nsCOMPtr.h"
+%}
+
+/**
+ * The nsIChannel interface allows clients to construct "GET" requests for
+ * specific protocols, and manage them in a uniform way. Once a channel is
+ * created (via nsIIOService::newChannel), parameters for that request may
+ * be set by using the channel attributes, or by QI'ing to a subclass of
+ * nsIChannel for protocol-specific parameters. Then, the URI can be fetched
+ * by calling nsIChannel::open or nsIChannel::asyncOpen.
+ *
+ * After a request has been completed, the channel is still valid for accessing
+ * protocol-specific results. For example, QI'ing to nsIHttpChannel allows
+ * response headers to be retrieved for the corresponding http transaction.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(2c389865-23db-4aa7-9fe5-60cc7b00697e)]
+interface nsIChannel : nsIRequest
+{
+ /**
+ * The original URI used to construct the channel. This is used in
+ * the case of a redirect or URI "resolution" (e.g. resolving a
+ * resource: URI to a file: URI) so that the original pre-redirect
+ * URI can still be obtained. This is never null. Attempts to
+ * set it to null must throw.
+ *
+ * NOTE: this is distinctly different from the http Referer (referring URI),
+ * which is typically the page that contained the original URI (accessible
+ * from nsIHttpChannel).
+ */
+ attribute nsIURI originalURI;
+
+ /**
+ * The URI corresponding to the channel. Its value is immutable.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The owner, corresponding to the entity that is responsible for this
+ * channel. Used by the security manager to grant or deny privileges to
+ * mobile code loaded from this channel.
+ *
+ * NOTE: this is a strong reference to the owner, so if the owner is also
+ * holding a strong reference to the channel, care must be taken to
+ * explicitly drop its reference to the channel.
+ */
+ attribute nsISupports owner;
+
+ /**
+ * The notification callbacks for the channel. This is set by clients, who
+ * wish to provide a means to receive progress, status and protocol-specific
+ * notifications. If this value is NULL, the channel implementation may use
+ * the notification callbacks from its load group. The channel may also
+ * query the notification callbacks from its load group if its notification
+ * callbacks do not supply the requested interface.
+ *
+ * Interfaces commonly requested include: nsIProgressEventSink, nsIPrompt,
+ * and nsIAuthPrompt/nsIAuthPrompt2.
+ *
+ * When the channel is done, it must not continue holding references to
+ * this object.
+ *
+ * NOTE: A channel implementation should take care when "caching" an
+ * interface pointer queried from its notification callbacks. If the
+ * notification callbacks are changed, then a cached interface pointer may
+ * become invalid and may therefore need to be re-queried.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any) corresponding to the
+ * channel.
+ *
+ * NOTE: In some circumstances TLS information is propagated onto
+ * non-nsIHttpChannel objects to indicate that their contents were likely
+ * delivered over TLS all the same. For example, document.open() may
+ * create an nsWyciwygChannel to store the data that will be written to the
+ * document. In that case, if the caller has TLS information, we propagate
+ * that info onto the nsWyciwygChannel given that it is likely that the
+ * caller will be writing data that was delivered over TLS to the document.
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * The MIME type of the channel's content if available.
+ *
+ * NOTE: the content type can often be wrongly specified (e.g., wrong file
+ * extension, wrong MIME type, wrong document type stored on a server, etc.),
+ * and the caller most likely wants to verify with the actual data.
+ *
+ * Setting contentType before the channel has been opened provides a hint
+ * to the channel as to what the MIME type is. The channel may ignore this
+ * hint in deciding on the actual MIME type that it will report.
+ *
+ * Setting contentType after onStartRequest has been fired or after open()
+ * is called will override the type determined by the channel.
+ *
+ * Setting contentType between the time that asyncOpen() is called and the
+ * time when onStartRequest is fired has undefined behavior at this time.
+ *
+ * The value of the contentType attribute is a lowercase string. A value
+ * assigned to this attribute will be parsed and normalized as follows:
+ * 1- any parameters (delimited with a ';') will be stripped.
+ * 2- if a charset parameter is given, then its value will replace the
+ * the contentCharset attribute of the channel.
+ * 3- the stripped contentType will be lowercased.
+ * Any implementation of nsIChannel must follow these rules.
+ */
+ attribute ACString contentType;
+
+ /**
+ * The character set of the channel's content if available and if applicable.
+ * This attribute only applies to textual data.
+ *
+ * The value of the contentCharset attribute is a mixedcase string.
+ */
+ attribute ACString contentCharset;
+
+ /**
+ * The length of the data associated with the channel if available. A value
+ * of -1 indicates that the content length is unknown. Note that this is a
+ * 64-bit value and obsoletes the "content-length" property used on some
+ * channels.
+ */
+ attribute int64_t contentLength;
+
+ /**
+ * Synchronously open the channel.
+ *
+ * @return blocking input stream to the channel's data.
+ *
+ * NOTE: nsIChannel implementations are not required to implement this
+ * method. Moreover, since this method may block the calling thread, it
+ * should not be called on a thread that processes UI events. Like any
+ * other nsIChannel method it must not be called on any thread other
+ * than the XPCOM main thread.
+ *
+ * NOTE: Implementations should throw NS_ERROR_IN_PROGRESS if the channel
+ * is reopened.
+ */
+ nsIInputStream open();
+
+ /**
+ * Performs content security check and calls open()
+ */
+ nsIInputStream open2();
+
+ /**
+ * Asynchronously open this channel. Data is fed to the specified stream
+ * listener as it becomes available. The stream listener's methods are
+ * called on the thread that calls asyncOpen and are not called until
+ * after asyncOpen returns. If asyncOpen returns successfully, the
+ * channel promises to call at least onStartRequest and onStopRequest.
+ *
+ * If the nsIRequest object passed to the stream listener's methods is not
+ * this channel, an appropriate onChannelRedirect notification needs to be
+ * sent to the notification callbacks before onStartRequest is called.
+ * Once onStartRequest is called, all following method calls on aListener
+ * will get the request that was passed to onStartRequest.
+ *
+ * If the channel's and loadgroup's notification callbacks do not provide
+ * an nsIChannelEventSink when onChannelRedirect would be called, that's
+ * equivalent to having called onChannelRedirect.
+ *
+ * If asyncOpen returns successfully, the channel is responsible for
+ * keeping itself alive until it has called onStopRequest on aListener or
+ * called onChannelRedirect.
+ *
+ * Implementations are allowed to synchronously add themselves to the
+ * associated load group (if any).
+ *
+ * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the
+ * channel is reopened.
+ *
+ * @param aListener the nsIStreamListener implementation
+ * @param aContext an opaque parameter forwarded to aListener's methods
+ * @see nsIChannelEventSink for onChannelRedirect
+ */
+ void asyncOpen(in nsIStreamListener aListener, in nsISupports aContext);
+
+ /**
+ * Performs content security check and calls asyncOpen().
+ */
+ void asyncOpen2(in nsIStreamListener aListener);
+
+ /**************************************************************************
+ * Channel specific load flags:
+ *
+ * Bits 26-31 are reserved for future use by this interface or one of its
+ * derivatives (e.g., see nsICachingChannel).
+ */
+
+ /**
+ * Set (e.g., by the docshell) to indicate whether or not the channel
+ * corresponds to a document URI.
+ */
+ const unsigned long LOAD_DOCUMENT_URI = 1 << 16;
+
+ /**
+ * If the end consumer for this load has been retargeted after discovering
+ * its content, this flag will be set:
+ */
+ const unsigned long LOAD_RETARGETED_DOCUMENT_URI = 1 << 17;
+
+ /**
+ * This flag is set to indicate that this channel is replacing another
+ * channel. This means that:
+ *
+ * 1) the stream listener this channel will be notifying was initially
+ * passed to the asyncOpen method of some other channel
+ *
+ * and
+ *
+ * 2) this channel's URI is a better identifier of the resource being
+ * accessed than this channel's originalURI.
+ *
+ * This flag can be set, for example, for redirects or for cases when a
+ * single channel has multiple parts to it (and thus can follow
+ * onStopRequest with another onStartRequest/onStopRequest pair, each pair
+ * for a different request).
+ */
+ const unsigned long LOAD_REPLACE = 1 << 18;
+
+ /**
+ * Set (e.g., by the docshell) to indicate whether or not the channel
+ * corresponds to an initial document URI load (e.g., link click).
+ */
+ const unsigned long LOAD_INITIAL_DOCUMENT_URI = 1 << 19;
+
+ /**
+ * Set (e.g., by the URILoader) to indicate whether or not the end consumer
+ * for this load has been determined.
+ */
+ const unsigned long LOAD_TARGETED = 1 << 20;
+
+ /**
+ * If this flag is set, the channel should call the content sniffers as
+ * described in nsNetCID.h about NS_CONTENT_SNIFFER_CATEGORY.
+ *
+ * Note: Channels may ignore this flag; however, new channel implementations
+ * should only do so with good reason.
+ */
+ const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21;
+
+ /**
+ * This flag tells the channel to use URI classifier service to check
+ * the URI when opening the channel.
+ */
+ const unsigned long LOAD_CLASSIFY_URI = 1 << 22;
+
+ /**
+ * If this flag is set, the media-type content sniffer will be allowed
+ * to override any server-set content-type. Otherwise it will only
+ * be allowed to override "no content type" and application/octet-stream.
+ */
+ const unsigned long LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE = 1 << 23;
+
+ /**
+ * Set to let explicitely provided credentials be used over credentials
+ * we have cached previously. In some situations like form login using HTTP
+ * auth via XMLHttpRequest we need to let consumers override the cached
+ * credentials explicitely. For form login 403 response instead of 401 is
+ * usually used to prevent an auth dialog. But any code other then 401/7
+ * will leave original credentials in the cache and there is then no way
+ * to override them for the same user name.
+ */
+ const unsigned long LOAD_EXPLICIT_CREDENTIALS = 1 << 24;
+
+ /**
+ * Set to force bypass of any service worker interception of the channel.
+ */
+ const unsigned long LOAD_BYPASS_SERVICE_WORKER = 1 << 25;
+
+ // nsICachingChannel load flags begin at bit 26.
+
+ /**
+ * Access to the type implied or stated by the Content-Disposition header
+ * if available and if applicable. This allows determining inline versus
+ * attachment.
+ *
+ * Setting contentDisposition provides a hint to the channel about the
+ * disposition. If a normal Content-Disposition header is present its
+ * value will always be used. If it is missing the hinted value will
+ * be used if set.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+ * doesn't exist for this type of channel or is empty, and return
+ * DISPOSITION_ATTACHMENT if an invalid/noncompliant value is present.
+ */
+ attribute unsigned long contentDisposition;
+ const unsigned long DISPOSITION_INLINE = 0;
+ const unsigned long DISPOSITION_ATTACHMENT = 1;
+
+ /**
+ * Access to the filename portion of the Content-Disposition header if
+ * available and if applicable. This allows getting the preferred filename
+ * without having to parse it out yourself.
+ *
+ * Setting contentDispositionFilename provides a hint to the channel about
+ * the disposition. If a normal Content-Disposition header is present its
+ * value will always be used. If it is missing the hinted value will be
+ * used if set.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header doesn't
+ * exist for this type of channel, if the header is empty, if the header
+ * doesn't contain a filename portion, or the value of the filename
+ * attribute is empty/missing.
+ */
+ attribute AString contentDispositionFilename;
+
+ /**
+ * Access to the raw Content-Disposition header if available and applicable.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+ * doesn't exist for this type of channel or is empty.
+ *
+ * @deprecated Use contentDisposition/contentDispositionFilename instead.
+ */
+ readonly attribute ACString contentDispositionHeader;
+
+ /**
+ * The LoadInfo object contains information about a network load, why it
+ * was started, and how we plan on using the resulting response.
+ * If a network request is redirected, the new channel will receive a new
+ * LoadInfo object. The new object will contain mostly the same
+ * information as the pre-redirect one, but updated as appropriate.
+ * For detailed information about what parts of LoadInfo are updated on
+ * redirect, see documentation on individual properties.
+ */
+ attribute nsILoadInfo loadInfo;
+
+%{ C++
+ inline already_AddRefed<nsILoadInfo> GetLoadInfo()
+ {
+ nsCOMPtr<nsILoadInfo> result;
+ mozilla::DebugOnly<nsresult> rv = GetLoadInfo(getter_AddRefs(result));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || !result);
+ return result.forget();
+ }
+%}
+
+};
diff --git a/netwerk/base/nsIChannelEventSink.idl b/netwerk/base/nsIChannelEventSink.idl
new file mode 100644
index 0000000000..64c5b9eec3
--- /dev/null
+++ b/netwerk/base/nsIChannelEventSink.idl
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+/**
+ * Implement this interface to receive control over various channel events.
+ * Channels will try to get this interface from a channel's
+ * notificationCallbacks or, if not available there, from the loadGroup's
+ * notificationCallbacks.
+ *
+ * These methods are called before onStartRequest.
+ */
+[scriptable, uuid(0197720d-37ed-4e75-8956-d0d296e4d8a6)]
+interface nsIChannelEventSink : nsISupports
+{
+ /**
+ * This is a temporary redirect. New requests for this resource should
+ * continue to use the URI of the old channel.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_TEMPORARY = 1 << 0;
+
+ /**
+ * This is a permanent redirect. New requests for this resource should use
+ * the URI of the new channel (This might be an HTTP 301 reponse).
+ * If this flag is not set, this is a temporary redirect.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_PERMANENT = 1 << 1;
+
+ /**
+ * This is an internal redirect, i.e. it was not initiated by the remote
+ * server, but is specific to the channel implementation.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_INTERNAL = 1 << 2;
+
+ /**
+ * This is a special-cased redirect coming from hitting HSTS upgrade
+ * redirect from http to https only. In some cases this type of redirect
+ * may be considered as safe despite not being the-same-origin redirect.
+ */
+ const unsigned long REDIRECT_STS_UPGRADE = 1 << 3;
+
+ /**
+ * Called when a redirect occurs. This may happen due to an HTTP 3xx status
+ * code. The purpose of this method is to notify the sink that a redirect
+ * is about to happen, but also to give the sink the right to veto the
+ * redirect by throwing or passing a failure-code in the callback.
+ *
+ * Note that vetoing the redirect simply means that |newChannel| will not
+ * be opened. It is important to understand that |oldChannel| will continue
+ * loading as if it received a HTTP 200, which includes notifying observers
+ * and possibly display or process content attached to the HTTP response.
+ * If the sink wants to prevent this loading it must explicitly deal with
+ * it, e.g. by calling |oldChannel->Cancel()|
+ *
+ * There is a certain freedom in implementing this method:
+ *
+ * If the return-value indicates success, a callback on |callback| is
+ * required. This callback can be done from within asyncOnChannelRedirect
+ * (effectively making the call synchronous) or at some point later
+ * (making the call asynchronous). Repeat: A callback must be done
+ * if this method returns successfully.
+ *
+ * If the return value indicates error (method throws an exception)
+ * the redirect is vetoed and no callback must be done. Repeat: No
+ * callback must be done if this method throws!
+ *
+ * @see nsIAsyncVerifyRedirectCallback::onRedirectVerifyCallback()
+ *
+ * @param oldChannel
+ * The channel that's being redirected.
+ * @param newChannel
+ * The new channel. This channel is not opened yet.
+ * @param flags
+ * Flags indicating the type of redirect. A bitmask consisting
+ * of flags from above.
+ * One of REDIRECT_TEMPORARY and REDIRECT_PERMANENT will always be
+ * set.
+ * @param callback
+ * Object to inform about the async result of this method
+ *
+ * @throw <any> Throwing an exception will cause the redirect to be
+ * cancelled
+ */
+ void asyncOnChannelRedirect(in nsIChannel oldChannel,
+ in nsIChannel newChannel,
+ in unsigned long flags,
+ in nsIAsyncVerifyRedirectCallback callback);
+};
diff --git a/netwerk/base/nsIChannelWithDivertableParentListener.idl b/netwerk/base/nsIChannelWithDivertableParentListener.idl
new file mode 100644
index 0000000000..1fb52931fd
--- /dev/null
+++ b/netwerk/base/nsIChannelWithDivertableParentListener.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class ADivertableParentChannel;
+}
+}
+%}
+
+[ptr] native ADivertableParentChannelPtr(mozilla::net::ADivertableParentChannel);
+
+/** When we are diverting messages from the child to the parent. The
+ * nsHttpChannel and nsFtpChannel must know that there is a ChannelParent to
+ * be able to suspend message delivery if the channel is suspended.
+ */
+[uuid(c073d79f-2503-4dff-ba87-d3071f8b433b)]
+interface nsIChannelWithDivertableParentListener : nsISupports
+{
+ /**
+ * Informs nsHttpChannel or nsFtpChannel that a ParentChannel starts
+ * diverting messages. During this time all suspend/resume calls to the
+ * channel must also suspend the ParentChannel by calling
+ * SuspendMessageDiversion/ResumeMessageDiversion.
+ */
+ void MessageDiversionStarted(in ADivertableParentChannelPtr aParentChannel);
+
+ /**
+ * The message diversion has finished the calls to
+ * SuspendMessageDiversion/ResumeMessageDiversion are not necessary anymore.
+ */
+ void MessageDiversionStop();
+
+ /**
+ * Internal versions of Suspend/Resume that suspend (or resume) the channel
+ * but do not suspend the ParentChannel's IPDL message queue.
+ */
+ void SuspendInternal();
+ void ResumeInternal();
+};
diff --git a/netwerk/base/nsIChildChannel.idl b/netwerk/base/nsIChildChannel.idl
new file mode 100644
index 0000000000..fae77f322e
--- /dev/null
+++ b/netwerk/base/nsIChildChannel.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+/**
+ * Implemented by content side of IPC protocols.
+ */
+
+[scriptable, uuid(c45b92ae-4f07-41dd-b0ef-aa044eeabb1e)]
+interface nsIChildChannel : nsISupports
+{
+ /**
+ * Create the chrome side of the IPC protocol and join an existing 'real'
+ * channel on the parent process. The id is provided by
+ * nsIRedirectChannelRegistrar on the chrome process and pushed to the child
+ * protocol as an argument to event starting a redirect.
+ *
+ * Primarilly used in HttpChannelChild::Redirect1Begin on a newly created
+ * child channel, where the new channel is intended to be created on the
+ * child process.
+ */
+ void connectParent(in uint32_t registrarId);
+
+ /**
+ * As AsyncOpen is called on the chrome process for redirect target channels,
+ * we have to inform the child side of the protocol of that fact by a special
+ * method.
+ */
+ void completeRedirectSetup(in nsIStreamListener aListener,
+ in nsISupports aContext);
+};
diff --git a/netwerk/base/nsIClassOfService.idl b/netwerk/base/nsIClassOfService.idl
new file mode 100644
index 0000000000..30590b3244
--- /dev/null
+++ b/netwerk/base/nsIClassOfService.idl
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIClassOfService.idl
+ *
+ * Used to express class dependencies and characteristics - complimentary to
+ * nsISupportsPriority which is used to express weight
+ *
+ * Channels that implement this interface may make use of this
+ * information in different ways.
+ *
+ * The default gecko HTTP/1 stack makes Followers wait for Leaders to
+ * complete before dispatching followers. Other classes run in
+ * parallel - neither being blocked nor blocking. All grouping is done
+ * based on the Load Group - separate load groups proceed
+ * independently.
+ *
+ * HTTP/2 does not use the load group, but prioritization is done per
+ * HTTP/2 session. HTTP/2 dispatches all the requests as soon as
+ * possible.
+ * The various classes are assigned logical priority
+ * dependency groups and then transactions of that class depend on the
+ * group. In this model Followers block on Leaders and Speculative
+ * depends on Background. See Http2Stream.cpp for weighting details.
+ *
+ */
+
+[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)]
+interface nsIClassOfService : nsISupports
+{
+ attribute unsigned long classFlags;
+
+ void clearClassFlags(in unsigned long flags);
+ void addClassFlags(in unsigned long flags);
+
+ const unsigned long Leader = 1 << 0;
+ const unsigned long Follower = 1 << 1;
+ const unsigned long Speculative = 1 << 2;
+ const unsigned long Background = 1 << 3;
+ const unsigned long Unblocked = 1 << 4;
+ const unsigned long Throttleable = 1 << 5;
+ const unsigned long UrgentStart = 1 << 6;
+};
diff --git a/netwerk/base/nsIContentSniffer.idl b/netwerk/base/nsIContentSniffer.idl
new file mode 100644
index 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/nsICryptoFIPSInfo.idl b/netwerk/base/nsICryptoFIPSInfo.idl
new file mode 100644
index 0000000000..1defc56abc
--- /dev/null
+++ b/netwerk/base/nsICryptoFIPSInfo.idl
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(99e81922-7318-4431-b3aa-78b3cb4119bb)]
+interface nsICryptoFIPSInfo : nsISupports
+{
+ readonly attribute boolean isFIPSModeActive;
+};
+
+%{C++
+#define NS_CRYPTO_FIPSINFO_SERVICE_CONTRACTID "@mozilla.org/crypto/fips-info-service;1"
+%}
diff --git a/netwerk/base/nsICryptoHMAC.idl b/netwerk/base/nsICryptoHMAC.idl
new file mode 100644
index 0000000000..92e06a0a48
--- /dev/null
+++ b/netwerk/base/nsICryptoHMAC.idl
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIInputStream;
+interface nsIKeyObject;
+
+/**
+ * nsICryptoHMAC
+ * This interface provides HMAC signature algorithms.
+ */
+
+[scriptable, uuid(8FEB4C7C-1641-4a7b-BC6D-1964E2099497)]
+interface nsICryptoHMAC : nsISupports
+{
+ /**
+ * Hashing Algorithms. These values are to be used by the
+ * |init| method to indicate which hashing function to
+ * use. These values map onto the values defined in
+ * mozilla/security/nss/lib/softoken/pkcs11t.h and are
+ * switched to CKM_*_HMAC constant.
+ */
+ const short MD2 = 1;
+ const short MD5 = 2;
+ const short SHA1 = 3;
+ const short SHA256 = 4;
+ const short SHA384 = 5;
+ const short SHA512 = 6;
+
+ /**
+ * Initialize the hashing object. This method may be
+ * called multiple times with different algorithm types.
+ *
+ * @param aAlgorithm the algorithm type to be used.
+ * This value must be one of the above valid
+ * algorithm types.
+ *
+ * @param aKeyObject
+ * Object holding a key. To create the key object use for instance:
+ * var keyObject = Components.classes["@mozilla.org/security/keyobjectfactory;1"]
+ * .getService(Components.interfaces.nsIKeyObjectFactory)
+ * .keyFromString(Components.interfaces.nsIKeyObject.HMAC, rawKeyData);
+ *
+ * WARNING: This approach is not FIPS compliant.
+ *
+ * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm
+ * type is passed.
+ *
+ * NOTE: This method must be called before any other method
+ * on this interface is called.
+ */
+ void init(in unsigned long aAlgorithm, in nsIKeyObject aKeyObject);
+
+ /**
+ * @param aData a buffer to calculate the hash over
+ *
+ * @param aLen the length of the buffer |aData|
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ */
+ void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen);
+
+ /**
+ * Calculates and updates a new hash based on a given data stream.
+ *
+ * @param aStream an input stream to read from.
+ *
+ * @param aLen how much to read from the given |aStream|. Passing
+ * UINT32_MAX indicates that all data available will be used
+ * to update the hash.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of
+ * data to be calculated into the hash is not available.
+ *
+ */
+ void updateFromStream(in nsIInputStream aStream, in unsigned long aLen);
+
+ /**
+ * Completes this HMAC object and produces the actual HMAC diegest data.
+ *
+ * @param aASCII if true then the returned value is a base-64
+ * encoded string. if false, then the returned value is
+ * binary data.
+ *
+ * @return a hash of the data that was read by this object. This can
+ * be either binary data or base 64 encoded.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ *
+ * NOTE: This method may be called any time after |init|
+ * is called. This call resets the object to its
+ * pre-init state.
+ */
+ ACString finish(in boolean aASCII);
+
+ /**
+ * Reinitialize HMAC context to be reused with the same
+ * settings (the key and hash algorithm) but on different
+ * set of data.
+ */
+ void reset();
+};
diff --git a/netwerk/base/nsICryptoHash.idl b/netwerk/base/nsICryptoHash.idl
new file mode 100644
index 0000000000..cd865a3a9d
--- /dev/null
+++ b/netwerk/base/nsICryptoHash.idl
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIInputStream;
+
+/**
+ * nsICryptoHash
+ * This interface provides crytographic hashing algorithms.
+ */
+
+[scriptable, uuid(1e5b7c43-4688-45ce-92e1-77ed931e3bbe)]
+interface nsICryptoHash : nsISupports
+{
+ /**
+ * Hashing Algorithms. These values are to be used by the
+ * |init| method to indicate which hashing function to
+ * use. These values map directly onto the values defined
+ * in mozilla/security/nss/lib/cryptohi/hasht.h.
+ */
+ const short MD2 = 1; /* String value: "md2" */
+ const short MD5 = 2; /* String value: "md5" */
+ const short SHA1 = 3; /* String value: "sha1" */
+ const short SHA256 = 4; /* String value: "sha256" */
+ const short SHA384 = 5; /* String value: "sha384" */
+ const short SHA512 = 6; /* String value: "sha512" */
+
+ /**
+ * Initialize the hashing object. This method may be
+ * called multiple times with different algorithm types.
+ *
+ * @param aAlgorithm the algorithm type to be used.
+ * This value must be one of the above valid
+ * algorithm types.
+ *
+ * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm
+ * type is passed.
+ *
+ * NOTE: This method or initWithString must be called
+ * before any other method on this interface is called.
+ */
+ void init(in unsigned long aAlgorithm);
+
+ /**
+ * Initialize the hashing object. This method may be
+ * called multiple times with different algorithm types.
+ *
+ * @param aAlgorithm the algorithm type to be used.
+ *
+ * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm
+ * type is passed.
+ *
+ * NOTE: This method or init must be called before any
+ * other method on this interface is called.
+ */
+ void initWithString(in ACString aAlgorithm);
+
+ /**
+ * @param aData a buffer to calculate the hash over
+ *
+ * @param aLen the length of the buffer |aData|
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ */
+ void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen);
+
+ /**
+ * Calculates and updates a new hash based on a given data stream.
+ *
+ * @param aStream an input stream to read from.
+ *
+ * @param aLen how much to read from the given |aStream|. Passing
+ * UINT32_MAX indicates that all data available will be used
+ * to update the hash.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of
+ * data to be calculated into the hash is not available.
+ *
+ */
+ void updateFromStream(in nsIInputStream aStream, in unsigned long aLen);
+
+ /**
+ * Completes this hash object and produces the actual hash data.
+ *
+ * @param aASCII if true then the returned value is a base-64
+ * encoded string. if false, then the returned value is
+ * binary data.
+ *
+ * @return a hash of the data that was read by this object. This can
+ * be either binary data or base 64 encoded.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ *
+ * NOTE: This method may be called any time after |init|
+ * is called. This call resets the object to its
+ * pre-init state.
+ */
+ ACString finish(in boolean aASCII);
+};
diff --git a/netwerk/base/nsIDashboard.idl b/netwerk/base/nsIDashboard.idl
new file mode 100644
index 0000000000..a18710e5a0
--- /dev/null
+++ b/netwerk/base/nsIDashboard.idl
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/* A JavaScript callback function that takes a JSON as its parameter.
+ * The returned JSON contains arrays with data
+ */
+[scriptable, function, uuid(19d7f24f-a95a-4fd9-87e2-d96e9e4b1f6d)]
+interface NetDashboardCallback : nsISupports
+{
+ void onDashboardDataAvailable(in jsval data);
+};
+
+/* The dashboard service.
+ * The async API returns JSONs, which hold arrays with the required info.
+ * Only one request of each type may be pending at any time.
+ */
+[scriptable, uuid(c79eb3c6-091a-45a6-8544-5a8d1ab79537)]
+interface nsIDashboard : nsISupports
+{
+ /* Arrays: host, port, tcp, active, socksent, sockreceived
+ * Values: sent, received */
+ void requestSockets(in NetDashboardCallback cb);
+
+ /* Arrays: host, port, spdy, ssl
+ * Array of arrays: active, idle */
+ void requestHttpConnections(in NetDashboardCallback cb);
+
+ /* Arrays: hostport, encrypted, msgsent, msgreceived, sentsize, receivedsize */
+ void requestWebsocketConnections(in NetDashboardCallback cb);
+
+ /* Arrays: hostname, family, hostaddr, expiration */
+ void requestDNSInfo(in NetDashboardCallback cb);
+
+ /* aProtocol: a transport layer protocol:
+ * ex: "ssl", "tcp", default is "tcp".
+ * aHost: the host's name
+ * aPort: the port which the connection will open on
+ * aTimeout: the timespan before the connection will be timed out */
+ void requestConnection(in ACString aHost, in unsigned long aPort,
+ in string aProtocol, in unsigned long aTimeout,
+ in NetDashboardCallback cb);
+
+ /* When true, the service will log websocket events */
+ attribute boolean enableLogging;
+
+ /* DNS resolver for host name
+ * aHost: host name */
+ void requestDNSLookup(in ACString aHost, in NetDashboardCallback cb);
+
+ AUTF8String getLogPath();
+};
diff --git a/netwerk/base/nsIDashboardEventNotifier.idl b/netwerk/base/nsIDashboardEventNotifier.idl
new file mode 100644
index 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/nsIDivertableChannel.idl b/netwerk/base/nsIDivertableChannel.idl
new file mode 100644
index 0000000000..4c28ca7dca
--- /dev/null
+++ b/netwerk/base/nsIDivertableChannel.idl
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class ChannelDiverterChild;
+}
+}
+%}
+
+[ptr] native ChannelDiverterChild(mozilla::net::ChannelDiverterChild);
+
+interface nsIStreamListener;
+
+/**
+ * A channel implementing this interface allows diverting from an
+ * nsIStreamListener in the child process to one in the parent.
+ */
+[uuid(7a9bf52d-f828-4b31-b8df-b40fdd37d007)]
+interface nsIDivertableChannel : nsISupports
+{
+ /**
+ * CHILD ONLY.
+ * Called by Necko client in child process during OnStartRequest to divert
+ * nsIStreamListener and nsIRequest callbacks to the parent process.
+ *
+ * The process should look like the following:
+ *
+ * 1) divertToParent is called in the child process. It can only be called
+ * during OnStartRequest().
+ *
+ * 2) The ChannelDiverterChild that is returned is an IPDL object. It should
+ * be passed via some other IPDL method of the client's choosing to the
+ * parent. On the parent the ChannelDiverterParent's divertTo() function
+ * should be called with an nsIStreamListener that will then receive the
+ * OnStartRequest/OnDataAvailable/OnStopRequest for the channel. The
+ * ChannelDiverterParent can then be deleted (which will also destroy the
+ * ChannelDiverterChild in the child).
+ *
+ * After divertToParent() has been called, NO further function calls
+ * should be made on the channel. It is a dead object for all purposes.
+ * The reference that the channel holds to the listener in the child is
+ * released is once OnStartRequest completes, and no other
+ * nsIStreamListener calls (OnDataAvailable, OnStopRequest) will be made
+ * to it.
+ *
+ * @return ChannelDiverterChild IPDL actor to be passed to parent process by
+ * client IPDL message, e.g. PClient.DivertUsing(PDiverterChild).
+ *
+ * @throws exception if the channel was canceled early. Throws status code of
+ * canceled channel.
+ */
+ ChannelDiverterChild divertToParent();
+
+ /**
+ * nsUnknownDecoder delays calling OnStartRequest until it gets enough data
+ * to decide about the content type (until OnDataAvaiable is called). In a
+ * OnStartRequest DivertToParent can be called but some OnDataAvailables are
+ * already called and therefore can not be diverted to parent.
+ *
+ * nsUnknownDecoder will call UnknownDecoderInvolvedKeepData in its
+ * OnStartRequest function and when it calls OnStartRequest of the next
+ * listener it will call UnknownDecoderInvolvedOnStartRequestCalled. In this
+ * function Child process will decide to discarge data if it is not diverting
+ * to parent or keep them if it is diverting to parent.
+ */
+ void unknownDecoderInvolvedKeepData();
+
+ void unknownDecoderInvolvedOnStartRequestCalled();
+
+ readonly attribute bool divertingToParent;
+};
diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl
new file mode 100644
index 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..77cee0d28b
--- /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..e5f357f6a6
--- /dev/null
+++ b/netwerk/base/nsIFileStreams.idl
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+
+interface nsIFile;
+
+%{C++
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+
+/**
+ * An input stream that allows you to read from a file.
+ */
+[scriptable, uuid(e3d56a20-c7ec-11d3-8cda-0060b0fc14a3)]
+interface nsIFileInputStream : nsIInputStream
+{
+ /**
+ * @param file file to read from
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDONLY).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * If this is set, the file will be deleted by the time the stream is
+ * closed. It may be removed before the stream is closed if it is possible
+ * to delete it and still read from it.
+ *
+ * If OPEN_ON_READ is defined, and the file was recreated after the first
+ * delete, the file will be deleted again when it is closed again.
+ */
+ const long DELETE_ON_CLOSE = 1<<1;
+
+ /**
+ * If this is set, the file will close automatically when the end of the
+ * file is reached.
+ */
+ const long CLOSE_ON_EOF = 1<<2;
+
+ /**
+ * If this is set, the file will be reopened whenever we reach the start of
+ * the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative
+ * seek that happen to reach the beginning of the file. If the file is
+ * already open and the seek occurs, it will happen naturally. (The file
+ * will only be reopened if it is closed for some reason.)
+ */
+ const long REOPEN_ON_REWIND = 1<<3;
+
+ /**
+ * If this is set, the file will be opened (i.e., a call to
+ * PR_Open done) only when we do an actual operation on the stream,
+ * or more specifically, when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - ReadLine
+ *
+ * DEFER_OPEN is useful if we use the stream on a background
+ * thread, so that the opening and possible |stat|ing of the file
+ * happens there as well.
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read. Also, the file is not locked when Init is called,
+ * so it might be deleted before we try to read from it.
+ */
+ const long DEFER_OPEN = 1<<4;
+
+ /**
+ * This flag has no effect and is totally ignored on any platform except
+ * Windows since this is the default behavior on POSIX systems. On Windows
+ * if this flag is set then the stream is opened in a special mode that
+ * allows the OS to delete the file from disk just like POSIX.
+ */
+ const long SHARE_DELETE = 1<<5;
+};
+
+/**
+ * An output stream that lets you stream to a file.
+ */
+[scriptable, uuid(e734cac9-1295-4e6f-9684-3ac4e1f91063)]
+interface nsIFileOutputStream : nsIOutputStream
+{
+ /**
+ * @param file file to write to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_WRONLY |
+ * PR_CREATE_FILE | PR_TRUNCATE)
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default permissions (0664)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (currently none supported)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * @param length asks the operating system to allocate storage for
+ * this file of at least |length| bytes long, and
+ * set the file length to the corresponding size.
+ * @throws NS_ERROR_FAILURE if the preallocation fails.
+ * @throws NS_ERROR_NOT_INITIALIZED if the file is not opened.
+ */
+ [noscript] void preallocate(in long long length);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Write
+ * - Flush
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first write, and if the file is to be created, then it will not
+ * appear on the disk until the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * An input stream that allows you to read from a slice of a file.
+ */
+[scriptable, uuid(3ce03a2f-97f7-4375-b6bb-1788a60cad3b)]
+interface nsIPartialFileInputStream : nsISupports
+{
+ /**
+ * Initialize with a file and new start/end positions. Both start and
+ * start+length must be smaller than the size of the file. Not doing so
+ * will lead to undefined behavior.
+ * You must initialize the stream, and only initialize it once, before it
+ * can be used.
+ *
+ * @param file file to read from
+ * @param start start offset of slice to read. Must be smaller
+ * than the size of the file.
+ * @param length length of slice to read. Must be small enough that
+ * start+length is smaller than the size of the file.
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDONLY).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in nsIFileInputStream)
+ */
+ void init(in nsIFile file, in unsigned long long start,
+ in unsigned long long length,
+ in long ioFlags, in long perm, in long behaviorFlags);
+};
+
+/**
+ * A stream that allows you to read from a file or stream to a file.
+ */
+[scriptable, uuid(82cf605a-8393-4550-83ab-43cd5578e006)]
+interface nsIFileStream : nsISupports
+{
+ /**
+ * @param file file to read from or stream to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDWR).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - Flush
+ * - Write
+ * - GetSize
+ * - GetLastModified
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read or write. The file is not locked when Init is called,
+ * so it might be deleted before we try to read from it and if the
+ * file is to be created, then it will not appear on the disk until
+ * the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * An interface that allows you to get some metadata like file size and
+ * file last modified time.
+ */
+[scriptable, uuid(07f679e4-9601-4bd1-b510-cd3852edb881)]
+interface nsIFileMetadata : nsISupports
+{
+ /**
+ * File size in bytes;
+ */
+ readonly attribute long long size;
+
+ /**
+ * File last modified time in milliseconds from midnight (00:00:00),
+ * January 1, 1970 Greenwich Mean Time (GMT).
+ */
+ readonly attribute long long lastModified;
+
+ /**
+ * The internal file descriptor. It can be used for memory mapping of the
+ * underlying file. Please use carefully!
+ */
+ [noscript] PRFileDescPtr getFileDescriptor();
+};
diff --git a/netwerk/base/nsIFileURL.idl b/netwerk/base/nsIFileURL.idl
new file mode 100644
index 0000000000..9f8b67cb81
--- /dev/null
+++ b/netwerk/base/nsIFileURL.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURL.idl"
+
+interface nsIFile;
+
+/**
+ * nsIFileURL provides access to the underlying nsIFile object corresponding to
+ * an URL. The URL scheme need not be file:, since other local protocols may
+ * map URLs to files (e.g., resource:).
+ */
+[scriptable, uuid(e91ac988-27c2-448b-b1a1-3822e1ef1987)]
+interface nsIFileURL : nsIURL
+{
+ /**
+ * Get/Set nsIFile corresponding to this URL.
+ *
+ * - Getter returns a reference to an immutable object. Callers must clone
+ * before attempting to modify the returned nsIFile object. NOTE: this
+ * constraint might not be enforced at runtime, so beware!!
+ *
+ * - Setter clones the nsIFile object (allowing the caller to safely modify
+ * the nsIFile object after setting it on this interface).
+ */
+ attribute nsIFile file;
+};
diff --git a/netwerk/base/nsIForcePendingChannel.idl b/netwerk/base/nsIForcePendingChannel.idl
new file mode 100644
index 0000000000..a4d6ef894b
--- /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..9ce2335150
--- /dev/null
+++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)]
+interface nsIHttpAuthenticatorCallback : nsISupports
+{
+ /**
+ * Authentication data for a header is available.
+ *
+ * @param aCreds
+ * Credentials which were obtained asynchonously.
+ * @param aFlags
+ * Flags set by asynchronous call.
+ * @param aResult
+ * Result status of credentials generation
+ * @param aSessionState
+ * Modified session state to be passed to caller
+ * @param aContinuationState
+ * Modified continuation state to be passed to caller
+ */
+ void onCredsGenerated(in string aCreds,
+ in unsigned long aFlags,
+ in nsresult aResult,
+ in nsISupports aSessionsState,
+ in nsISupports aContinuationState);
+
+};
+
diff --git a/netwerk/base/nsIHttpPushListener.idl b/netwerk/base/nsIHttpPushListener.idl
new file mode 100644
index 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..9bb7774054
--- /dev/null
+++ b/netwerk/base/nsIIOService.idl
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIProtocolHandler;
+interface nsIChannel;
+interface nsIURI;
+interface nsIFile;
+interface nsIDOMNode;
+interface nsIPrincipal;
+interface nsILoadInfo;
+
+/**
+ * nsIIOService provides a set of network utility functions. This interface
+ * duplicates many of the nsIProtocolHandler methods in a protocol handler
+ * independent way (e.g., NewURI inspects the scheme in order to delegate
+ * creation of the new URI to the appropriate protocol handler). nsIIOService
+ * also provides a set of URL parsing utility functions. These are provided
+ * as a convenience to the programmer and in some cases to improve performance
+ * by eliminating intermediate data structures and interfaces.
+ */
+[scriptable, uuid(4286de5a-b2ea-446f-8f70-e2a461f42694)]
+interface nsIIOService : nsISupports
+{
+ /**
+ * Returns a protocol handler for a given URI scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return reference to corresponding nsIProtocolHandler
+ */
+ nsIProtocolHandler getProtocolHandler(in string aScheme);
+
+ /**
+ * Returns the protocol flags for a given scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return value of corresponding nsIProtocolHandler::protocolFlags
+ */
+ unsigned long getProtocolFlags(in string aScheme);
+
+ /**
+ * This method constructs a new URI by determining the scheme of the
+ * URI spec, and then delegating the construction of the URI to the
+ * protocol handler for that scheme. QueryInterface can be used on
+ * the resulting URI object to obtain a more specific type of URI.
+ *
+ * @see nsIProtocolHandler::newURI
+ */
+ nsIURI newURI(in AUTF8String aSpec,
+ [optional] in string aOriginCharset,
+ [optional] in nsIURI aBaseURI);
+
+ /**
+ * This method constructs a new URI from a nsIFile.
+ *
+ * @param aFile specifies the file path
+ * @return reference to a new nsIURI object
+ *
+ * Note: in the future, for perf reasons we should allow
+ * callers to specify whether this is a file or directory by
+ * splitting this into newDirURI() and newActualFileURI().
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ */
+ nsIChannel newChannelFromURI2(in nsIURI aURI,
+ in nsIDOMNode aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in unsigned long aContentPolicyType);
+
+ /**
+ * Equivalent to newChannelFromURI2(aURI, aLoadingNode, ...)
+ */
+ nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI,
+ in nsILoadInfo aLoadInfo);
+
+ /**
+ * Equivalent to newChannelFromURI2(newURI(...))
+ */
+ nsIChannel newChannel2(in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI,
+ in nsIDOMNode aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in unsigned long aContentPolicyType);
+
+ /**
+ * ***** DEPRECATED *****
+ * Please use NewChannelFromURI2()
+ *
+ * Creates a channel for a given URI.
+ *
+ * @param aURI nsIURI from which to make a channel
+ * @return reference to the new nsIChannel object
+ */
+ nsIChannel newChannelFromURI(in nsIURI aURI);
+
+ /**
+ * ***** DEPRECATED *****
+ * Please use newChannel2().
+ *
+ * Equivalent to newChannelFromURI(newURI(...))
+ */
+ nsIChannel newChannel(in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI);
+
+
+ /**
+ * Returns true if networking is in "offline" mode. When in offline mode,
+ * attempts to access the network will fail (although this does not
+ * necessarily correlate with whether there is actually a network
+ * available -- that's hard to detect without causing the dialer to
+ * come up).
+ *
+ * Changing this fires observer notifications ... see below.
+ */
+ attribute boolean offline;
+
+ /**
+ * Returns false if there are no interfaces for a network request
+ */
+ readonly attribute boolean connectivity;
+
+ /**
+ * Checks if a port number is banned. This involves consulting a list of
+ * unsafe ports, corresponding to network services that may be easily
+ * exploitable. If the given port is considered unsafe, then the protocol
+ * handler (corresponding to aScheme) will be asked whether it wishes to
+ * override the IO service's decision to block the port. This gives the
+ * protocol handler ultimate control over its own security policy while
+ * ensuring reasonable, default protection.
+ *
+ * @see nsIProtocolHandler::allowPort
+ */
+ boolean allowPort(in long aPort, in string aScheme);
+
+ /**
+ * Utility to extract the scheme from a URL string, consistently and
+ * according to spec (see RFC 2396).
+ *
+ * NOTE: Most URL parsing is done via nsIURI, and in fact the scheme
+ * can also be extracted from a URL string via nsIURI. This method
+ * is provided purely as an optimization.
+ *
+ * @param aSpec the URL string to parse
+ * @return URL scheme
+ *
+ * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form.
+ */
+ ACString extractScheme(in AUTF8String urlString);
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE
+ * when 'offline' has changed from false to true, and we are about
+ * to shut down network services such as DNS. When those
+ * services have been shut down, we send a notification with
+ * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_OFFLINE.
+ *
+ * When 'offline' changes from true to false, then after
+ * network services have been restarted, we send a notification
+ * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_ONLINE.
+ */
+#define NS_IOSERVICE_GOING_OFFLINE_TOPIC "network:offline-about-to-go-offline"
+#define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed"
+#define NS_IOSERVICE_OFFLINE "offline"
+#define NS_IOSERVICE_ONLINE "online"
+
+%}
+
+[builtinclass, uuid(6633c0bf-d97a-428f-8ece-cb6a655fb95a)]
+interface nsIIOServiceInternal : nsISupports
+{
+ /**
+ * This is an internal method that should only be called from ContentChild
+ * in order to pass the connectivity state from the chrome process to the
+ * content process. It throws if called outside the content process.
+ */
+ void SetConnectivity(in boolean connectivity);
+
+ /**
+ * An internal method to asynchronously run our notifications that happen
+ * when we wake from sleep
+ */
+ void NotifyWakeup();
+};
diff --git a/netwerk/base/nsIIOService2.idl b/netwerk/base/nsIIOService2.idl
new file mode 100644
index 0000000000..d7b434d170
--- /dev/null
+++ b/netwerk/base/nsIIOService2.idl
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIIOService.idl"
+
+interface nsIDOMNode;
+interface nsIPrincipal;
+
+/**
+ * nsIIOService2 extends nsIIOService
+ */
+[scriptable, uuid(52c5804b-0d3c-4d4f-8654-1c36fd310e69)]
+interface nsIIOService2 : nsIIOService
+{
+ /**
+ * While this is set, IOService will monitor an nsINetworkLinkService
+ * (if available) and set its offline status to "true" whenever
+ * isLinkUp is false.
+ *
+ * Applications that want to control changes to the IOService's offline
+ * status should set this to false, watch for network:link-status-changed
+ * broadcasts, and change nsIIOService::offline as they see fit. Note
+ * that this means during application startup, IOService may be offline
+ * if there is no link, until application code runs and can turn off
+ * this management.
+ */
+ attribute boolean manageOfflineStatus;
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aProxyURI
+ * nsIURI to use for proxy resolution. Can be null in which
+ * case aURI is used
+ * @param aProxyFlags flags from nsIProtocolProxyService to use
+ * when resolving proxies for this new channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ */
+ nsIChannel newChannelFromURIWithProxyFlags2(in nsIURI aURI,
+ in nsIURI aProxyURI,
+ in unsigned long aProxyFlags,
+ in nsIDOMNode aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in unsigned long aContentPolicyType);
+
+ /**
+ * ***** DEPRECATED *****
+ * Please use newChannelFromURIWithProxyFlags2()
+ *
+ * Creates a channel for a given URI.
+ *
+ * @param aURI nsIURI from which to make a channel
+ * @param aProxyURI nsIURI to use for proxy resolution. Can be null in which
+ * case aURI is used
+ * @param aProxyFlags flags from nsIProtocolProxyService to use
+ * when resolving proxies for this new channel
+ * @return reference to the new nsIChannel object
+ */
+ nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI,
+ in nsIURI aProxyURI,
+ in unsigned long aProxyFlags);
+
+};
diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl
new file mode 100644
index 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..3af16ed62f
--- /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..83c29cdbb5
--- /dev/null
+++ b/netwerk/base/nsIInputStreamPump.idl
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsIStreamListener;
+
+/**
+ * nsIInputStreamPump
+ *
+ * This interface provides a means to configure and use a input stream pump
+ * instance. The input stream pump will asynchronously read from an input
+ * stream, and push data to an nsIStreamListener instance. It utilizes the
+ * current thread's nsIEventTarget in order to make reading from the stream
+ * asynchronous. A different thread can be used if the pump also implements
+ * nsIThreadRetargetableRequest.
+ *
+ * If the given stream supports nsIAsyncInputStream, then the stream pump will
+ * call the stream's AsyncWait method to drive the stream listener. Otherwise,
+ * the stream will be read on a background thread utilizing the stream
+ * transport service. More details are provided below.
+ */
+[scriptable, uuid(400F5468-97E7-4d2b-9C65-A82AECC7AE82)]
+interface nsIInputStreamPump : nsIRequest
+{
+ /**
+ * Initialize the input stream pump.
+ *
+ * @param aStream
+ * contains the data to be read. if the input stream is non-blocking,
+ * then it will be QI'd to nsIAsyncInputStream. if the QI succeeds
+ * then the stream will be read directly. otherwise, it will be read
+ * on a background thread using the stream transport service.
+ * @param aStreamPos
+ * specifies the stream offset from which to start reading. the
+ * offset value is absolute. pass -1 to specify the current offset.
+ * NOTE: this parameter is ignored if the underlying stream does not
+ * implement nsISeekableStream.
+ * @param aStreamLen
+ * specifies how much data to read from the stream. pass -1 to read
+ * all data available in the stream.
+ * @param aSegmentSize
+ * if the stream transport service is used, then this parameter
+ * specifies the segment size for the stream transport's buffer.
+ * pass 0 to specify the default value.
+ * @param aSegmentCount
+ * if the stream transport service is used, then this parameter
+ * specifies the segment count for the stream transport's buffer.
+ * pass 0 to specify the default value.
+ * @param aCloseWhenDone
+ * if true, the input stream will be closed after it has been read.
+ */
+ void init(in nsIInputStream aStream,
+ in long long aStreamPos,
+ in long long aStreamLen,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount,
+ in boolean aCloseWhenDone);
+
+ /**
+ * asyncRead causes the input stream to be read in chunks and delivered
+ * asynchronously to the listener via OnDataAvailable.
+ *
+ * @param aListener
+ * receives notifications.
+ * @param aListenerContext
+ * passed to listener methods.
+ */
+ void asyncRead(in nsIStreamListener aListener,
+ in nsISupports aListenerContext);
+};
diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl
new file mode 100644
index 0000000000..b344a15443
--- /dev/null
+++ b/netwerk/base/nsILoadContextInfo.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+%}
+native OriginAttributesNativePtr(const mozilla::NeckoOriginAttributes*);
+
+interface nsILoadContext;
+interface nsIDOMWindow;
+
+/**
+ * Helper interface to carry informatin about the load context
+ * encapsulating origin attributes and IsAnonymous, IsPrivite properties.
+ * It shall be used where nsILoadContext cannot be used or is not
+ * available.
+ */
+
+[scriptable, builtinclass, uuid(555e2f8a-a1f6-41dd-88ca-ed4ed6b98a22)]
+interface nsILoadContextInfo : nsISupports
+{
+ const unsigned long NO_APP_ID = 0;
+ const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX
+
+ /**
+ * Whether the context is in a Private Browsing mode
+ */
+ readonly attribute boolean isPrivate;
+
+ /**
+ * Whether the load is initiated as anonymous
+ */
+ readonly attribute boolean isAnonymous;
+
+ /**
+ * NeckoOriginAttributes hiding all the security context attributes
+ */
+ [implicit_jscontext]
+ readonly attribute jsval originAttributes;
+ [noscript, notxpcom, nostdcall, binaryname(OriginAttributesPtr)]
+ OriginAttributesNativePtr binaryOriginAttributesPtr();
+
+%{C++
+ /**
+ * De-XPCOMed getters
+ */
+ bool IsPrivate()
+ {
+ bool pb;
+ GetIsPrivate(&pb);
+ return pb;
+ }
+
+ bool IsAnonymous()
+ {
+ bool anon;
+ GetIsAnonymous(&anon);
+ return anon;
+ }
+
+ bool Equals(nsILoadContextInfo *aOther)
+ {
+ return IsAnonymous() == aOther->IsAnonymous() &&
+ *OriginAttributesPtr() == *aOther->OriginAttributesPtr();
+ }
+%}
+};
+
+/**
+ * Since NeckoOriginAttributes struct limits the implementation of
+ * nsILoadContextInfo (that needs to be thread safe) to C++,
+ * we need a scriptable factory to create instances of that
+ * interface from JS.
+ */
+[scriptable, uuid(c1c7023d-4318-4f99-8307-b5ccf0558793)]
+interface nsILoadContextInfoFactory : nsISupports
+{
+ readonly attribute nsILoadContextInfo default;
+ readonly attribute nsILoadContextInfo private;
+ readonly attribute nsILoadContextInfo anonymous;
+ [implicit_jscontext]
+ nsILoadContextInfo custom(in boolean aAnonymous, in jsval aOriginAttributes);
+ nsILoadContextInfo fromLoadContext(in nsILoadContext aLoadContext, in boolean aAnonymous);
+ nsILoadContextInfo fromWindow(in nsIDOMWindow aWindow, in boolean aAnonymous);
+};
diff --git a/netwerk/base/nsILoadGroup.idl b/netwerk/base/nsILoadGroup.idl
new file mode 100644
index 0000000000..4f89bd0e3a
--- /dev/null
+++ b/netwerk/base/nsILoadGroup.idl
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsISimpleEnumerator;
+interface nsIRequestObserver;
+interface nsIInterfaceRequestor;
+interface nsIRequestContext;
+
+/**
+ * A load group maintains a collection of nsIRequest objects.
+ * This is used in lots of places where groups of requests need to be tracked.
+ * For example, nsIDocument::mDocumentLoadGroup is used to track all requests
+ * made for subdocuments in order to track page load progress and allow all
+ * requests made on behalf of the document to be stopped, etc.
+ */
+[scriptable, uuid(f0c87725-7a35-463c-9ceb-2c07f23406cc)]
+interface nsILoadGroup : nsIRequest
+{
+ /**
+ * The group observer is notified when requests are added to and removed
+ * from this load group. The groupObserver is weak referenced.
+ */
+ attribute nsIRequestObserver groupObserver;
+
+ /**
+ * Accesses the default load request for the group. Each time a number
+ * of requests are added to a group, the defaultLoadRequest may be set
+ * to indicate that all of the requests are related to a base request.
+ *
+ * The load group inherits its load flags from the default load request.
+ * If the default load request is NULL, then the group's load flags are
+ * not changed.
+ */
+ attribute nsIRequest defaultLoadRequest;
+
+ /**
+ * Adds a new request to the group. This will cause the default load
+ * flags to be applied to the request. If this is a foreground
+ * request then the groupObserver's onStartRequest will be called.
+ *
+ * If the request is the default load request or if the default load
+ * request is null, then the load group will inherit its load flags from
+ * the request.
+ */
+ void addRequest(in nsIRequest aRequest,
+ in nsISupports aContext);
+
+ /**
+ * Removes a request from the group. If this is a foreground request
+ * then the groupObserver's onStopRequest will be called.
+ *
+ * By the time this call ends, aRequest will have been removed from the
+ * loadgroup, even if this function throws an exception.
+ */
+ void removeRequest(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsresult aStatus);
+
+ /**
+ * Returns the requests contained directly in this group.
+ * Enumerator element type: nsIRequest.
+ */
+ readonly attribute nsISimpleEnumerator requests;
+
+ /**
+ * Returns the count of "active" requests (ie. requests without the
+ * LOAD_BACKGROUND bit set).
+ */
+ readonly attribute unsigned long activeCount;
+
+ /**
+ * Notification callbacks for the load group.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Context for managing things like js/css connection blocking,
+ * and per-tab connection grouping.
+ */
+ [noscript] readonly attribute nsID requestContextID;
+
+ /**
+ * The set of load flags that will be added to all new requests added to
+ * this group. Any existing requests in the load group are not modified,
+ * so it is expected these flags will be added before requests are added
+ * to the group - typically via nsIDocShell::defaultLoadFlags on a new
+ * docShell.
+ * Note that these flags are *not* added to the default request for the
+ * load group; it is expected the default request will already have these
+ * flags (again, courtesy of setting nsIDocShell::defaultLoadFlags before
+ * the docShell has created the default request.)
+ */
+ attribute nsLoadFlags defaultLoadFlags;
+
+ /**
+ * The cached user agent override created by UserAgentOverrides.jsm. Used
+ * for all sub-resource requests in the loadgroup.
+ */
+ attribute ACString userAgentOverrideCache;
+};
diff --git a/netwerk/base/nsILoadGroupChild.idl b/netwerk/base/nsILoadGroupChild.idl
new file mode 100644
index 0000000000..3ec60a1a26
--- /dev/null
+++ b/netwerk/base/nsILoadGroupChild.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILoadGroup;
+
+/**
+ * nsILoadGroupChild provides a hierarchy of load groups so that the
+ * root load group can be used to conceptually tie a series of loading
+ * operations into a logical whole while still leaving them separate
+ * for the purposes of cancellation and status events.
+ */
+
+[scriptable, uuid(02efe8e2-fbbc-4718-a299-b8a09c60bf6b)]
+interface nsILoadGroupChild : nsISupports
+{
+ /**
+ * The parent of this load group. It is stored with
+ * a nsIWeakReference/nsWeakPtr so there is no requirement for the
+ * parentLoadGroup to out live the child, nor will the child keep a
+ * reference count on the parent.
+ */
+ attribute nsILoadGroup parentLoadGroup;
+
+ /**
+ * The nsILoadGroup associated with this nsILoadGroupChild
+ */
+ readonly attribute nsILoadGroup childLoadGroup;
+
+ /**
+ * The rootLoadGroup is the recursive parent of this
+ * load group where parent is defined as parentlLoadGroup if set
+ * or childLoadGroup.loadGroup as a backup. (i.e. parentLoadGroup takes
+ * precedence.) The nsILoadGroup child is the root if neither parent
+ * nor loadgroup attribute is specified.
+ */
+ readonly attribute nsILoadGroup rootLoadGroup;
+};
+
diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
new file mode 100644
index 0000000000..78433c8b88
--- /dev/null
+++ b/netwerk/base/nsILoadInfo.idl
@@ -0,0 +1,732 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIDOMDocument;
+interface nsINode;
+interface nsIPrincipal;
+
+%{C++
+#include "nsTArray.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/LoadTainting.h"
+
+class nsCString;
+%}
+
+[ref] native const_nsIPrincipalArray(const nsTArray<nsCOMPtr<nsIPrincipal>>);
+native NeckoOriginAttributes(mozilla::NeckoOriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes);
+[ref] native StringArrayRef(const nsTArray<nsCString>);
+
+typedef unsigned long nsSecurityFlags;
+
+/**
+ * The LoadInfo object contains information about a network load, why it
+ * was started, and how we plan on using the resulting response.
+ * If a network request is redirected, the new channel will receive a new
+ * LoadInfo object. The new object will contain mostly the same
+ * information as the pre-redirect one, but updated as appropriate.
+ * For detailed information about what parts of LoadInfo are updated on
+ * redirect, see documentation on individual properties.
+ */
+[scriptable, builtinclass, uuid(ddc65bf9-2f60-41ab-b22a-4f1ae9efcd36)]
+interface nsILoadInfo : nsISupports
+{
+ /**
+ * *** DEPRECATED ***
+ * No LoadInfo created within Gecko should contain this security flag.
+ * Please use any of the five security flags defined underneath.
+ * We only keep this security flag to provide backwards compatibilty.
+ */
+ const unsigned long SEC_NORMAL = 0;
+
+ /**
+ * The following five flags determine the security mode and hence what kind of
+ * security checks should be performed throughout the lifetime of the channel.
+ *
+ * * SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS
+ * * SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ * * SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
+ * * SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
+ * * SEC_REQUIRE_CORS_DATA_INHERITS
+ *
+ * Exactly one of these flags are required to be set in order to allow
+ * the channel to perform the correct security checks (SOP, CORS, ...) and
+ * return the correct result principal. If none or more than one of these
+ * flags are set AsyncOpen2 will fail.
+ */
+
+ /*
+ * Enforce the same origin policy where data: loads inherit
+ * the principal.
+ */
+ const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS = (1<<0);
+
+ /*
+ * Enforce the same origin policy but data: loads are blocked.
+ */
+ const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED = (1<<1);
+
+ /**
+ * Allow loads from other origins. Loads from data: will inherit
+ * the principal of the origin that triggered the load.
+ * Commonly used by plain <img>, <video>, <link rel=stylesheet> etc.
+ */
+ const unsigned long SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS = (1<<2);
+
+ /**
+ * Allow loads from other origins. Loads from data: will be allowed,
+ * but the resulting resource will get a null principal.
+ * Used in blink/webkit for <iframe>s. Likely also the mode
+ * that should be used by most Chrome code.
+ */
+ const unsigned long SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL = (1<<3);
+
+ /**
+ * Allow loads from any origin, but require CORS for cross-origin
+ * loads. Loads from data: are allowed and the result will inherit
+ * the principal of the origin that triggered the load.
+ * Commonly used by <img crossorigin>, <video crossorigin>,
+ * XHR, fetch(), etc.
+ */
+ const unsigned long SEC_REQUIRE_CORS_DATA_INHERITS = (1<<4);
+
+ /**
+ * Choose cookie policy. The default policy is equivalent to "INCLUDE" for
+ * SEC_REQUIRE_SAME_ORIGIN_* and SEC_ALLOW_CROSS_ORIGIN_* modes, and
+ * equivalent to "SAME_ORIGIN" for SEC_REQUIRE_CORS_DATA_INHERITS mode.
+ *
+ * This means that if you want to perform a CORS load with credentials, pass
+ * SEC_COOKIES_INCLUDE.
+ *
+ * Note that these flags are still subject to the user's cookie policies.
+ * For example, if the user is blocking 3rd party cookies, those cookies
+ * will be blocked no matter which of these flags are set.
+ */
+ const unsigned long SEC_COOKIES_DEFAULT = (0 << 5);
+ const unsigned long SEC_COOKIES_INCLUDE = (1 << 5);
+ const unsigned long SEC_COOKIES_SAME_ORIGIN = (2 << 5);
+ const unsigned long SEC_COOKIES_OMIT = (3 << 5);
+
+ /**
+ * Force inheriting of the Principal. The resulting resource will use the
+ * principal of the document which is doing the load. Setting this flag
+ * will cause GetChannelResultPrincipal to return the same principal as
+ * the loading principal that's passed in when creating the channel.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ *
+ * So if the loading document comes from "http://a.com/", and the channel
+ * is loading the URI "http://b.com/whatever", GetChannelResultPrincipal
+ * will return a principal from "http://a.com/".
+ *
+ * This flag can not be used together with SEC_SANDBOXED. If both are passed
+ * to the LoadInfo constructor then this flag will be dropped. If you need
+ * to know whether this flag would have been present but was dropped due to
+ * sandboxing, check for the forceInheritPrincipalDropped flag.
+ */
+ const unsigned long SEC_FORCE_INHERIT_PRINCIPAL = (1<<7);
+
+ /**
+ * Sandbox the load. The resulting resource will use a freshly created
+ * null principal. So GetChannelResultPrincipal will always return a
+ * null principal whenever this flag is set.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ *
+ * This flag can not be used together with SEC_FORCE_INHERIT_PRINCIPAL.
+ */
+ const unsigned long SEC_SANDBOXED = (1<<8);
+
+ /**
+ * Inherit the Principal for about:blank.
+ */
+ const unsigned long SEC_ABOUT_BLANK_INHERITS = (1<<9);
+
+ /**
+ * Allow access to chrome: packages that are content accessible.
+ */
+ const unsigned long SEC_ALLOW_CHROME = (1<<10);
+
+ /**
+ * Disallow access to javascript: uris.
+ */
+ const unsigned long SEC_DISALLOW_SCRIPT = (1<<11);
+
+ /**
+ * Don't follow redirects. Instead the redirect response is returned
+ * as a successful response for the channel.
+ *
+ * Redirects not initiated by a server response, i.e. REDIRECT_INTERNAL and
+ * REDIRECT_STS_UPGRADE, are still followed.
+ *
+ * Note: If this flag is set and the channel response is a redirect, then
+ * the response body might not be available.
+ * This can happen if the redirect was cached.
+ */
+ const unsigned long SEC_DONT_FOLLOW_REDIRECTS = (1<<12);
+
+ /**
+ * Load an error page, it should be one of following : about:neterror,
+ * about:certerror, about:blocked, or about:tabcrashed.
+ */
+ const unsigned long SEC_LOAD_ERROR_PAGE = (1<<13);
+
+ /**
+ * Force inheriting of the principalToInherit, overruling any owner
+ * that might be set on the channel. (Please note that channel.owner
+ * is deprecated and will be removed within Bug 1286838).
+ * Setting this flag will cause GetChannelResultPrincipal to return the
+ * principalToInherit set in the loadInfo.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ */
+ const unsigned long SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER = (1<<14);
+
+ /**
+ * This is the principal of the network request's caller/requester where
+ * the resulting resource will be used. I.e. it is the principal which
+ * will get access to the result of the request. (Where "get access to"
+ * might simply mean "embed" depending on the type of resource that is
+ * loaded).
+ *
+ * For example for an image, it is the principal of the document where
+ * the image is rendered. For a stylesheet it is the principal of the
+ * document where the stylesheet will be applied.
+ *
+ * So if document at http://a.com/page.html loads an image from
+ * http://b.com/pic.jpg, then loadingPrincipal will be
+ * http://a.com/page.html.
+ *
+ * For <iframe> and <frame> loads, the LoadingPrincipal is the
+ * principal of the parent document. For top-level loads, the
+ * LoadingPrincipal is null. For all loads except top-level loads
+ * the LoadingPrincipal is never null.
+ *
+ * If the loadingPrincipal is the system principal, no security checks
+ * will be done at all. There will be no security checks on the initial
+ * load or any subsequent redirects. This means there will be no
+ * nsIContentPolicy checks or any CheckLoadURI checks. Because of
+ * this, never set the loadingPrincipal to the system principal when
+ * the URI to be loaded is controlled by a webpage.
+ * If the loadingPrincipal and triggeringPrincipal are both
+ * codebase-principals, then we will always call into
+ * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies
+ * and CheckLoadURI happen even if the URI to be loaded is same-origin
+ * with the loadingPrincipal or triggeringPrincipal.
+ */
+ readonly attribute nsIPrincipal loadingPrincipal;
+
+ /**
+ * A C++-friendly version of loadingPrincipal.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(LoadingPrincipal)]
+ nsIPrincipal binaryLoadingPrincipal();
+
+ /**
+ * This is the principal which caused the network load to start. I.e.
+ * this is the principal which provided the URL to be loaded. This is
+ * often the same as the LoadingPrincipal, but there are a few cases
+ * where that's not true.
+ *
+ * For example for loads into an <iframe>, the LoadingPrincipal is always
+ * the principal of the parent document. However the triggeringPrincipal
+ * is the principal of the document which provided the URL that the
+ * <iframe> is navigating to. This could be the previous document inside
+ * the <iframe> which set document.location. Or a document elsewhere in
+ * the frame tree which contained a <a target="..."> which targetted the
+ * <iframe>.
+ *
+ * If a stylesheet links to a sub-resource, like an @imported stylesheet,
+ * or a background image, then the triggeringPrincipal is the principal
+ * of the stylesheet, while the LoadingPrincipal is the principal of the
+ * document being styled.
+ *
+ * The triggeringPrincipal is never null.
+ *
+ * If the triggeringPrincipal is the system principal, no security checks
+ * will be done at all. There will be no security checks on the initial
+ * load or any subsequent redirects. This means there will be no
+ * nsIContentPolicy checks or any CheckLoadURI checks. Because of
+ * this, never set the triggeringPrincipal to the system principal when
+ * the URI to be loaded is controlled by a webpage.
+ * If the loadingPrincipal and triggeringPrincipal are both
+ * codebase-principals, then we will always call into
+ * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies
+ * and CheckLoadURI happen even if the URI to be loaded is same-origin
+ * with the loadingPrincipal or triggeringPrincipal.
+ */
+ readonly attribute nsIPrincipal triggeringPrincipal;
+
+ /**
+ * A C++-friendly version of triggeringPrincipal.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)]
+ nsIPrincipal binaryTriggeringPrincipal();
+
+ /**
+ * For non-document loads the principalToInherit is always null. For
+ * loads of type TYPE_DOCUMENT or TYPE_SUBDOCUMENT the principalToInherit
+ * might be null. If it's non null, then this is the principal that is
+ * inherited if a principal needs to be inherited. If the principalToInherit
+ * is null but the inherit flag is set, then the triggeringPrincipal is
+ * the principal that is inherited.
+ */
+ attribute nsIPrincipal principalToInherit;
+
+ /**
+ * A C++-friendly version of principalToInherit.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(PrincipalToInherit)]
+ nsIPrincipal binaryPrincipalToInherit();
+
+ /**
+ * This is the ownerDocument of the LoadingNode. Unless the LoadingNode
+ * is a Document, in which case the LoadingDocument is the same as the
+ * LoadingNode.
+ *
+ * For top-level loads, and for loads originating from workers, the
+ * LoadingDocument is null. When the LoadingDocument is not null, the
+ * LoadingPrincipal is set to the principal of the LoadingDocument.
+ */
+ readonly attribute nsIDOMDocument loadingDocument;
+
+ /**
+ * A C++-friendly version of loadingDocument (loadingNode).
+ * This is the Node where the resulting resource will be used. I.e. it is
+ * the Node which will get access to the result of the request. (Where
+ * "get access to" might simply mean "embed" depending on the type of
+ * resource that is loaded).
+ *
+ * For example for an <img>/<video> it is the image/video element. For
+ * document loads inside <iframe> and <frame>s, the LoadingNode is the
+ * <iframe>/<frame> element. For an XMLHttpRequest, it is the Document
+ * which contained the JS which initiated the XHR. For a stylesheet, it
+ * is the Document that contains <link rel=stylesheet>.
+ *
+ * For loads triggered by the HTML pre-parser, the LoadingNode is the
+ * Document which is currently being parsed.
+ *
+ * For top-level loads, and for loads originating from workers, the
+ * LoadingNode is null. If the LoadingNode is non-null, then the
+ * LoadingPrincipal is the principal of the LoadingNode.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(LoadingNode)]
+ nsINode binaryLoadingNode();
+
+ /**
+ * The securityFlags of that channel.
+ */
+ readonly attribute nsSecurityFlags securityFlags;
+
+%{ C++
+ inline nsSecurityFlags GetSecurityFlags()
+ {
+ nsSecurityFlags result;
+ mozilla::DebugOnly<nsresult> rv = GetSecurityFlags(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * Allows to query only the security mode bits from above.
+ */
+ [infallible] readonly attribute unsigned long securityMode;
+
+ /**
+ * True if this request is embedded in a context that can't be third-party
+ * (i.e. an iframe embedded in a cross-origin parent window). If this is
+ * false, then this request may be third-party if it's a third-party to
+ * loadingPrincipal.
+ */
+ [infallible] readonly attribute boolean isInThirdPartyContext;
+
+ /**
+ * See the SEC_COOKIES_* flags above. This attribute will never return
+ * SEC_COOKIES_DEFAULT, but will instead return what the policy resolves to.
+ * I.e. SEC_COOKIES_SAME_ORIGIN for CORS mode, and SEC_COOKIES_INCLUDE
+ * otherwise.
+ */
+ [infallible] readonly attribute unsigned long cookiePolicy;
+
+ /**
+ * If forceInheritPrincipal is true, the data coming from the channel should
+ * use loadingPrincipal for its principal, even when the data is loaded over
+ * http:// or another protocol that would normally use a URI-based principal.
+ * This attribute will never be true when loadingSandboxed is true.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipal;
+
+ /**
+ * If forceInheritPrincipalOverruleOwner is true, the data coming from the
+ * channel should use principalToInherit for its principal, even when the
+ * data is loaded over http:// or another protocol that would normally use
+ * a URI-based principal.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipalOverruleOwner;
+
+ /**
+ * If loadingSandboxed is true, the data coming from the channel is
+ * being loaded sandboxed, so it should have a nonce origin and
+ * hence should use a NullPrincipal.
+ */
+ [infallible] readonly attribute boolean loadingSandboxed;
+
+ /**
+ * If aboutBlankInherits is true, then about:blank should inherit
+ * the principal.
+ */
+ [infallible] readonly attribute boolean aboutBlankInherits;
+
+ /**
+ * If allowChrome is true, then use nsIScriptSecurityManager::ALLOW_CHROME
+ * when calling CheckLoadURIWithPrincipal().
+ */
+ [infallible] readonly attribute boolean allowChrome;
+
+ /**
+ * If disallowScript is true, then use nsIScriptSecurityManager::DISALLOW_SCRIPT
+ * when calling CheckLoadURIWithPrincipal().
+ */
+ [infallible] readonly attribute boolean disallowScript;
+
+ /**
+ * Returns true if SEC_DONT_FOLLOW_REDIRECTS is set.
+ */
+ [infallible] readonly attribute boolean dontFollowRedirects;
+
+ /**
+ * Returns true if SEC_LOAD_ERROR_PAGE is set.
+ */
+ [infallible] readonly attribute boolean loadErrorPage;
+
+ /**
+ * The external contentPolicyType of the channel, used for security checks
+ * like Mixed Content Blocking and Content Security Policy.
+ *
+ * Specifically, content policy types with _INTERNAL_ in their name will
+ * never get returned from this attribute.
+ */
+ readonly attribute nsContentPolicyType externalContentPolicyType;
+
+%{ C++
+ inline nsContentPolicyType GetExternalContentPolicyType()
+ {
+ nsContentPolicyType result;
+ mozilla::DebugOnly<nsresult> rv = GetExternalContentPolicyType(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * The internal contentPolicyType of the channel, used for constructing
+ * RequestContext values when creating a fetch event for an intercepted
+ * channel.
+ *
+ * This should not be used for the purposes of security checks, since
+ * the content policy implementations cannot be expected to deal with
+ * _INTERNAL_ values. Please use the contentPolicyType attribute above
+ * for that purpose.
+ */
+ [noscript, notxpcom]
+ nsContentPolicyType internalContentPolicyType();
+
+ /**
+ * Returns true if document or any of the documents ancestors
+ * up to the toplevel document make use of the CSP directive
+ * 'upgrade-insecure-requests'. Used to identify upgrade
+ * requests in e10s where the loadingDocument is not available.
+ *
+ * Warning: If the loadingDocument is null, then the
+ * upgradeInsecureRequests is false.
+ */
+ [infallible] readonly attribute boolean upgradeInsecureRequests;
+
+ /**
+ * If true, the content of the channel is queued up and checked
+ * if it matches a content signature. Note, setting this flag
+ * to true will negatively impact performance since the preloader
+ * can not start until all of the content is fetched from the
+ * netwerk.
+ *
+ * Only use that in combination with TYPE_DOCUMENT.
+ */
+ [infallible] attribute boolean verifySignedContent;
+
+ /**
+ * If true, this load will fail if it has no SRI integrity
+ */
+ [infallible] attribute boolean enforceSRI;
+
+ /**
+ * The SEC_FORCE_INHERIT_PRINCIPAL flag may be dropped when a load info
+ * object is created. Specifically, it will be dropped if the SEC_SANDBOXED
+ * flag is also present. This flag is set if SEC_FORCE_INHERIT_PRINCIPAL was
+ * dropped.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipalDropped;
+
+ /**
+ * These are the window IDs of the window in which the element being
+ * loaded lives. parentOuterWindowID is the window ID of this window's
+ * parent.
+ *
+ * Note that these window IDs can be 0 if the window is not
+ * available. parentOuterWindowID will be the same as outerWindowID if the
+ * window has no parent.
+ */
+ [infallible] readonly attribute unsigned long long innerWindowID;
+ [infallible] readonly attribute unsigned long long outerWindowID;
+ [infallible] readonly attribute unsigned long long parentOuterWindowID;
+
+ /**
+ * Only when the element being loaded is <frame src="foo.html">
+ * (or, more generally, if the element QIs to nsIFrameLoaderOwner),
+ * the frameOuterWindowID is the outer window containing the
+ * foo.html document.
+ *
+ * Note: For other cases, frameOuterWindowID is 0.
+ */
+ [infallible] readonly attribute unsigned long long frameOuterWindowID;
+
+ /**
+ * For all loads of none TYPE_DOUCMENT this function resets the
+ * LoadingPrincipal, the TriggeringPrincipal and the
+ * PrincipalToInherit to a freshly created NullPrincipal which inherits
+ * the current origin attributes from the loadinfo.
+ * For loads of TYPE_DOCUMENT this function resets only the
+ * TriggeringPrincipal as well as the PrincipalToInherit to a freshly
+ * created NullPrincipal which inherits the origin attributes from
+ * the loadInfo. (Please note that the LoadingPrincipal for TYPE_DOCUMENT
+ * loads is always null.)
+ *
+ * WARNING: Please only use that function if you know exactly what
+ * you are doing!!!
+ */
+ void resetPrincipalsToNullPrincipal();
+
+ /**
+ * Customized NeckoOriginAttributes within LoadInfo to allow overwriting of the
+ * default originAttributes from the loadingPrincipal.
+ *
+ * In chrome side, originAttributes.privateBrowsingId will always be 0 even if
+ * the usePrivateBrowsing is true, because chrome docshell won't set
+ * privateBrowsingId on origin attributes (See bug 1278664). This is to make
+ * sure nsILoadInfo and nsILoadContext have the same origin attributes.
+ */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ attribute jsval originAttributes;
+
+ [noscript, nostdcall, binaryname(GetOriginAttributes)]
+ NeckoOriginAttributes binaryGetOriginAttributes();
+
+ [noscript, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+
+%{ C++
+ inline mozilla::NeckoOriginAttributes GetOriginAttributes()
+ {
+ mozilla::NeckoOriginAttributes result;
+ mozilla::DebugOnly<nsresult> rv = GetOriginAttributes(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * Whenever a channel is openend by asyncOpen2() [or also open2()],
+ * lets set this flag so that redirects of such channels are also
+ * openend using asyncOpen2() [open2()].
+ *
+ * Please note, once the flag is set to true it must remain true
+ * throughout the lifetime of the channel. Trying to set it
+ * to anything else than true will be discareded.
+ *
+ */
+ [infallible] attribute boolean enforceSecurity;
+
+ /**
+ * Whenever a channel is evaluated by the ContentSecurityManager
+ * the first time, we set this flag to true to indicate that
+ * subsequent calls of AsyncOpen2() do not have to enforce all
+ * security checks again. E.g., after a redirect there is no
+ * need to set up CORS again. We need this separate flag
+ * because the redirectChain might also contain internal
+ * redirects which might pollute the redirectChain so we can't
+ * rely on the size of the redirectChain-array to query whether
+ * a channel got redirected or not.
+ *
+ * Please note, once the flag is set to true it must remain true
+ * throughout the lifetime of the channel. Trying to set it
+ * to anything else than true will be discarded.
+ *
+ */
+ [infallible] attribute boolean initialSecurityCheckDone;
+
+ /**
+ * Whenever a channel gets redirected, append the principal of the
+ * channel [before the channels got redirected] to the loadinfo,
+ * so that at every point this array lets us reason about all the
+ * redirects this channel went through.
+ * @param aPrincipal, the channelURIPrincipal before the channel
+ * got redirected.
+ * @param aIsInternalRedirect should be true if the channel is going
+ * through an internal redirect, otherwise false.
+ */
+ void appendRedirectedPrincipal(in nsIPrincipal principal,
+ in boolean isInternalRedirect);
+
+ /**
+ * An array of nsIPrincipals which stores redirects associated with this
+ * channel. This array is filled whether or not the channel has ever been
+ * opened. The last element of the array is associated with the most recent
+ * redirect. Please note, that this array *includes* internal redirects.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval redirectChainIncludingInternalRedirects;
+
+ /**
+ * A C++-friendly version of redirectChain.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChainIncludingInternalRedirects)]
+ const_nsIPrincipalArray binaryRedirectChainIncludingInternalRedirects();
+
+ /**
+ * Same as RedirectChain but does *not* include internal redirects.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval redirectChain;
+
+ /**
+ * A C++-friendly version of redirectChain.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChain)]
+ const_nsIPrincipalArray binaryRedirectChain();
+
+ /**
+ * Sets the list of unsafe headers according to CORS spec, as well as
+ * potentially forces a preflight.
+ * Note that you do not need to set the Content-Type header. That will be
+ * automatically detected as needed.
+ *
+ * Only call this function when using the SEC_REQUIRE_CORS_DATA_INHERITS mode.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setCorsPreflightInfo(in StringArrayRef unsafeHeaders,
+ in boolean forcePreflight);
+
+ /**
+ * A C++-friendly getter for the list of cors-unsafe headers.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(CorsUnsafeHeaders)]
+ StringArrayRef corsUnsafeHeaders();
+
+ /**
+ * Returns value set through setCorsPreflightInfo.
+ */
+ [infallible] readonly attribute boolean forcePreflight;
+
+ /**
+ * A C++ friendly getter for the forcePreflight flag.
+ */
+ [infallible] readonly attribute boolean isPreflight;
+
+ /**
+ * When this request would be mixed-content and we do not have an
+ * entry in the HSTS cache, we send an HSTS priming request to
+ * determine if it is ok to upgrade the request to HTTPS.
+ */
+ /**
+ * True if this is a mixed-content load and HSTS priming request will be sent.
+ */
+ [noscript, infallible] readonly attribute boolean forceHSTSPriming;
+ /**
+ * Carry the decision whether this load would be blocked by mixed content so
+ * that if HSTS priming fails, the correct decision can be made.
+ */
+ [noscript, infallible] readonly attribute boolean mixedContentWouldBlock;
+
+ /**
+ * Mark this LoadInfo as needing HSTS Priming
+ *
+ * @param wouldBlock Carry the decision of Mixed Content Blocking to be
+ * applied when HSTS priming is complete.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setHSTSPriming(in boolean mixeContentWouldBlock);
+ [noscript, notxpcom, nostdcall]
+ void clearHSTSPriming();
+
+ /**
+ * Constants reflecting the channel tainting. These are mainly defined here
+ * for script. Internal C++ code should use the enum defined in LoadTainting.h.
+ * See LoadTainting.h for documentation.
+ */
+ const unsigned long TAINTING_BASIC = 0;
+ const unsigned long TAINTING_CORS = 1;
+ const unsigned long TAINTING_OPAQUE = 2;
+
+ /**
+ * Determine the associated channel's current tainting. Note, this can
+ * change due to a service worker intercept, so it should be checked after
+ * OnStartRequest() fires.
+ */
+ readonly attribute unsigned long tainting;
+
+ /**
+ * Note a new tainting level and possibly increase the current tainting
+ * to match. If the tainting level is already greater than the given
+ * value, then there is no effect. It is not possible to reduce the tainting
+ * level on an existing channel/loadinfo.
+ */
+ void maybeIncreaseTainting(in unsigned long aTainting);
+
+ /**
+ * Various helper code to provide more convenient C++ access to the tainting
+ * attribute and maybeIncreaseTainting().
+ */
+%{C++
+ static_assert(TAINTING_BASIC == static_cast<uint32_t>(mozilla::LoadTainting::Basic),
+ "basic tainting enums should match");
+ static_assert(TAINTING_CORS == static_cast<uint32_t>(mozilla::LoadTainting::CORS),
+ "cors tainting enums should match");
+ static_assert(TAINTING_OPAQUE == static_cast<uint32_t>(mozilla::LoadTainting::Opaque),
+ "opaque tainting enums should match");
+
+ mozilla::LoadTainting GetTainting()
+ {
+ uint32_t tainting = TAINTING_BASIC;
+ MOZ_ALWAYS_SUCCEEDS(GetTainting(&tainting));
+ return static_cast<mozilla::LoadTainting>(tainting);
+ }
+
+ void MaybeIncreaseTainting(mozilla::LoadTainting aTainting)
+ {
+ uint32_t tainting = static_cast<uint32_t>(aTainting);
+ MOZ_ALWAYS_SUCCEEDS(MaybeIncreaseTainting(tainting));
+ }
+%}
+
+ /**
+ * Returns true if this load is for top level document.
+ * Note that the load for a sub-frame's document will return false here.
+ */
+ [infallible] readonly attribute boolean isTopLevelLoad;
+};
diff --git a/netwerk/base/nsIMIMEInputStream.idl b/netwerk/base/nsIMIMEInputStream.idl
new file mode 100644
index 0000000000..82992d939b
--- /dev/null
+++ b/netwerk/base/nsIMIMEInputStream.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+[scriptable, uuid(dcbce63c-1dd1-11b2-b94d-91f6d49a3161)]
+interface nsIMIMEInputStream : nsIInputStream
+{
+ /**
+ * When true a "Content-Length" header is automatically added to the
+ * stream. The value of the content-length is automatically calculated
+ * using the available() method on the data stream. The value is
+ * recalculated every time the stream is rewinded to the start.
+ * Not allowed to be changed once the stream has been started to be read.
+ */
+ attribute boolean addContentLength;
+
+ /**
+ * Adds an additional header to the stream on the form "name: value". May
+ * not be called once the stream has been started to be read.
+ * @param name name of the header
+ * @param value value of the header
+ */
+ void addHeader(in string name, in string value);
+
+ /**
+ * Sets data-stream. May not be called once the stream has been started
+ * to be read.
+ * The cursor of the new stream should be located at the beginning of the
+ * stream if the implementation of the nsIMIMEInputStream also is used as
+ * an nsISeekableStream.
+ * @param stream stream containing the data for the stream
+ */
+ void setData(in nsIInputStream stream);
+
+ /**
+ * Get the wrapped data stream
+ */
+ readonly attribute nsIInputStream data;
+};
diff --git a/netwerk/base/nsIMultiPartChannel.idl b/netwerk/base/nsIMultiPartChannel.idl
new file mode 100644
index 0000000000..1b95176282
--- /dev/null
+++ b/netwerk/base/nsIMultiPartChannel.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * An interface to access the the base channel
+ * associated with a MultiPartChannel.
+ */
+
+[scriptable, uuid(4fefb490-5567-11e5-a837-0800200c9a66)]
+interface nsIMultiPartChannel : nsISupports
+{
+ /**
+ * readonly attribute to access the underlying channel
+ */
+ readonly attribute nsIChannel baseChannel;
+
+ /**
+ * Attribute guaranteed to be different for different parts of
+ * the same multipart document.
+ */
+ readonly attribute uint32_t partID;
+
+ /**
+ * Set to true when onStopRequest is received from the base channel.
+ * The listener can check this from its onStopRequest to determine
+ * whether more data can be expected.
+ */
+ readonly attribute boolean isLastPart;
+};
diff --git a/netwerk/base/nsINSSErrorsService.idl b/netwerk/base/nsINSSErrorsService.idl
new file mode 100644
index 0000000000..95a6e6d0c7
--- /dev/null
+++ b/netwerk/base/nsINSSErrorsService.idl
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(12f60021-e14b-4020-99d1-ed2c795be66a)]
+interface nsINSSErrorsService : nsISupports
+{
+ /**
+ * @param aNSPRCode An error code obtained using PR_GetError()
+ * @return True if it is error code defined by the NSS library
+ */
+ boolean isNSSErrorCode(in int32_t aNSPRCode);
+
+ /**
+ * Function will fail if aNSPRCode is not an NSS error code.
+ * @param aNSPRCode An error code obtained using PR_GetError()
+ * @return The result of the conversion, an XPCOM error code
+ */
+ nsresult getXPCOMFromNSSError(in int32_t aNSPRCode);
+
+ /**
+ * Function will fail if aXPCOMErrorCode is not an NSS error code.
+ * @param aXPCOMErrorCode An error code obtained using getXPCOMFromNSSError
+ * return A localized human readable error explanation.
+ */
+ AString getErrorMessage(in nsresult aXPCOMErrorCode);
+
+ /**
+ * Function will fail if aXPCOMErrorCode is not an NSS error code.
+ * @param aXPCOMErrorCode An error code obtained using getXPCOMFromNSSError
+ * return the error class of the code, either ERROR_CLASS_BAD_CERT
+ * or ERROR_CLASS_SSL_PROTOCOL
+ */
+ uint32_t getErrorClass(in nsresult aXPCOMErrorCode);
+
+ const unsigned long ERROR_CLASS_SSL_PROTOCOL = 1;
+ const unsigned long ERROR_CLASS_BAD_CERT = 2;
+
+ /**
+ * The following values define the range of NSPR error codes used by NSS.
+ * NSS remains the authorative source for these numbers, as a result,
+ * the values might change in the future.
+ * The security module will perform a runtime check and assertion
+ * to ensure the values are in synch with NSS.
+ */
+ const long NSS_SEC_ERROR_BASE = -(0x2000);
+ const long NSS_SEC_ERROR_LIMIT = (NSS_SEC_ERROR_BASE + 1000);
+ const long NSS_SSL_ERROR_BASE = -(0x3000);
+ const long NSS_SSL_ERROR_LIMIT = (NSS_SSL_ERROR_BASE + 1000);
+
+ /**
+ * The error codes within each module must fit in 16 bits. We want these
+ * errors to fit in the same module as the NSS errors but not overlap with
+ * any of them. Converting an NSS SEC, NSS SSL, or mozilla::pkix error to
+ * an NS error involves negating the value of the error and then
+ * synthesizing an error in the NS_ERROR_MODULE_SECURITY module. Hence,
+ * mozilla::pkix errors will start at a negative value that both doesn't
+ * overlap with the current value ranges for NSS errors and that will fit
+ * in 16 bits when negated.
+ *
+ * Keep these in sync with pkixnss.h.
+ */
+ const long MOZILLA_PKIX_ERROR_BASE = -(0x4000);
+ const long MOZILLA_PKIX_ERROR_LIMIT = (MOZILLA_PKIX_ERROR_BASE + 1000);
+};
diff --git a/netwerk/base/nsINestedURI.idl b/netwerk/base/nsINestedURI.idl
new file mode 100644
index 0000000000..48cd9b36a7
--- /dev/null
+++ b/netwerk/base/nsINestedURI.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsINestedURI is an interface that must be implemented by any nsIURI
+ * implementation which has an "inner" URI that it actually gets data
+ * from.
+ *
+ * For example, if URIs for the scheme "sanitize" have the structure:
+ *
+ * sanitize:http://example.com
+ *
+ * and opening a channel on such a sanitize: URI gets the data from
+ * http://example.com, sanitizes it, and returns it, then the sanitize: URI
+ * should implement nsINestedURI and return the http://example.com URI as its
+ * inner URI.
+ */
+[scriptable, uuid(6de2c874-796c-46bf-b57f-0d7bd7d6cab0)]
+interface nsINestedURI : nsISupports
+{
+ /**
+ * The inner URI for this nested URI. This must not return null if the
+ * getter succeeds; URIs that have no inner must not QI to this interface.
+ * Dynamically changing whether there is an inner URI is not allowed.
+ *
+ * Modifying the returned URI must not in any way modify the nested URI; this
+ * means the returned URI must be either immutable or a clone.
+ */
+ readonly attribute nsIURI innerURI;
+
+ /**
+ * The innermost URI for this nested URI. This must not return null if the
+ * getter succeeds. This is equivalent to repeatedly calling innerURI while
+ * the returned URI QIs to nsINestedURI.
+ *
+ * Modifying the returned URI must not in any way modify the nested URI; this
+ * means the returned URI must be either immutable or a clone.
+ */
+ readonly attribute nsIURI innermostURI;
+};
diff --git a/netwerk/base/nsINetAddr.idl b/netwerk/base/nsINetAddr.idl
new file mode 100644
index 0000000000..c7388354d3
--- /dev/null
+++ b/netwerk/base/nsINetAddr.idl
@@ -0,0 +1,88 @@
+/* vim: et ts=4 sw=4 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+
+/**
+ * nsINetAddr
+ *
+ * This interface represents a native NetAddr struct in a readonly
+ * interface.
+ */
+[scriptable, uuid(652B9EC5-D159-45D7-9127-50BB559486CD)]
+interface nsINetAddr : nsISupports
+{
+ /**
+ * @return the address family of the network address, which corresponds to
+ * one of the FAMILY_ constants.
+ */
+ readonly attribute unsigned short family;
+
+ /**
+ * @return Either the IP address (FAMILY_INET, FAMILY_INET6) or the path
+ * (FAMILY_LOCAL) in string form. IP addresses are in the format produced by
+ * mozilla::net::NetAddrToString.
+ *
+ * Note: Paths for FAMILY_LOCAL may have length limitations which are
+ * implementation dependent and not documented as part of this interface.
+ */
+ readonly attribute AUTF8String address;
+
+ /**
+ * @return the port number for a FAMILY_INET or FAMILY_INET6 address.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET or
+ * FAMILY_INET6.
+ */
+ readonly attribute unsigned short port;
+
+ /**
+ * @return the flow label for a FAMILY_INET6 address.
+ *
+ * @see http://www.ietf.org/rfc/rfc3697.txt
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute unsigned long flow;
+
+ /**
+ * @return the address scope of a FAMILY_INET6 address.
+ *
+ * @see http://tools.ietf.org/html/rfc4007
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute unsigned long scope;
+
+ /**
+ * @return whether a FAMILY_INET6 address is mapped from FAMILY_INET.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute boolean isV4Mapped;
+
+ /**
+ * Network address families. These correspond to all the network address
+ * families supported by the NetAddr struct.
+ */
+ const unsigned long FAMILY_INET = 1;
+ const unsigned long FAMILY_INET6 = 2;
+ const unsigned long FAMILY_LOCAL = 3;
+
+ /**
+ * @return the underlying NetAddr struct.
+ */
+ [noscript] NetAddr getNetAddr();
+};
diff --git a/netwerk/base/nsINetUtil.idl b/netwerk/base/nsINetUtil.idl
new file mode 100644
index 0000000000..800a9ae905
--- /dev/null
+++ b/netwerk/base/nsINetUtil.idl
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIPrefBranch;
+
+/**
+ * nsINetUtil provides various network-related utility methods.
+ */
+[scriptable, uuid(fe2625ec-b884-4df1-b39c-9e830e47aa94)]
+interface nsINetUtil : nsISupports
+{
+ /**
+ * Parse a Content-Type header value in strict mode. This is a more
+ * conservative parser that reject things that violate RFC 7231 section
+ * 3.1.1.1. This is typically useful for parsing Content-Type header values
+ * that are used for HTTP requests, and those that are used to make security
+ * decisions.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aHadCharset whether a charset was explicitly specified.
+ * @return the MIME type specified in the header, in lower-case.
+ */
+ AUTF8String parseRequestContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out boolean aHadCharset);
+
+ /**
+ * Parse a Content-Type header value in relaxed mode. This is a more
+ * permissive parser that ignores things that go against RFC 7231 section
+ * 3.1.1.1. This is typically useful for parsing Content-Type header values
+ * received from web servers where we want to make a best effort attempt
+ * at extracting a useful MIME type and charset.
+ *
+ * NOTE: DO NOT USE THIS if you're going to make security decisions
+ * based on the result.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aHadCharset whether a charset was explicitly specified.
+ * @return the MIME type specified in the header, in lower-case.
+ */
+ AUTF8String parseResponseContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out boolean aHadCharset);
+
+ /**
+ * Test whether the given URI's handler has the given protocol flags.
+ *
+ * @param aURI the URI in question
+ * @param aFlags the flags we're testing for.
+ *
+ * @return whether the protocol handler for aURI has all the flags
+ * in aFlags.
+ */
+ boolean protocolHasFlags(in nsIURI aURI, in unsigned long aFlag);
+
+ /**
+ * Test whether the protocol handler for this URI or that for any of
+ * its inner URIs has the given protocol flags. This will QI aURI to
+ * nsINestedURI and walk the nested URI chain.
+ *
+ * @param aURI the URI in question
+ * @param aFlags the flags we're testing for.
+ *
+ * @return whether any of the protocol handlers involved have all the flags
+ * in aFlags.
+ */
+ boolean URIChainHasFlags(in nsIURI aURI, in unsigned long aFlags);
+
+ /**
+ * Take aURI and produce an immutable version of it for the caller. If aURI
+ * is immutable this will be aURI itself; otherwise this will be a clone,
+ * marked immutable if possible. Passing null to this method is allowed; in
+ * that case it will return null.
+ */
+ nsIURI toImmutableURI(in nsIURI aURI);
+
+ /**
+ * Create a simple nested URI using the result of
+ * toImmutableURI on the passed-in aURI which may not be null.
+ * Note: The return URI will not have had its spec set yet.
+ */
+ nsIURI newSimpleNestedURI(in nsIURI aURI);
+
+ /** Escape every character with its %XX-escaped equivalent */
+ const unsigned long ESCAPE_ALL = 0;
+
+ /** Leave alphanumeric characters intact and %XX-escape all others */
+ const unsigned long ESCAPE_XALPHAS = 1;
+
+ /** Leave alphanumeric characters intact, convert spaces to '+',
+ %XX-escape all others */
+ const unsigned long ESCAPE_XPALPHAS = 2;
+
+ /** Leave alphanumeric characters and forward slashes intact,
+ %XX-escape all others */
+ const unsigned long ESCAPE_URL_PATH = 4;
+
+ /**
+ * escape a string with %00-style escaping
+ */
+ ACString escapeString(in ACString aString, in unsigned long aEscapeType);
+
+ /** %XX-escape URL scheme */
+ const unsigned long ESCAPE_URL_SCHEME = 1;
+
+ /** %XX-escape username in the URL */
+ const unsigned long ESCAPE_URL_USERNAME = 1 << 1;
+
+ /** %XX-escape password in the URL */
+ const unsigned long ESCAPE_URL_PASSWORD = 1 << 2;
+
+ /** %XX-escape URL host */
+ const unsigned long ESCAPE_URL_HOST = 1 << 3;
+
+ /** %XX-escape URL directory */
+ const unsigned long ESCAPE_URL_DIRECTORY = 1 << 4;
+
+ /** %XX-escape file basename in the URL */
+ const unsigned long ESCAPE_URL_FILE_BASENAME = 1 << 5;
+
+ /** %XX-escape file extension in the URL */
+ const unsigned long ESCAPE_URL_FILE_EXTENSION = 1 << 6;
+
+ /** %XX-escape URL parameters */
+ const unsigned long ESCAPE_URL_PARAM = 1 << 7;
+
+ /** %XX-escape URL query */
+ const unsigned long ESCAPE_URL_QUERY = 1 << 8;
+
+ /** %XX-escape URL ref */
+ const unsigned long ESCAPE_URL_REF = 1 << 9;
+
+ /** %XX-escape URL path - same as escaping directory, basename and extension */
+ const unsigned long ESCAPE_URL_FILEPATH =
+ ESCAPE_URL_DIRECTORY | ESCAPE_URL_FILE_BASENAME | ESCAPE_URL_FILE_EXTENSION;
+
+ /** %XX-escape scheme, username, password, host, path, params, query and ref */
+ const unsigned long ESCAPE_URL_MINIMAL =
+ ESCAPE_URL_SCHEME | ESCAPE_URL_USERNAME | ESCAPE_URL_PASSWORD |
+ ESCAPE_URL_HOST | ESCAPE_URL_FILEPATH | ESCAPE_URL_PARAM |
+ ESCAPE_URL_QUERY | ESCAPE_URL_REF;
+
+ /** Force %XX-escaping of already escaped sequences */
+ const unsigned long ESCAPE_URL_FORCED = 1 << 10;
+
+ /** Skip non-ascii octets, %XX-escape all others */
+ const unsigned long ESCAPE_URL_ONLY_ASCII = 1 << 11;
+
+ /**
+ * Skip graphic octets (0x20-0x7E) when escaping
+ * Skips all ASCII octets (0x00-0x7F) when unescaping
+ */
+ const unsigned long ESCAPE_URL_ONLY_NONASCII = 1 << 12;
+
+ /** Force %XX-escape of colon */
+ const unsigned long ESCAPE_URL_COLON = 1 << 14;
+
+ /** Skip C0 and DEL from unescaping */
+ const unsigned long ESCAPE_URL_SKIP_CONTROL = 1 << 15;
+
+ /**
+ * %XX-Escape invalid chars in a URL segment.
+ *
+ * @param aStr the URL to be escaped
+ * @param aFlags the URL segment type flags
+ *
+ * @return the escaped string (the string itself if escaping did not happen)
+ *
+ */
+ ACString escapeURL(in ACString aStr, in unsigned long aFlags);
+
+ /**
+ * Expands URL escape sequences
+ *
+ * @param aStr the URL to be unescaped
+ * @param aFlags only ESCAPE_URL_ONLY_NONASCII and ESCAPE_URL_SKIP_CONTROL
+ * are recognized. If |aFlags| is 0 all escape sequences are
+ * unescaped
+ * @return unescaped string
+ */
+ ACString unescapeString(in AUTF8String aStr, in unsigned long aFlags);
+
+ /**
+ * Extract the charset parameter location and value from a content-type
+ * header.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aCharsetStart index of the start of the charset parameter
+ * (the ';' separating it from what came before) in aTypeHeader.
+ * If this function returns false, this argument will still be
+ * set, to the index of the location where a new charset should
+ * be inserted.
+ * @param [out] aCharsetEnd index of the end of the charset parameter (the
+ * ';' separating it from what comes after, or the end
+ * of the string) in aTypeHeader. If this function returns
+ * false, this argument will still be set, to the index of the
+ * location where a new charset should be inserted.
+ *
+ * @return whether a charset parameter was found. This can be false even in
+ * cases when parseContentType would claim to have a charset, if the type
+ * that won out does not have a charset parameter specified.
+ */
+ boolean extractCharsetFromContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out long aCharsetStart,
+ out long aCharsetEnd);
+
+/**
+ * Parse an attribute referrer policy string (no-referrer, origin, unsafe-url)
+ * and return the according integer code (defined in nsIHttpChannel.idl)
+ *
+ * @param aPolicyString
+ * the policy string given as attribute
+ * @return aPolicyEnum
+ * referrer policy code from nsIHttpChannel.idl, (see parser in
+ * ReferrerPolicy.h for details)
+ */
+ unsigned long parseAttributePolicyString(in AString aPolicyString);
+};
diff --git a/netwerk/base/nsINetworkInfoService.idl b/netwerk/base/nsINetworkInfoService.idl
new file mode 100644
index 0000000000..bd88045087
--- /dev/null
+++ b/netwerk/base/nsINetworkInfoService.idl
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Listener for getting list of addresses.
+ */
+[scriptable, uuid(c4bdaac1-3ab1-4fdb-9a16-17cbed794603)]
+interface nsIListNetworkAddressesListener : nsISupports
+{
+ /**
+ * Callback function that gets called by nsINetworkInfoService.listNetworkAddresses.
+ * Each address in the array is a string IP address in canonical form,
+ * e.g. 192.168.1.10, or an IPV6 address in string form.
+ */
+ void onListedNetworkAddresses([array, size_is(aAddressArraySize)] in string aAddressArray,
+ in unsigned long aAddressArraySize);
+ void onListNetworkAddressesFailed();
+};
+
+/**
+ * Listener for getting hostname.
+ */
+[scriptable, uuid(3ebdcb62-2df4-4042-8864-3fa81abd4693)]
+interface nsIGetHostnameListener : nsISupports
+{
+ void onGotHostname(in AUTF8String aHostname);
+ void onGetHostnameFailed();
+};
+
+/**
+ * Service information
+ */
+[scriptable, uuid(55fc8dae-4a58-4e0f-a49b-901cbabae809)]
+interface nsINetworkInfoService : nsISupports
+{
+ /**
+ * Obtain a list of local machine network addresses. The listener object's
+ * onListedNetworkAddresses will be called with the obtained addresses.
+ * On failure, the listener object's onListNetworkAddressesFailed() will be called.
+ */
+ void listNetworkAddresses(in nsIListNetworkAddressesListener aListener);
+
+ /**
+ * Obtain the hostname of the local machine. The listener object's
+ * onGotHostname will be called with the obtained hostname.
+ * On failure, the listener object's onGetHostnameFailed() will be called.
+ */
+ void getHostname(in nsIGetHostnameListener aListener);
+};
+
+%{ C++
+#define NETWORKINFOSERVICE_CONTRACT_ID \
+ "@mozilla.org/network-info-service;1"
+%}
diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl
new file mode 100644
index 0000000000..17d27de42a
--- /dev/null
+++ b/netwerk/base/nsINetworkInterceptController.idl
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicyBase.idl"
+
+interface nsIChannel;
+interface nsIConsoleReportCollector;
+interface nsIOutputStream;
+interface nsIURI;
+
+%{C++
+#include "nsIConsoleReportCollector.h"
+namespace mozilla {
+namespace dom {
+class ChannelInfo;
+}
+}
+%}
+
+[ptr] native ChannelInfo(mozilla::dom::ChannelInfo);
+
+/**
+ * Interface to allow implementors of nsINetworkInterceptController to control the behaviour
+ * of intercepted channels without tying implementation details of the interception to
+ * the actual channel. nsIInterceptedChannel is expected to be implemented by objects
+ * which do not implement nsIChannel.
+ */
+
+[scriptable, uuid(f4b82975-6a86-4cc4-87fe-9a1fd430c86d)]
+interface nsIInterceptedChannel : nsISupports
+{
+ /**
+ * Instruct a channel that has been intercepted to continue with the original
+ * network request.
+ */
+ void resetInterception();
+
+ /**
+ * Set the status and reason for the forthcoming synthesized response.
+ * Multiple calls overwrite existing values.
+ */
+ void synthesizeStatus(in uint16_t status, in ACString reason);
+
+ /**
+ * Attach a header name/value pair to the forthcoming synthesized response.
+ * Overwrites any existing header value.
+ */
+ void synthesizeHeader(in ACString name, in ACString value);
+
+ /**
+ * Instruct a channel that has been intercepted that a response has been
+ * synthesized and can now be read. No further header modification is allowed
+ * after this point. The caller may optionally pass a spec for a URL that
+ * this response originates from; an empty string will cause the original
+ * intercepted request's URL to be used instead.
+ */
+ void finishSynthesizedResponse(in ACString finalURLSpec);
+
+ /**
+ * Cancel the pending intercepted request.
+ * @return NS_ERROR_FAILURE if the response has already been synthesized or
+ * the original request has been instructed to continue.
+ */
+ void cancel(in nsresult status);
+
+ /**
+ * The synthesized response body to be produced.
+ */
+ readonly attribute nsIOutputStream responseBody;
+
+ /**
+ * The underlying channel object that was intercepted.
+ */
+ readonly attribute nsIChannel channel;
+
+ /**
+ * The URL of the underlying channel object, corrected for a potential
+ * secure upgrade.
+ */
+ readonly attribute nsIURI secureUpgradedChannelURI;
+
+ /**
+ * This method allows to override the channel info for the channel.
+ */
+ [noscript]
+ void setChannelInfo(in ChannelInfo channelInfo);
+
+ /**
+ * Get the internal load type from the underlying channel.
+ */
+ [noscript]
+ readonly attribute nsContentPolicyType internalContentPolicyType;
+
+ [noscript]
+ readonly attribute nsIConsoleReportCollector consoleReportCollector;
+
+%{C++
+ already_AddRefed<nsIConsoleReportCollector>
+ GetConsoleReportCollector()
+ {
+ nsCOMPtr<nsIConsoleReportCollector> reporter;
+ GetConsoleReportCollector(getter_AddRefs(reporter));
+ return reporter.forget();
+ }
+%}
+
+ /**
+ * Allow the ServiceWorkerManager to set an RAII-style object on the
+ * intercepted channel that should be released once the channel is
+ * torn down.
+ */
+ [noscript]
+ void setReleaseHandle(in nsISupports aHandle);
+};
+
+/**
+ * Interface to allow consumers to attach themselves to a channel's
+ * notification callbacks/loadgroup and determine if a given channel
+ * request should be intercepted before any network request is initiated.
+ */
+
+[scriptable, uuid(70d2b4fe-a552-48cd-8d93-1d8437a56b53)]
+interface nsINetworkInterceptController : nsISupports
+{
+ /**
+ * Returns true if a channel should avoid initiating any network
+ * requests until specifically instructed to do so.
+ *
+ * @param aURI the URI being requested by a channel
+ * @param aIsNavigate True if the request is for a navigation, false for a fetch.
+ */
+ bool shouldPrepareForIntercept(in nsIURI aURI, in bool aIsNonSubresourceRequest);
+
+ /**
+ * Notification when a given intercepted channel is prepared to accept a synthesized
+ * response via the provided stream.
+ *
+ * @param aChannel the controlling interface for a channel that has been intercepted
+ */
+ void channelIntercepted(in nsIInterceptedChannel aChannel);
+};
diff --git a/netwerk/base/nsINetworkLinkService.idl b/netwerk/base/nsINetworkLinkService.idl
new file mode 100644
index 0000000000..456c71b6c8
--- /dev/null
+++ b/netwerk/base/nsINetworkLinkService.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Network link status monitoring service.
+ */
+[scriptable, uuid(103e5293-77b3-4b70-af59-6e9e4a1f994a)]
+interface nsINetworkLinkService : nsISupports
+{
+ /* Link type constants */
+ const unsigned long LINK_TYPE_UNKNOWN = 0;
+ const unsigned long LINK_TYPE_ETHERNET = 1;
+ const unsigned long LINK_TYPE_USB = 2;
+ const unsigned long LINK_TYPE_WIFI = 3;
+ const unsigned long LINK_TYPE_WIMAX = 4;
+ const unsigned long LINK_TYPE_2G = 5;
+ const unsigned long LINK_TYPE_3G = 6;
+ const unsigned long LINK_TYPE_4G = 7;
+
+ /**
+ * This is set to true when the system is believed to have a usable
+ * network connection.
+ *
+ * The link is only up when network connections can be established. For
+ * example, the link is down during DHCP configuration (unless there
+ * is another usable interface already configured).
+ *
+ * If the link status is not currently known, we generally assume that
+ * it is up.
+ */
+ readonly attribute boolean isLinkUp;
+
+ /**
+ * This is set to true when we believe that isLinkUp is accurate.
+ */
+ readonly attribute boolean linkStatusKnown;
+
+ /**
+ * The type of network connection.
+ */
+ readonly attribute unsigned long linkType;
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_NETWORK_LINK_TOPIC whenever one of isLinkUp or linkStatusKnown
+ * changes. We pass one of the NS_NETWORK_LINK_DATA_ constants below
+ * as the aData parameter of the notification.
+ */
+#define NS_NETWORK_LINK_TOPIC "network:link-status-changed"
+
+/**
+ * isLinkUp is now true, linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_UP "up"
+/**
+ * isLinkUp is now false, linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_DOWN "down"
+/**
+ * isLinkUp is still true, but the network setup is modified.
+ * linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_CHANGED "changed"
+/**
+ * linkStatusKnown is now false.
+ */
+#define NS_NETWORK_LINK_DATA_UNKNOWN "unknown"
+
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_NETWORK_LINK_TYPE_TOPIC whenever the network connection type
+ * changes. We pass one of the valid connection type constants
+ * below as the aData parameter of the notification.
+ */
+#define NS_NETWORK_LINK_TYPE_TOPIC "network:link-type-changed"
+
+/** We were unable to determine the network connection type */
+#define NS_NETWORK_LINK_TYPE_UNKNOWN "unknown"
+
+/** A standard wired ethernet connection */
+#define NS_NETWORK_LINK_TYPE_ETHERNET "ethernet"
+
+/** A connection via a USB port */
+#define NS_NETWORK_LINK_TYPE_USB "usb"
+
+/** A connection via a WiFi access point (IEEE802.11) */
+#define NS_NETWORK_LINK_TYPE_WIFI "wifi"
+
+/** A connection via WiMax (IEEE802.16) */
+#define NS_NETWORK_LINK_TYPE_WIMAX "wimax"
+
+/** A '2G' mobile connection (e.g. GSM, GPRS, EDGE) */
+#define NS_NETWORK_LINK_TYPE_2G "2g"
+
+/** A '3G' mobile connection (e.g. UMTS, CDMA) */
+#define NS_NETWORK_LINK_TYPE_3G "3g"
+
+/** A '4G' mobile connection (e.g. LTE, UMB) */
+#define NS_NETWORK_LINK_TYPE_4G "4g"
+%}
diff --git a/netwerk/base/nsINetworkPredictor.idl b/netwerk/base/nsINetworkPredictor.idl
new file mode 100644
index 0000000000..1b6b9576bf
--- /dev/null
+++ b/netwerk/base/nsINetworkPredictor.idl
@@ -0,0 +1,165 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsILoadContext;
+interface nsINetworkPredictorVerifier;
+
+typedef unsigned long PredictorPredictReason;
+typedef unsigned long PredictorLearnReason;
+
+/**
+ * nsINetworkPredictor - learn about pages users visit, and allow us to take
+ * predictive actions upon future visits.
+ * NOTE: nsINetworkPredictor should only
+ * be used on the main thread.
+ */
+[scriptable, uuid(acc88e7c-3f39-42c7-ac31-6377c2c3d73e)]
+interface nsINetworkPredictor : nsISupports
+{
+ /**
+ * Prediction reasons
+ *
+ * PREDICT_LINK - we are being asked to take predictive action because
+ * the user is hovering over a link.
+ *
+ * PREDICT_LOAD - we are being asked to take predictive action because
+ * the user has initiated a pageload.
+ *
+ * PREDICT_STARTUP - we are being asked to take predictive action
+ * because the browser is starting up.
+ */
+ const PredictorPredictReason PREDICT_LINK = 0;
+ const PredictorPredictReason PREDICT_LOAD = 1;
+ const PredictorPredictReason PREDICT_STARTUP = 2;
+
+ /**
+ * Start taking predictive actions
+ *
+ * Calling this will cause the predictor to (possibly) start
+ * taking actions such as DNS prefetch and/or TCP preconnect based on
+ * (1) the host name that we are given, and (2) the reason we are being
+ * asked to take actions.
+ *
+ * @param targetURI - The URI we are being asked to take actions based on.
+ * @param sourceURI - The URI that is currently loaded. This is so we can
+ * avoid doing predictive actions for link hover on an HTTPS page (for
+ * example).
+ * @param reason - The reason we are being asked to take actions. Can be
+ * any of the PREDICT_* values above.
+ * In the case of PREDICT_LINK, targetURI should be the URI of the link
+ * that is being hovered over, and sourceURI should be the URI of the page
+ * on which the link appears.
+ * In the case of PREDICT_LOAD, targetURI should be the URI of the page that
+ * is being loaded and sourceURI should be null.
+ * In the case of PREDICT_STARTUP, both targetURI and sourceURI should be
+ * null.
+ * @param loadContext - The nsILoadContext of the page load we are predicting
+ * about.
+ * @param verifier - An nsINetworkPredictorVerifier used in testing to ensure
+ * we're predicting the way we expect to. Not necessary (or desired) for
+ * normal operation.
+ */
+ void predict(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorPredictReason reason,
+ in nsILoadContext loadContext,
+ in nsINetworkPredictorVerifier verifier);
+
+
+ /*
+ * Reasons we are learning something
+ *
+ * LEARN_LOAD_TOPLEVEL - we are learning about the toplevel resource of a
+ * pageload (NOTE: this should ONLY be used by tests)
+ *
+ * LEARN_LOAD_SUBRESOURCE - we are learning a subresource from a pageload
+ *
+ * LEARN_LOAD_REDIRECT - we are learning about the re-direct of a URI
+ *
+ * LEARN_STARTUP - we are learning about a page loaded during startup
+ */
+ const PredictorLearnReason LEARN_LOAD_TOPLEVEL = 0;
+ const PredictorLearnReason LEARN_LOAD_SUBRESOURCE = 1;
+ const PredictorLearnReason LEARN_LOAD_REDIRECT = 2;
+ const PredictorLearnReason LEARN_STARTUP = 3;
+
+ /**
+ * Add to our compendium of knowledge
+ *
+ * This adds to our prediction database to make things (hopefully)
+ * smarter next time we predict something.
+ *
+ * @param targetURI - The URI that was loaded that we are keeping track of.
+ * @param sourceURI - The URI that caused targetURI to be loaded (for page
+ * loads). This means the DOCUMENT URI.
+ * @param reason - The reason we are learning this bit of knowledge.
+ * Reason can be any of the LEARN_* values.
+ * In the case of LEARN_LOAD_SUBRESOURCE, targetURI should be the URI of a
+ * subresource of a page, and sourceURI should be the top-level URI.
+ * In the case of LEARN_LOAD_REDIRECT, targetURI is the NEW URI of a
+ * top-level resource that was redirected to, and sourceURI is the
+ * ORIGINAL URI of said top-level resource.
+ * In the case of LEARN_STARTUP, targetURI should be the URI of a page
+ * that was loaded immediately after browser startup, and sourceURI should
+ * be null.
+ * @param loadContext - The nsILoadContext for the page load that we are
+ * learning about.
+ */
+ void learn(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorLearnReason reason,
+ in nsILoadContext loadContext);
+
+ /**
+ * Clear out all our learned knowledge
+ *
+ * This removes everything from our database so that any predictions begun
+ * after this completes will start from a blank slate.
+ */
+ void reset();
+};
+
+%{C++
+// Wrapper functions to make use of the predictor easier and less invasive
+class nsIChannel;
+class nsIDocument;
+class nsILoadContext;
+class nsILoadGroup;
+class nsINetworkPredictorVerifier;
+
+namespace mozilla {
+namespace net {
+
+nsresult PredictorPredict(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorPredictReason reason,
+ nsILoadContext *loadContext,
+ nsINetworkPredictorVerifier *verifier);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadContext *loadContext);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadGroup *loadGroup);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsIDocument *document);
+
+nsresult PredictorLearnRedirect(nsIURI *targetURI,
+ nsIChannel *channel,
+ nsILoadContext *loadContext);
+
+} // mozilla::net
+} // mozilla
+%}
diff --git a/netwerk/base/nsINetworkPredictorVerifier.idl b/netwerk/base/nsINetworkPredictorVerifier.idl
new file mode 100644
index 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/nsINetworkProperties.idl b/netwerk/base/nsINetworkProperties.idl
new file mode 100644
index 0000000000..30f809324f
--- /dev/null
+++ b/netwerk/base/nsINetworkProperties.idl
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/* This interface provides supplemental information
+ to that which is provided by the network info definition. It is
+ reasonable to expect it to grow.
+*/
+
+
+[scriptable, builtinclass, uuid(0af94dec-7ffc-4301-8937-766c214ac688)]
+interface nsINetworkProperties : nsISupports
+{
+ readonly attribute boolean isWifi;
+ readonly attribute unsigned long dhcpGateway;
+};
diff --git a/netwerk/base/nsINullChannel.idl b/netwerk/base/nsINullChannel.idl
new file mode 100644
index 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..0da79c18ae
--- /dev/null
+++ b/netwerk/base/nsIOService.cpp
@@ -0,0 +1,1880 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 cindent et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+
+#include "nsIOService.h"
+#include "nsIDOMNode.h"
+#include "nsIProtocolHandler.h"
+#include "nsIFileProtocolHandler.h"
+#include "nscore.h"
+#include "nsIURI.h"
+#include "prprf.h"
+#include "nsIErrorService.h"
+#include "netCore.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsXPCOM.h"
+#include "nsIProxiedProtocolHandler.h"
+#include "nsIProxyInfo.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsSecCheckWrapChannel.h"
+#include "nsSimpleNestedURI.h"
+#include "nsTArray.h"
+#include "nsIConsoleService.h"
+#include "nsIUploadChannel2.h"
+#include "nsXULAppAPI.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsICancelable.h"
+#include "nsINetworkLinkService.h"
+#include "nsPISocketTransportService.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsURLHelper.h"
+#include "nsPIDNSService.h"
+#include "nsIProtocolProxyService2.h"
+#include "MainThreadUtils.h"
+#include "nsINode.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/CaptivePortalService.h"
+#include "ReferrerPolicy.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "xpcpublic.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkManager.h"
+#include "nsINetworkInterface.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+#define PORT_PREF_PREFIX "network.security.ports."
+#define PORT_PREF(x) PORT_PREF_PREFIX x
+#define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status"
+#define OFFLINE_MIRRORS_CONNECTIVITY "network.offline-mirrors-connectivity"
+
+// Nb: these have been misnomers since bug 715770 removed the buffer cache.
+// "network.segment.count" and "network.segment.size" would be better names,
+// but the old names are still used to preserve backward compatibility.
+#define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count"
+#define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size"
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+#define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled"
+
+#define MAX_RECURSION_COUNT 50
+
+nsIOService* gIOService = nullptr;
+static bool gHasWarnedUploadChannel2;
+
+static LazyLogModule gIOServiceLog("nsIOService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gIOServiceLog, LogLevel::Debug, args)
+
+// A general port blacklist. Connections to these ports will not be allowed
+// unless the protocol overrides.
+//
+// TODO: I am sure that there are more ports to be added.
+// This cut is based on the classic mozilla codebase
+
+int16_t gBadPortList[] = {
+ 1, // tcpmux
+ 7, // echo
+ 9, // discard
+ 11, // systat
+ 13, // daytime
+ 15, // netstat
+ 17, // qotd
+ 19, // chargen
+ 20, // ftp-data
+ 21, // ftp-cntl
+ 22, // ssh
+ 23, // telnet
+ 25, // smtp
+ 37, // time
+ 42, // name
+ 43, // nicname
+ 53, // domain
+ 77, // priv-rjs
+ 79, // finger
+ 87, // ttylink
+ 95, // supdup
+ 101, // hostriame
+ 102, // iso-tsap
+ 103, // gppitnp
+ 104, // acr-nema
+ 109, // pop2
+ 110, // pop3
+ 111, // sunrpc
+ 113, // auth
+ 115, // sftp
+ 117, // uucp-path
+ 119, // nntp
+ 123, // NTP
+ 135, // loc-srv / epmap
+ 139, // netbios
+ 143, // imap2
+ 179, // BGP
+ 389, // ldap
+ 465, // smtp+ssl
+ 512, // print / exec
+ 513, // login
+ 514, // shell
+ 515, // printer
+ 526, // tempo
+ 530, // courier
+ 531, // Chat
+ 532, // netnews
+ 540, // uucp
+ 556, // remotefs
+ 563, // nntp+ssl
+ 587, //
+ 601, //
+ 636, // ldap+ssl
+ 993, // imap+ssl
+ 995, // pop3+ssl
+ 2049, // nfs
+ 3659, // apple-sasl / PasswordServer
+ 4045, // lockd
+ 6000, // x11
+ 6665, // Alternate IRC [Apple addition]
+ 6666, // Alternate IRC [Apple addition]
+ 6667, // Standard IRC [Apple addition]
+ 6668, // Alternate IRC [Apple addition]
+ 6669, // Alternate IRC [Apple addition]
+ 0, // This MUST be zero so that we can populating the array
+};
+
+static const char kProfileChangeNetTeardownTopic[] = "profile-change-net-teardown";
+static const char kProfileChangeNetRestoreTopic[] = "profile-change-net-restore";
+static const char kProfileDoChange[] = "profile-do-change";
+
+// Necko buffer defaults
+uint32_t nsIOService::gDefaultSegmentSize = 4096;
+uint32_t nsIOService::gDefaultSegmentCount = 24;
+
+bool nsIOService::sTelemetryEnabled = false;
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsIOService::nsIOService()
+ : mOffline(true)
+ , mOfflineForProfileChange(false)
+ , mManageLinkStatus(false)
+ , mConnectivity(true)
+ , mOfflineMirrorsConnectivity(true)
+ , mSettingOffline(false)
+ , mSetOfflineValue(false)
+ , mShutdown(false)
+ , mHttpHandlerAlreadyShutingDown(false)
+ , mNetworkLinkServiceInitialized(false)
+ , mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY)
+ , mNetworkNotifyChanged(true)
+ , mLastOfflineStateChange(PR_IntervalNow())
+ , mLastConnectivityChange(PR_IntervalNow())
+ , mLastNetworkLinkChange(PR_IntervalNow())
+ , mNetTearingDownStarted(0)
+{
+}
+
+nsresult
+nsIOService::Init()
+{
+ nsresult rv;
+
+ // We need to get references to the DNS service so that we can shut it
+ // down later. If we wait until the nsIOService is being shut down,
+ // GetService will fail at that point.
+
+ mDNSService = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to get DNS service");
+ return rv;
+ }
+
+ // XXX hack until xpidl supports error info directly (bug 13423)
+ nsCOMPtr<nsIErrorService> errorService = do_GetService(NS_ERRORSERVICE_CONTRACTID);
+ if (errorService) {
+ errorService->RegisterErrorStringBundle(NS_ERROR_MODULE_NETWORK, NECKO_MSGS_URL);
+ }
+ else
+ NS_WARNING("failed to get error service");
+
+ InitializeCaptivePortalService();
+
+ // setup our bad port list stuff
+ for(int i=0; gBadPortList[i]; i++)
+ mRestrictedPortList.AppendElement(gBadPortList[i]);
+
+ // Further modifications to the port list come from prefs
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ GetPrefBranch(getter_AddRefs(prefBranch));
+ if (prefBranch) {
+ prefBranch->AddObserver(PORT_PREF_PREFIX, this, true);
+ prefBranch->AddObserver(MANAGE_OFFLINE_STATUS_PREF, this, true);
+ prefBranch->AddObserver(NECKO_BUFFER_CACHE_COUNT_PREF, this, true);
+ prefBranch->AddObserver(NECKO_BUFFER_CACHE_SIZE_PREF, this, true);
+ prefBranch->AddObserver(NETWORK_NOTIFY_CHANGED_PREF, this, true);
+ prefBranch->AddObserver(NETWORK_CAPTIVE_PORTAL_PREF, this, true);
+ PrefsChanged(prefBranch);
+ }
+
+ // Register for profile change notifications
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, kProfileChangeNetTeardownTopic, true);
+ observerService->AddObserver(this, kProfileChangeNetRestoreTopic, true);
+ observerService->AddObserver(this, kProfileDoChange, true);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
+ observerService->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+ }
+ else
+ NS_WARNING("failed to get observer service");
+
+ Preferences::AddBoolVarCache(&sTelemetryEnabled, "toolkit.telemetry.enabled", false);
+ Preferences::AddBoolVarCache(&mOfflineMirrorsConnectivity, OFFLINE_MIRRORS_CONNECTIVITY, true);
+
+ gIOService = this;
+
+ InitializeNetworkLinkService();
+
+ SetOffline(false);
+
+ return NS_OK;
+}
+
+
+nsIOService::~nsIOService()
+{
+ gIOService = nullptr;
+}
+
+nsresult
+nsIOService::InitializeCaptivePortalService()
+{
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // We only initalize a captive portal service in the main process
+ return NS_OK;
+ }
+
+ mCaptivePortalService = do_GetService(NS_CAPTIVEPORTAL_CID);
+ if (mCaptivePortalService) {
+ return static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Initialize();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsIOService::InitializeSocketTransportService()
+{
+ nsresult rv = NS_OK;
+
+ if (!mSocketTransportService) {
+ mSocketTransportService = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to get socket transport service");
+ }
+ }
+
+ if (mSocketTransportService) {
+ rv = mSocketTransportService->Init();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service init failed");
+ mSocketTransportService->SetOffline(false);
+ }
+
+ return rv;
+}
+
+nsresult
+nsIOService::InitializeNetworkLinkService()
+{
+ nsresult rv = NS_OK;
+
+ if (mNetworkLinkServiceInitialized)
+ return rv;
+
+ if (!NS_IsMainThread()) {
+ NS_WARNING("Network link service should be created on main thread");
+ return NS_ERROR_FAILURE;
+ }
+
+ // go into managed mode if we can, and chrome process
+ if (XRE_IsParentProcess())
+ {
+ mNetworkLinkService = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ }
+
+ if (mNetworkLinkService) {
+ mNetworkLinkServiceInitialized = true;
+ }
+
+ // After initializing the networkLinkService, query the connectivity state
+ OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
+
+ return rv;
+}
+
+nsIOService*
+nsIOService::GetInstance() {
+ if (!gIOService) {
+ gIOService = new nsIOService();
+ if (!gIOService)
+ return nullptr;
+ NS_ADDREF(gIOService);
+
+ nsresult rv = gIOService->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(gIOService);
+ return nullptr;
+ }
+ return gIOService;
+ }
+ NS_ADDREF(gIOService);
+ return gIOService;
+}
+
+NS_IMPL_ISUPPORTS(nsIOService,
+ nsIIOService,
+ nsIIOService2,
+ nsINetUtil,
+ nsISpeculativeConnect,
+ nsIObserver,
+ nsIIOServiceInternal,
+ nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsIOService::RecheckCaptivePortal()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ if (mCaptivePortalService) {
+ mCaptivePortalService->RecheckCaptivePortal();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsIOService::RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan)
+{
+ nsresult rv;
+
+ if (!mCaptivePortalService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = newChan->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(host.BeginReading(), &prAddr) != PR_SUCCESS) {
+ // The redirect wasn't to an IP literal, so there's probably no need
+ // to trigger the captive portal detection right now. It can wait.
+ return NS_OK;
+ }
+
+ NetAddr netAddr;
+ PRNetAddrToNetAddr(&prAddr, &netAddr);
+ if (IsIPAddrLocal(&netAddr)) {
+ // Redirects to local IP addresses are probably captive portals
+ mCaptivePortalService->RecheckCaptivePortal();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsIOService::AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags,
+ nsAsyncRedirectVerifyHelper *helper)
+{
+ // If a redirect to a local network address occurs, then chances are we
+ // are in a captive portal, so we trigger a recheck.
+ RecheckCaptivePortalIfLocalRedirect(newChan);
+
+ // This is silly. I wish there was a simpler way to get at the global
+ // reference of the contentSecurityManager. But it lives in the XPCOM
+ // service registry.
+ nsCOMPtr<nsIChannelEventSink> sink =
+ do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
+ if (sink) {
+ nsresult rv = helper->DelegateOnChannelRedirect(sink, oldChan,
+ newChan, flags);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Finally, our category
+ nsCOMArray<nsIChannelEventSink> entries;
+ mChannelEventSinks.GetEntries(entries);
+ int32_t len = entries.Count();
+ for (int32_t i = 0; i < len; ++i) {
+ nsresult rv = helper->DelegateOnChannelRedirect(entries[i], oldChan,
+ newChan, flags);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsIOService::CacheProtocolHandler(const char *scheme, nsIProtocolHandler *handler)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (unsigned int i=0; i<NS_N(gScheme); i++)
+ {
+ if (!nsCRT::strcasecmp(scheme, gScheme[i]))
+ {
+ nsresult rv;
+ NS_ASSERTION(!mWeakHandler[i], "Protocol handler already cached");
+ // Make sure the handler supports weak references.
+ nsCOMPtr<nsISupportsWeakReference> factoryPtr = do_QueryInterface(handler, &rv);
+ if (!factoryPtr)
+ {
+ // Don't cache handlers that don't support weak reference as
+ // there is real danger of a circular reference.
+#ifdef DEBUG_dp
+ printf("DEBUG: %s protcol handler doesn't support weak ref. Not cached.\n", scheme);
+#endif /* DEBUG_dp */
+ return NS_ERROR_FAILURE;
+ }
+ mWeakHandler[i] = do_GetWeakReference(handler);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsIOService::GetCachedProtocolHandler(const char *scheme, nsIProtocolHandler **result, uint32_t start, uint32_t end)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ uint32_t len = end - start - 1;
+ for (unsigned int i=0; i<NS_N(gScheme); i++)
+ {
+ if (!mWeakHandler[i])
+ continue;
+
+ // handle unterminated strings
+ // start is inclusive, end is exclusive, len = end - start - 1
+ if (end ? (!nsCRT::strncasecmp(scheme + start, gScheme[i], len)
+ && gScheme[i][len] == '\0')
+ : (!nsCRT::strcasecmp(scheme, gScheme[i])))
+ {
+ return CallQueryReferent(mWeakHandler[i].get(), result);
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+static bool
+UsesExternalProtocolHandler(const char* aScheme)
+{
+ if (NS_LITERAL_CSTRING("file").Equals(aScheme) ||
+ NS_LITERAL_CSTRING("chrome").Equals(aScheme) ||
+ NS_LITERAL_CSTRING("resource").Equals(aScheme)) {
+ // Don't allow file:, chrome: or resource: URIs to be handled with
+ // nsExternalProtocolHandler, since internally we rely on being able to
+ // use and read from these URIs.
+ return false;
+ }
+
+ nsAutoCString pref("network.protocol-handler.external.");
+ pref += aScheme;
+
+ return Preferences::GetBool(pref.get(), false);
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolHandler(const char* scheme, nsIProtocolHandler* *result)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(scheme);
+ // XXX we may want to speed this up by introducing our own protocol
+ // scheme -> protocol handler mapping, avoiding the string manipulation
+ // and service manager stuff
+
+ rv = GetCachedProtocolHandler(scheme, result);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ if (!UsesExternalProtocolHandler(scheme)) {
+ nsAutoCString contractID(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX);
+ contractID += scheme;
+ ToLowerCase(contractID);
+
+ rv = CallGetService(contractID.get(), result);
+ if (NS_SUCCEEDED(rv)) {
+ CacheProtocolHandler(scheme, *result);
+ return rv;
+ }
+
+#ifdef MOZ_ENABLE_GIO
+ // check to see whether GVFS can handle this URI scheme. if it can
+ // create a nsIURI for the "scheme:", then we assume it has support for
+ // the requested protocol. otherwise, we failover to using the default
+ // protocol handler.
+
+ rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"moz-gio",
+ result);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString spec(scheme);
+ spec.Append(':');
+
+ nsIURI *uri;
+ rv = (*result)->NewURI(spec, nullptr, nullptr, &uri);
+ if (NS_SUCCEEDED(rv)) {
+ NS_RELEASE(uri);
+ return rv;
+ }
+
+ NS_RELEASE(*result);
+ }
+#endif
+ }
+
+ // Okay we don't have a protocol handler to handle this url type, so use
+ // the default protocol handler. This will cause urls to get dispatched
+ // out to the OS ('cause we can't do anything with them) when we try to
+ // read from a channel created by the default protocol handler.
+
+ rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default",
+ result);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractScheme(const nsACString &inURI, nsACString &scheme)
+{
+ return net_ExtractURLScheme(inURI, scheme);
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolFlags(const char* scheme, uint32_t *flags)
+{
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // We can't call DoGetProtocolFlags here because we don't have a URI. This
+ // API is used by (and only used by) extensions, which is why it's still
+ // around. Calling this on a scheme with dynamic flags will throw.
+ rv = handler->GetProtocolFlags(flags);
+ return rv;
+}
+
+class AutoIncrement
+{
+ public:
+ explicit AutoIncrement(uint32_t *var) : mVar(var)
+ {
+ ++*var;
+ }
+ ~AutoIncrement()
+ {
+ --*mVar;
+ }
+ private:
+ uint32_t *mVar;
+};
+
+nsresult
+nsIOService::NewURI(const nsACString &aSpec, const char *aCharset, nsIURI *aBaseURI, nsIURI **result)
+{
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ static uint32_t recursionCount = 0;
+ if (recursionCount >= MAX_RECURSION_COUNT)
+ return NS_ERROR_MALFORMED_URI;
+ AutoIncrement inc(&recursionCount);
+
+ nsAutoCString scheme;
+ nsresult rv = ExtractScheme(aSpec, scheme);
+ if (NS_FAILED(rv)) {
+ // then aSpec is relative
+ if (!aBaseURI)
+ return NS_ERROR_MALFORMED_URI;
+
+ if (!aSpec.IsEmpty() && aSpec[0] == '#') {
+ // Looks like a reference instead of a fully-specified URI.
+ // --> initialize |uri| as a clone of |aBaseURI|, with ref appended.
+ return aBaseURI->CloneWithNewRef(aSpec, result);
+ }
+
+ rv = aBaseURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // now get the handler for this scheme
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ return handler->NewURI(aSpec, aCharset, aBaseURI, result);
+}
+
+
+NS_IMETHODIMP
+nsIOService::NewFileURI(nsIFile *file, nsIURI **result)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(file);
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+
+ rv = GetProtocolHandler("file", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler( do_QueryInterface(handler, &rv) );
+ if (NS_FAILED(rv)) return rv;
+
+ return fileHandler->NewFileURI(file, result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURI2(nsIURI* aURI,
+ nsIDOMNode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType,
+ nsIChannel** result)
+{
+ return NewChannelFromURIWithProxyFlags2(aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadingNode,
+ aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ result);
+}
+
+/* ***** DEPRECATED *****
+ * please use NewChannelFromURI2 providing the right arguments for:
+ * * aLoadingNode
+ * * aLoadingPrincipal
+ * * aTriggeringPrincipal
+ * * aSecurityFlags
+ * * aContentPolicyType
+ *
+ * See nsIIoService.idl for a detailed description of those arguments
+ */
+NS_IMETHODIMP
+nsIOService::NewChannelFromURI(nsIURI *aURI, nsIChannel **result)
+{
+ NS_ASSERTION(false, "Deprecated, use NewChannelFromURI2 providing loadInfo arguments!");
+
+ const char16_t* params[] = {
+ u"nsIOService::NewChannelFromURI()",
+ u"nsIOService::NewChannelFromURI2()"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+
+ return NewChannelFromURI2(aURI,
+ nullptr, // aLoadingNode
+ nsContentUtils::GetSystemPrincipal(),
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithLoadInfo(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ return NewChannelFromURIWithProxyFlagsInternal(aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadInfo,
+ result);
+}
+
+nsresult
+nsIOService::NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t protoFlags;
+ rv = handler->DoGetProtocolFlags(aURI, &protoFlags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Ideally we are creating new channels by calling NewChannel2 (NewProxiedChannel2).
+ // Keep in mind that Addons can implement their own Protocolhandlers, hence
+ // NewChannel2() might *not* be implemented.
+ // We do not want to break those addons, therefore we first try to create a channel
+ // calling NewChannel2(); if that fails:
+ // * we fall back to creating a channel by calling NewChannel()
+ // * wrap the addon channel
+ // * and attach the loadInfo to the channel wrapper
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler);
+ if (pph) {
+ rv = pph->NewProxiedChannel2(aURI, nullptr, aProxyFlags, aProxyURI,
+ aLoadInfo, getter_AddRefs(channel));
+ // if calling NewProxiedChannel2() fails we try to fall back to
+ // creating a new proxied channel by calling NewProxiedChannel().
+ if (NS_FAILED(rv)) {
+ rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The protocol handler does not implement NewProxiedChannel2, so
+ // maybe we need to wrap the channel (see comment in MaybeWrap
+ // function).
+ channel = nsSecCheckWrapChannel::MaybeWrap(channel, aLoadInfo);
+ }
+ }
+ else {
+ rv = handler->NewChannel2(aURI, aLoadInfo, getter_AddRefs(channel));
+ // if calling newChannel2() fails we try to fall back to
+ // creating a new channel by calling NewChannel().
+ if (NS_FAILED(rv)) {
+ rv = handler->NewChannel(aURI, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The protocol handler does not implement NewChannel2, so
+ // maybe we need to wrap the channel (see comment in MaybeWrap
+ // function).
+ channel = nsSecCheckWrapChannel::MaybeWrap(channel, aLoadInfo);
+ }
+ }
+
+ // Make sure that all the individual protocolhandlers attach a loadInfo.
+ if (aLoadInfo) {
+ // make sure we have the same instance of loadInfo on the newly created channel
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ if (aLoadInfo != loadInfo) {
+ MOZ_ASSERT(false, "newly created channel must have a loadinfo attached");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (loadInfo->GetLoadingSandboxed()) {
+ channel->SetOwner(nullptr);
+ }
+ }
+
+ // Some extensions override the http protocol handler and provide their own
+ // implementation. The channels returned from that implementation doesn't
+ // seem to always implement the nsIUploadChannel2 interface, presumably
+ // because it's a new interface.
+ // Eventually we should remove this and simply require that http channels
+ // implement the new interface.
+ // See bug 529041
+ if (!gHasWarnedUploadChannel2 && scheme.EqualsLiteral("http")) {
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(channel);
+ if (!uploadChannel2) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ consoleService->LogStringMessage(NS_LITERAL_STRING(
+ "Http channel implementation doesn't support nsIUploadChannel2. An extension has supplied a non-functional http protocol handler. This will break behavior and in future releases not work at all."
+ ).get());
+ }
+ gHasWarnedUploadChannel2 = true;
+ }
+ }
+
+ channel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithProxyFlags2(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsIDOMNode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType,
+ nsIChannel** result)
+{
+ // Ideally all callers of NewChannelFromURIWithProxyFlags2 provide the
+ // necessary arguments to create a loadinfo. Keep in mind that addons
+ // might still call NewChannelFromURIWithProxyFlags() which forwards
+ // its calls to NewChannelFromURIWithProxyFlags2 using *null* values
+ // as the arguments for aLoadingNode, aLoadingPrincipal, and also
+ // aTriggeringPrincipal.
+ // We do not want to break those addons, hence we only create a Loadinfo
+ // if 'aLoadingNode' or 'aLoadingPrincipal' are provided. Note, that
+ // either aLoadingNode or aLoadingPrincipal is required to succesfully
+ // create a LoadInfo object.
+ // Except in the case of top level TYPE_DOCUMENT loads, where the
+ // loadingNode and loadingPrincipal are allowed to have null values.
+ nsCOMPtr<nsILoadInfo> loadInfo;
+
+ // TYPE_DOCUMENT loads don't require a loadingNode or principal, but other
+ // types do.
+ if (aLoadingNode || aLoadingPrincipal ||
+ aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
+ nsCOMPtr<nsINode> loadingNode(do_QueryInterface(aLoadingNode));
+ loadInfo = new LoadInfo(aLoadingPrincipal,
+ aTriggeringPrincipal,
+ loadingNode,
+ aSecurityFlags,
+ aContentPolicyType);
+ }
+ NS_ASSERTION(loadInfo, "Please pass security info when creating a channel");
+ return NewChannelFromURIWithProxyFlagsInternal(aURI,
+ aProxyURI,
+ aProxyFlags,
+ loadInfo,
+ result);
+}
+
+/* ***** DEPRECATED *****
+ * please use NewChannelFromURIWithProxyFlags2 providing the right arguments for:
+ * * aLoadingNode
+ * * aLoadingPrincipal
+ * * aTriggeringPrincipal
+ * * aSecurityFlags
+ * * aContentPolicyType
+ *
+ * See nsIIoService.idl for a detailed description of those arguments
+ */
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithProxyFlags(nsIURI *aURI,
+ nsIURI *aProxyURI,
+ uint32_t aProxyFlags,
+ nsIChannel **result)
+{
+ NS_ASSERTION(false, "Deprecated, use NewChannelFromURIWithProxyFlags2 providing loadInfo arguments!");
+
+ const char16_t* params[] = {
+ u"nsIOService::NewChannelFromURIWithProxyFlags()",
+ u"nsIOService::NewChannelFromURIWithProxyFlags2()"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+
+ return NewChannelFromURIWithProxyFlags2(aURI,
+ aProxyURI,
+ aProxyFlags,
+ nullptr, // aLoadingNode
+ nsContentUtils::GetSystemPrincipal(),
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannel2(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIDOMNode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType,
+ nsIChannel** result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ return NewChannelFromURI2(uri,
+ aLoadingNode,
+ aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ result);
+}
+
+/* ***** DEPRECATED *****
+ * please use NewChannel2 providing the right arguments for:
+ * * aLoadingNode
+ * * aLoadingPrincipal
+ * * aTriggeringPrincipal
+ * * aSecurityFlags
+ * * aContentPolicyType
+ *
+ * See nsIIoService.idl for a detailed description of those arguments
+ */
+NS_IMETHODIMP
+nsIOService::NewChannel(const nsACString &aSpec, const char *aCharset, nsIURI *aBaseURI, nsIChannel **result)
+{
+ NS_ASSERTION(false, "Deprecated, use NewChannel2 providing loadInfo arguments!");
+
+ const char16_t* params[] = {
+ u"nsIOService::NewChannel()",
+ u"nsIOService::NewChannel2()"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+
+ // Call NewChannel2 providing default arguments for the loadInfo.
+ return NewChannel2(aSpec,
+ aCharset,
+ aBaseURI,
+ nullptr, // aLoadingNode
+ nsContentUtils::GetSystemPrincipal(), // aLoadingPrincipal
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ result);
+}
+
+bool
+nsIOService::IsLinkUp()
+{
+ InitializeNetworkLinkService();
+
+ if (!mNetworkLinkService) {
+ // We cannot decide, assume the link is up
+ return true;
+ }
+
+ bool isLinkUp;
+ nsresult rv;
+ rv = mNetworkLinkService->GetIsLinkUp(&isLinkUp);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ return isLinkUp;
+}
+
+NS_IMETHODIMP
+nsIOService::GetOffline(bool *offline)
+{
+ if (mOfflineMirrorsConnectivity) {
+ *offline = mOffline || !mConnectivity;
+ } else {
+ *offline = mOffline;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::SetOffline(bool offline)
+{
+ LOG(("nsIOService::SetOffline offline=%d\n", offline));
+ // When someone wants to go online (!offline) after we got XPCOM shutdown
+ // throw ERROR_NOT_AVAILABLE to prevent return to online state.
+ if ((mShutdown || mOfflineForProfileChange) && !offline)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // SetOffline() may re-enter while it's shutting down services.
+ // If that happens, save the most recent value and it will be
+ // processed when the first SetOffline() call is done bringing
+ // down the service.
+ mSetOfflineValue = offline;
+ if (mSettingOffline) {
+ return NS_OK;
+ }
+
+ mSettingOffline = true;
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+
+ NS_ASSERTION(observerService, "The observer service should not be null");
+
+ if (XRE_IsParentProcess()) {
+ if (observerService) {
+ (void)observerService->NotifyObservers(nullptr,
+ NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, offline ?
+ u"true" :
+ u"false");
+ }
+ }
+
+ nsIIOService *subject = static_cast<nsIIOService *>(this);
+ while (mSetOfflineValue != mOffline) {
+ offline = mSetOfflineValue;
+
+ if (offline && !mOffline) {
+ NS_NAMED_LITERAL_STRING(offlineString, NS_IOSERVICE_OFFLINE);
+ mOffline = true; // indicate we're trying to shutdown
+
+ // don't care if notifications fail
+ if (observerService)
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_GOING_OFFLINE_TOPIC,
+ offlineString.get());
+
+ if (mSocketTransportService)
+ mSocketTransportService->SetOffline(true);
+
+ mLastOfflineStateChange = PR_IntervalNow();
+ if (observerService)
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ offlineString.get());
+ }
+ else if (!offline && mOffline) {
+ // go online
+ if (mDNSService) {
+ DebugOnly<nsresult> rv = mDNSService->Init();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service init failed");
+ }
+ InitializeSocketTransportService();
+ mOffline = false; // indicate success only AFTER we've
+ // brought up the services
+
+ // trigger a PAC reload when we come back online
+ if (mProxyService)
+ mProxyService->ReloadPAC();
+
+ mLastOfflineStateChange = PR_IntervalNow();
+ // don't care if notification fails
+ // Only send the ONLINE notification if there is connectivity
+ if (observerService && mConnectivity) {
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ (u"" NS_IOSERVICE_ONLINE));
+ }
+ }
+ }
+
+ // Don't notify here, as the above notifications (if used) suffice.
+ if ((mShutdown || mOfflineForProfileChange) && mOffline) {
+ // be sure to try and shutdown both (even if the first fails)...
+ // shutdown dns service first, because it has callbacks for socket transport
+ if (mDNSService) {
+ DebugOnly<nsresult> rv = mDNSService->Shutdown();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service shutdown failed");
+ }
+ if (mSocketTransportService) {
+ DebugOnly<nsresult> rv = mSocketTransportService->Shutdown(mShutdown);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service shutdown failed");
+ }
+ }
+
+ mSettingOffline = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetConnectivity(bool *aConnectivity)
+{
+ *aConnectivity = mConnectivity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::SetConnectivity(bool aConnectivity)
+{
+ LOG(("nsIOService::SetConnectivity aConnectivity=%d\n", aConnectivity));
+ // This should only be called from ContentChild to pass the connectivity
+ // value from the chrome process to the content process.
+ if (XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SetConnectivityInternal(aConnectivity);
+}
+
+nsresult
+nsIOService::SetConnectivityInternal(bool aConnectivity)
+{
+ LOG(("nsIOService::SetConnectivityInternal aConnectivity=%d\n", aConnectivity));
+ if (mConnectivity == aConnectivity) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+ mConnectivity = aConnectivity;
+
+ // This is used for PR_Connect PR_Close telemetry so it is important that
+ // we have statistic about network change event even if we are offline.
+ mLastConnectivityChange = PR_IntervalNow();
+
+ if (mCaptivePortalService) {
+ if (aConnectivity && !xpc::AreNonLocalConnectionsDisabled()) {
+ // This will also trigger a captive portal check for the new network
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start();
+ } else {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ return NS_OK;
+ }
+ // This notification sends the connectivity to the child processes
+ if (XRE_IsParentProcess()) {
+ observerService->NotifyObservers(nullptr,
+ NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC, aConnectivity ?
+ u"true" :
+ u"false");
+ }
+
+ if (mOffline) {
+ // We don't need to send any notifications if we're offline
+ return NS_OK;
+ }
+
+ if (aConnectivity) {
+ // If we were previously offline due to connectivity=false,
+ // send the ONLINE notification
+ observerService->NotifyObservers(
+ static_cast<nsIIOService *>(this),
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ (u"" NS_IOSERVICE_ONLINE));
+ } else {
+ // If we were previously online and lost connectivity
+ // send the OFFLINE notification
+ const nsLiteralString offlineString(u"" NS_IOSERVICE_OFFLINE);
+ observerService->NotifyObservers(static_cast<nsIIOService *>(this),
+ NS_IOSERVICE_GOING_OFFLINE_TOPIC,
+ offlineString.get());
+ observerService->NotifyObservers(static_cast<nsIIOService *>(this),
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ offlineString.get());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::AllowPort(int32_t inPort, const char *scheme, bool *_retval)
+{
+ int16_t port = inPort;
+ if (port == -1) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ if (port == 0) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ // first check to see if the port is in our blacklist:
+ int32_t badPortListCnt = mRestrictedPortList.Length();
+ for (int i=0; i<badPortListCnt; i++)
+ {
+ if (port == mRestrictedPortList[i])
+ {
+ *_retval = false;
+
+ // check to see if the protocol wants to override
+ if (!scheme)
+ return NS_OK;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // let the protocol handler decide
+ return handler->AllowPort(port, scheme, _retval);
+ }
+ }
+
+ *_retval = true;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void
+nsIOService::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
+{
+ if (!prefs) return;
+
+ // Look for extra ports to block
+ if (!pref || strcmp(pref, PORT_PREF("banned")) == 0)
+ ParsePortList(prefs, PORT_PREF("banned"), false);
+
+ // ...as well as previous blocks to remove.
+ if (!pref || strcmp(pref, PORT_PREF("banned.override")) == 0)
+ ParsePortList(prefs, PORT_PREF("banned.override"), true);
+
+ if (!pref || strcmp(pref, MANAGE_OFFLINE_STATUS_PREF) == 0) {
+ bool manage;
+ if (mNetworkLinkServiceInitialized &&
+ NS_SUCCEEDED(prefs->GetBoolPref(MANAGE_OFFLINE_STATUS_PREF,
+ &manage))) {
+ LOG(("nsIOService::PrefsChanged ManageOfflineStatus manage=%d\n", manage));
+ SetManageOfflineStatus(manage);
+ }
+ }
+
+ if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_COUNT_PREF) == 0) {
+ int32_t count;
+ if (NS_SUCCEEDED(prefs->GetIntPref(NECKO_BUFFER_CACHE_COUNT_PREF,
+ &count)))
+ /* check for bogus values and default if we find such a value */
+ if (count > 0)
+ gDefaultSegmentCount = count;
+ }
+
+ if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_SIZE_PREF) == 0) {
+ int32_t size;
+ if (NS_SUCCEEDED(prefs->GetIntPref(NECKO_BUFFER_CACHE_SIZE_PREF,
+ &size)))
+ /* check for bogus values and default if we find such a value
+ * the upper limit here is arbitrary. having a 1mb segment size
+ * is pretty crazy. if you remove this, consider adding some
+ * integer rollover test.
+ */
+ if (size > 0 && size < 1024*1024)
+ gDefaultSegmentSize = size;
+ NS_WARNING_ASSERTION(!(size & (size - 1)),
+ "network segment size is not a power of 2!");
+ }
+
+ if (!pref || strcmp(pref, NETWORK_NOTIFY_CHANGED_PREF) == 0) {
+ bool allow;
+ nsresult rv = prefs->GetBoolPref(NETWORK_NOTIFY_CHANGED_PREF, &allow);
+ if (NS_SUCCEEDED(rv)) {
+ mNetworkNotifyChanged = allow;
+ }
+ }
+
+ if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) {
+ bool captivePortalEnabled;
+ nsresult rv = prefs->GetBoolPref(NETWORK_CAPTIVE_PORTAL_PREF, &captivePortalEnabled);
+ if (NS_SUCCEEDED(rv) && mCaptivePortalService) {
+ if (captivePortalEnabled && !xpc::AreNonLocalConnectionsDisabled()) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start();
+ } else {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ }
+ }
+ }
+}
+
+void
+nsIOService::ParsePortList(nsIPrefBranch *prefBranch, const char *pref, bool remove)
+{
+ nsXPIDLCString portList;
+
+ // Get a pref string and chop it up into a list of ports.
+ prefBranch->GetCharPref(pref, getter_Copies(portList));
+ if (portList) {
+ nsTArray<nsCString> portListArray;
+ ParseString(portList, ',', portListArray);
+ uint32_t index;
+ for (index=0; index < portListArray.Length(); index++) {
+ portListArray[index].StripWhitespace();
+ int32_t portBegin, portEnd;
+
+ if (PR_sscanf(portListArray[index].get(), "%d-%d", &portBegin, &portEnd) == 2) {
+ if ((portBegin < 65536) && (portEnd < 65536)) {
+ int32_t curPort;
+ if (remove) {
+ for (curPort=portBegin; curPort <= portEnd; curPort++)
+ mRestrictedPortList.RemoveElement(curPort);
+ } else {
+ for (curPort=portBegin; curPort <= portEnd; curPort++)
+ mRestrictedPortList.AppendElement(curPort);
+ }
+ }
+ } else {
+ nsresult aErrorCode;
+ int32_t port = portListArray[index].ToInteger(&aErrorCode);
+ if (NS_SUCCEEDED(aErrorCode) && port < 65536) {
+ if (remove)
+ mRestrictedPortList.RemoveElement(port);
+ else
+ mRestrictedPortList.AppendElement(port);
+ }
+ }
+
+ }
+ }
+}
+
+void
+nsIOService::GetPrefBranch(nsIPrefBranch **result)
+{
+ *result = nullptr;
+ CallGetService(NS_PREFSERVICE_CONTRACTID, result);
+}
+
+class nsWakeupNotifier : public Runnable
+{
+public:
+ explicit nsWakeupNotifier(nsIIOServiceInternal *ioService)
+ :mIOService(ioService)
+ { }
+
+ NS_IMETHOD Run() override
+ {
+ return mIOService->NotifyWakeup();
+ }
+
+private:
+ virtual ~nsWakeupNotifier() { }
+ nsCOMPtr<nsIIOServiceInternal> mIOService;
+};
+
+NS_IMETHODIMP
+nsIOService::NotifyWakeup()
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+
+ NS_ASSERTION(observerService, "The observer service should not be null");
+
+ if (observerService && mNetworkNotifyChanged) {
+ (void)observerService->
+ NotifyObservers(nullptr,
+ NS_NETWORK_LINK_TOPIC,
+ (u"" NS_NETWORK_LINK_DATA_CHANGED));
+ }
+
+ RecheckCaptivePortal();
+
+ return NS_OK;
+}
+
+void
+nsIOService::SetHttpHandlerAlreadyShutingDown()
+{
+ if (!mShutdown && !mOfflineForProfileChange) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ mHttpHandlerAlreadyShutingDown = true;
+ }
+}
+
+// nsIObserver interface
+NS_IMETHODIMP
+nsIOService::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject);
+ if (prefBranch)
+ PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get());
+ } else if (!strcmp(topic, kProfileChangeNetTeardownTopic)) {
+ if (!mHttpHandlerAlreadyShutingDown) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+ if (!mOffline) {
+ mOfflineForProfileChange = true;
+ SetOffline(true);
+ }
+ } else if (!strcmp(topic, kProfileChangeNetRestoreTopic)) {
+ if (mOfflineForProfileChange) {
+ mOfflineForProfileChange = false;
+ SetOffline(false);
+ }
+ } else if (!strcmp(topic, kProfileDoChange)) {
+ if (data && NS_LITERAL_STRING("startup").Equals(data)) {
+ // Lazy initialization of network link service (see bug 620472)
+ InitializeNetworkLinkService();
+ // Set up the initilization flag regardless the actuall result.
+ // If we fail here, we will fail always on.
+ mNetworkLinkServiceInitialized = true;
+
+ // And now reflect the preference setting
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ GetPrefBranch(getter_AddRefs(prefBranch));
+ PrefsChanged(prefBranch, MANAGE_OFFLINE_STATUS_PREF);
+ }
+ } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ // Remember we passed XPCOM shutdown notification to prevent any
+ // changes of the offline status from now. We must not allow going
+ // online after this point.
+ mShutdown = true;
+
+ if (!mHttpHandlerAlreadyShutingDown && !mOfflineForProfileChange) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+
+ SetOffline(true);
+
+ if (mCaptivePortalService) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ mCaptivePortalService = nullptr;
+ }
+
+ // Break circular reference.
+ mProxyService = nullptr;
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get());
+ } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ // coming back alive from sleep
+ // this indirection brought to you by:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1152048#c19
+ nsCOMPtr<nsIRunnable> wakeupNotifier = new nsWakeupNotifier(this);
+ NS_DispatchToMainThread(wakeupNotifier);
+ }
+
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseRequestContentType(const nsACString &aTypeHeader,
+ nsACString &aCharset,
+ bool *aHadCharset,
+ nsACString &aContentType)
+{
+ net_ParseRequestContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseResponseContentType(const nsACString &aTypeHeader,
+ nsACString &aCharset,
+ bool *aHadCharset,
+ nsACString &aContentType)
+{
+ net_ParseContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ProtocolHasFlags(nsIURI *uri,
+ uint32_t flags,
+ bool *result)
+{
+ NS_ENSURE_ARG(uri);
+
+ *result = false;
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Grab the protocol flags from the URI.
+ uint32_t protocolFlags;
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = handler->DoGetProtocolFlags(uri, &protocolFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = (protocolFlags & flags) == flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::URIChainHasFlags(nsIURI *uri,
+ uint32_t flags,
+ bool *result)
+{
+ nsresult rv = ProtocolHasFlags(uri, flags, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*result) {
+ return rv;
+ }
+
+ // Dig deeper into the chain. Note that this is not a do/while loop to
+ // avoid the extra addref/release on |uri| in the common (non-nested) case.
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
+ while (nestedURI) {
+ nsCOMPtr<nsIURI> innerURI;
+ rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProtocolHasFlags(innerURI, flags, result);
+
+ if (*result) {
+ return rv;
+ }
+
+ nestedURI = do_QueryInterface(innerURI);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIOService::ToImmutableURI(nsIURI* uri, nsIURI** result)
+{
+ if (!uri) {
+ *result = nullptr;
+ return NS_OK;
+ }
+
+ nsresult rv = NS_EnsureSafeToReturn(uri, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_TryToSetImmutable(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::NewSimpleNestedURI(nsIURI* aURI, nsIURI** aResult)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsCOMPtr<nsIURI> safeURI;
+ nsresult rv = NS_EnsureSafeToReturn(aURI, getter_AddRefs(safeURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = new nsSimpleNestedURI(safeURI));
+ return *aResult ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsIOService::SetManageOfflineStatus(bool aManage)
+{
+ LOG(("nsIOService::SetManageOfflineStatus aManage=%d\n", aManage));
+ mManageLinkStatus = aManage;
+
+ // When detection is not activated, the default connectivity state is true.
+ if (!mManageLinkStatus) {
+ SetConnectivityInternal(true);
+ return NS_OK;
+ }
+
+ InitializeNetworkLinkService();
+ // If the NetworkLinkService is already initialized, it does not call
+ // OnNetworkLinkEvent. This is needed, when mManageLinkStatus goes from
+ // false to true.
+ OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetManageOfflineStatus(bool* aManage)
+{
+ *aManage = mManageLinkStatus;
+ return NS_OK;
+}
+
+// input argument 'data' is already UTF8'ed
+nsresult
+nsIOService::OnNetworkLinkEvent(const char *data)
+{
+ LOG(("nsIOService::OnNetworkLinkEvent data:%s\n", data));
+ if (!mNetworkLinkService)
+ return NS_ERROR_FAILURE;
+
+ if (mShutdown)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mManageLinkStatus) {
+ LOG(("nsIOService::OnNetworkLinkEvent mManageLinkStatus=false\n"));
+ return NS_OK;
+ }
+
+ bool isUp = true;
+ if (!strcmp(data, NS_NETWORK_LINK_DATA_CHANGED)) {
+ mLastNetworkLinkChange = PR_IntervalNow();
+ // CHANGED means UP/DOWN didn't change
+ // but the status of the captive portal may have changed.
+ RecheckCaptivePortal();
+ return NS_OK;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) {
+ isUp = false;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UP)) {
+ isUp = true;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UNKNOWN)) {
+ nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_WARNING("Unhandled network event!");
+ return NS_OK;
+ }
+
+ return SetConnectivityInternal(isUp);
+}
+
+NS_IMETHODIMP
+nsIOService::EscapeString(const nsACString& aString,
+ uint32_t aEscapeType,
+ nsACString& aResult)
+{
+ NS_ENSURE_ARG_MAX(aEscapeType, 4);
+
+ nsAutoCString stringCopy(aString);
+ nsCString result;
+
+ if (!NS_Escape(stringCopy, result, (nsEscapeMask) aEscapeType))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aResult.Assign(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::EscapeURL(const nsACString &aStr,
+ uint32_t aFlags, nsACString &aResult)
+{
+ aResult.Truncate();
+ NS_EscapeURL(aStr.BeginReading(), aStr.Length(),
+ aFlags | esc_AlwaysCopy, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::UnescapeString(const nsACString &aStr,
+ uint32_t aFlags, nsACString &aResult)
+{
+ aResult.Truncate();
+ NS_UnescapeURL(aStr.BeginReading(), aStr.Length(),
+ aFlags | esc_AlwaysCopy, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractCharsetFromContentType(const nsACString &aTypeHeader,
+ nsACString &aCharset,
+ int32_t *aCharsetStart,
+ int32_t *aCharsetEnd,
+ bool *aHadCharset)
+{
+ nsAutoCString ignored;
+ net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset,
+ aCharsetStart, aCharsetEnd);
+ if (*aHadCharset && *aCharsetStart == *aCharsetEnd) {
+ *aHadCharset = false;
+ }
+ return NS_OK;
+}
+
+// parse policyString to policy enum value (see ReferrerPolicy.h)
+NS_IMETHODIMP
+nsIOService::ParseAttributePolicyString(const nsAString& policyString,
+ uint32_t *outPolicyEnum)
+{
+ NS_ENSURE_ARG(outPolicyEnum);
+ *outPolicyEnum = (uint32_t)AttributeReferrerPolicyFromString(policyString);
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+class IOServiceProxyCallback final : public nsIProtocolProxyCallback
+{
+ ~IOServiceProxyCallback() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ IOServiceProxyCallback(nsIInterfaceRequestor *aCallbacks,
+ nsIOService *aIOService)
+ : mCallbacks(aCallbacks)
+ , mIOService(aIOService)
+ { }
+
+private:
+ RefPtr<nsIInterfaceRequestor> mCallbacks;
+ RefPtr<nsIOService> mIOService;
+};
+
+NS_IMPL_ISUPPORTS(IOServiceProxyCallback, nsIProtocolProxyCallback)
+
+NS_IMETHODIMP
+IOServiceProxyCallback::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
+ nsIProxyInfo *pi, nsresult status)
+{
+ // Checking proxy status for speculative connect
+ nsAutoCString type;
+ if (NS_SUCCEEDED(status) && pi &&
+ NS_SUCCEEDED(pi->GetType(type)) &&
+ !type.EqualsLiteral("direct")) {
+ // proxies dont do speculative connect
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = mIOService->GetProtocolHandler(scheme.get(),
+ getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsCOMPtr<nsISpeculativeConnect> speculativeHandler =
+ do_QueryInterface(handler);
+ if (!speculativeHandler)
+ return NS_OK;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ nsCOMPtr<nsIPrincipal> principal;
+ if (loadInfo) {
+ principal = loadInfo->LoadingPrincipal();
+ }
+
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+ speculativeHandler->SpeculativeAnonymousConnect2(uri, principal, mCallbacks);
+ } else {
+ speculativeHandler->SpeculativeConnect2(uri, principal, mCallbacks);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsIOService::SpeculativeConnectInternal(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks,
+ bool aAnonymous)
+{
+ if (IsNeckoChild()) {
+ ipc::URIParams params;
+ SerializeURI(aURI, params);
+ gNeckoChild->SendSpeculativeConnect(params,
+ IPC::Principal(aPrincipal),
+ aAnonymous);
+ return NS_OK;
+ }
+
+ // Check for proxy information. If there is a proxy configured then a
+ // speculative connect should not be performed because the potential
+ // reward is slim with tcp peers closely located to the browser.
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aPrincipal;
+
+ // If the principal is given, we use this prinicpal directly. Otherwise,
+ // we fallback to use the system principal.
+ if (!aPrincipal) {
+ nsCOMPtr<nsIScriptSecurityManager> secMan(
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = secMan->GetSystemPrincipal(getter_AddRefs(loadingPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // dummy channel used to create a TCP connection.
+ // we perform security checks on the *real* channel, responsible
+ // for any network loads. this real channel just checks the TCP
+ // pool if there is an available connection created by the
+ // channel we create underneath - hence it's safe to use
+ // the systemPrincipal as the loadingPrincipal for this channel.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NewChannelFromURI2(aURI,
+ nullptr, // aLoadingNode,
+ loadingPrincipal,
+ nullptr, //aTriggeringPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aAnonymous) {
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ loadFlags |= nsIRequest::LOAD_ANONYMOUS;
+ channel->SetLoadFlags(loadFlags);
+ }
+
+ nsCOMPtr<nsICancelable> cancelable;
+ RefPtr<IOServiceProxyCallback> callback =
+ new IOServiceProxyCallback(aCallbacks, this);
+ nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
+ if (pps2) {
+ return pps2->AsyncResolve2(channel, 0, callback, getter_AddRefs(cancelable));
+ }
+ return pps->AsyncResolve(channel, 0, callback, getter_AddRefs(cancelable));
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeConnect(nsIURI *aURI,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeConnect2(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeAnonymousConnect(nsIURI *aURI,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, true);
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeAnonymousConnect2(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h
new file mode 100644
index 0000000000..7ac23b7910
--- /dev/null
+++ b/netwerk/base/nsIOService.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIOService_h__
+#define nsIOService_h__
+
+#include "nsStringFwd.h"
+#include "nsIIOService2.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsWeakPtr.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsINetUtil.h"
+#include "nsIChannelEventSink.h"
+#include "nsCategoryCache.h"
+#include "nsISpeculativeConnect.h"
+#include "nsDataHashtable.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "prtime.h"
+#include "nsICaptivePortalService.h"
+
+#define NS_N(x) (sizeof(x)/sizeof(*x))
+
+// We don't want to expose this observer topic.
+// Intended internal use only for remoting offline/inline events.
+// See Bug 552829
+#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
+#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity"
+
+static const char gScheme[][sizeof("moz-safe-about")] =
+ {"chrome", "file", "http", "https", "jar", "data", "about", "moz-safe-about", "resource"};
+
+class nsINetworkLinkService;
+class nsIPrefBranch;
+class nsIProtocolProxyService2;
+class nsIProxyInfo;
+class nsPIDNSService;
+class nsPISocketTransportService;
+
+namespace mozilla {
+namespace net {
+class NeckoChild;
+class nsAsyncRedirectVerifyHelper;
+
+class nsIOService final : public nsIIOService2
+ , public nsIObserver
+ , public nsINetUtil
+ , public nsISpeculativeConnect
+ , public nsSupportsWeakReference
+ , public nsIIOServiceInternal
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIOSERVICE
+ NS_DECL_NSIIOSERVICE2
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINETUTIL
+ NS_DECL_NSISPECULATIVECONNECT
+ NS_DECL_NSIIOSERVICEINTERNAL
+
+ // Gets the singleton instance of the IO Service, creating it as needed
+ // Returns nullptr on out of memory or failure to initialize.
+ // Returns an addrefed pointer.
+ static nsIOService* GetInstance();
+
+ nsresult Init();
+ nsresult NewURI(const char* aSpec, nsIURI* aBaseURI,
+ nsIURI* *result,
+ nsIProtocolHandler* *hdlrResult);
+
+ // Called by channels before a redirect happens. This notifies the global
+ // redirect observers.
+ nsresult AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags,
+ nsAsyncRedirectVerifyHelper *helper);
+
+ bool IsOffline() { return mOffline; }
+ PRIntervalTime LastOfflineStateChange() { return mLastOfflineStateChange; }
+ PRIntervalTime LastConnectivityChange() { return mLastConnectivityChange; }
+ PRIntervalTime LastNetworkLinkChange() { return mLastNetworkLinkChange; }
+ bool IsNetTearingDown() { return mShutdown || mOfflineForProfileChange ||
+ mHttpHandlerAlreadyShutingDown; }
+ PRIntervalTime NetTearingDownStarted() { return mNetTearingDownStarted; }
+
+ // nsHttpHandler is going to call this function to inform nsIOService that network
+ // is in process of tearing down. Moving nsHttpConnectionMgr::Shutdown to nsIOService
+ // caused problems (bug 1242755) so we doing it in this way.
+ // As soon as nsIOService gets notification that it is shutdown it is going to
+ // reset mHttpHandlerAlreadyShutingDown.
+ void SetHttpHandlerAlreadyShutingDown();
+
+ bool IsLinkUp();
+
+ // Used to trigger a recheck of the captive portal status
+ nsresult RecheckCaptivePortal();
+private:
+ // These shouldn't be called directly:
+ // - construct using GetInstance
+ // - destroy using Release
+ nsIOService();
+ ~nsIOService();
+ nsresult SetConnectivityInternal(bool aConnectivity);
+
+ nsresult OnNetworkLinkEvent(const char *data);
+
+ nsresult GetCachedProtocolHandler(const char *scheme,
+ nsIProtocolHandler* *hdlrResult,
+ uint32_t start=0,
+ uint32_t end=0);
+ nsresult CacheProtocolHandler(const char *scheme,
+ nsIProtocolHandler* hdlr);
+
+ nsresult InitializeCaptivePortalService();
+ nsresult RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan);
+
+ // Prefs wrangling
+ void PrefsChanged(nsIPrefBranch *prefs, const char *pref = nullptr);
+ void GetPrefBranch(nsIPrefBranch **);
+ void ParsePortList(nsIPrefBranch *prefBranch, const char *pref, bool remove);
+
+ nsresult InitializeSocketTransportService();
+ nsresult InitializeNetworkLinkService();
+
+ // consolidated helper function
+ void LookupProxyInfo(nsIURI *aURI, nsIURI *aProxyURI, uint32_t aProxyFlags,
+ nsCString *aScheme, nsIProxyInfo **outPI);
+
+ nsresult NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result);
+
+ nsresult SpeculativeConnectInternal(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks,
+ bool aAnonymous);
+
+private:
+ bool mOffline;
+ mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange;
+ bool mManageLinkStatus;
+ bool mConnectivity;
+ // If true, the connectivity state will be mirrored by IOService.offline
+ // meaning if !mConnectivity, GetOffline() will return true
+ bool mOfflineMirrorsConnectivity;
+
+ // Used to handle SetOffline() reentrancy. See the comment in
+ // SetOffline() for more details.
+ bool mSettingOffline;
+ bool mSetOfflineValue;
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mShutdown;
+ mozilla::Atomic<bool, mozilla::Relaxed> mHttpHandlerAlreadyShutingDown;
+
+ nsCOMPtr<nsPISocketTransportService> mSocketTransportService;
+ nsCOMPtr<nsPIDNSService> mDNSService;
+ nsCOMPtr<nsIProtocolProxyService2> mProxyService;
+ nsCOMPtr<nsICaptivePortalService> mCaptivePortalService;
+ nsCOMPtr<nsINetworkLinkService> mNetworkLinkService;
+ bool mNetworkLinkServiceInitialized;
+
+ // Cached protocol handlers, only accessed on the main thread
+ nsWeakPtr mWeakHandler[NS_N(gScheme)];
+
+ // cached categories
+ nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
+
+ nsTArray<int32_t> mRestrictedPortList;
+
+ bool mNetworkNotifyChanged;
+
+ static bool sTelemetryEnabled;
+
+ // These timestamps are needed for collecting telemetry on PR_Connect,
+ // PR_ConnectContinue and PR_Close blocking time. If we spend very long
+ // time in any of these functions we want to know if and what network
+ // change has happened shortly before.
+ mozilla::Atomic<PRIntervalTime> mLastOfflineStateChange;
+ mozilla::Atomic<PRIntervalTime> mLastConnectivityChange;
+ mozilla::Atomic<PRIntervalTime> mLastNetworkLinkChange;
+
+ // Time a network tearing down started.
+ mozilla::Atomic<PRIntervalTime> mNetTearingDownStarted;
+public:
+ // Used for all default buffer sizes that necko allocates.
+ static uint32_t gDefaultSegmentSize;
+ static uint32_t gDefaultSegmentCount;
+};
+
+/**
+ * Reference to the IO service singleton. May be null.
+ */
+extern nsIOService* gIOService;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsIOService_h__
diff --git a/netwerk/base/nsIParentChannel.idl b/netwerk/base/nsIParentChannel.idl
new file mode 100644
index 0000000000..2858bb95ea
--- /dev/null
+++ b/netwerk/base/nsIParentChannel.idl
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsITabParent;
+
+%{C++
+namespace mozilla {
+namespace net {
+class HttpChannelParentListener;
+}
+}
+%}
+
+[ptr] native HttpChannelParentListener(mozilla::net::HttpChannelParentListener);
+
+/**
+ * Implemented by chrome side of IPC protocols.
+ */
+
+[scriptable, uuid(e0fc4801-6030-4653-a59f-1fb282bd1a04)]
+interface nsIParentChannel : nsIStreamListener
+{
+ /**
+ * Called to set the HttpChannelParentListener object (optional).
+ */
+ [noscript] void setParentListener(in HttpChannelParentListener listener);
+
+ /**
+ * Called to notify the HttpChannelChild that tracking protection was
+ * disabled for this load.
+ */
+ [noscript] void notifyTrackingProtectionDisabled();
+
+ /**
+ * Called to invoke deletion of the IPC protocol.
+ */
+ void delete();
+};
diff --git a/netwerk/base/nsIParentRedirectingChannel.idl b/netwerk/base/nsIParentRedirectingChannel.idl
new file mode 100644
index 0000000000..df37a0131b
--- /dev/null
+++ b/netwerk/base/nsIParentRedirectingChannel.idl
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIParentChannel.idl"
+
+interface nsITabParent;
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+/**
+ * Implemented by chrome side of IPC protocols that support redirect responses.
+ */
+
+[scriptable, uuid(3ed1d288-5324-46ee-8a98-33ac37d1080b)]
+interface nsIParentRedirectingChannel : nsIParentChannel
+{
+ /**
+ * Called when the channel got a response that redirects it to a different
+ * URI. The implementation is responsible for calling the redirect observers
+ * on the child process and provide the decision result to the callback.
+ *
+ * @param newChannelId
+ * id of the redirect channel obtained from nsIRedirectChannelRegistrar.
+ * @param newURI
+ * the URI we redirect to
+ * @param callback
+ * redirect result callback, usage is compatible with how
+ * nsIChannelEventSink defines it
+ */
+ void startRedirect(in uint32_t newChannelId,
+ in nsIChannel newChannel,
+ in uint32_t redirectFlags,
+ in nsIAsyncVerifyRedirectCallback callback);
+
+ /**
+ * Called after we are done with redirecting process and we know if to
+ * redirect or not. Forward the redirect result to the child process. From
+ * that moment the nsIParentChannel implementation expects it will be
+ * forwarded all notifications from the 'real' channel.
+ *
+ * Primarilly used by HttpChannelParentListener::OnRedirectResult and kept
+ * as mActiveChannel and mRedirectChannel in that class.
+ */
+ void completeRedirect(in boolean succeeded);
+};
diff --git a/netwerk/base/nsIPermission.idl b/netwerk/base/nsIPermission.idl
new file mode 100644
index 0000000000..c5ddd90fe5
--- /dev/null
+++ b/netwerk/base/nsIPermission.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+[scriptable, uuid(bb409a51-2371-4fea-9dc9-b7286a458b8c)]
+/**
+ * This interface defines a "permission" object,
+ * used to specify allowed/blocked objects from
+ * user-specified sites (cookies, images etc).
+ */
+
+interface nsIPermission : nsISupports
+{
+ /**
+ * The principal for which this permission applies.
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * a case-sensitive ASCII string, indicating the type of permission
+ * (e.g., "cookie", "image", etc).
+ * This string is specified by the consumer when adding a permission
+ * via nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute ACString type;
+
+ /**
+ * The permission (see nsIPermissionManager.idl for allowed values)
+ */
+ readonly attribute uint32_t capability;
+
+ /**
+ * The expiration type of the permission (session, time-based or none).
+ * Constants are EXPIRE_*, defined in nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute uint32_t expireType;
+
+ /**
+ * The expiration time of the permission (milliseconds since Jan 1 1970
+ * 0:00:00).
+ */
+ readonly attribute int64_t expireTime;
+
+ /**
+ * Test whether a principal would be affected by this permission.
+ *
+ * @param principal the principal to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matches(in nsIPrincipal principal,
+ in boolean exactHost);
+
+ /**
+ * Test whether a URI would be affected by this permission.
+ * NOTE: This performs matches with default origin attribute values.
+ *
+ * @param uri the uri to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matchesURI(in nsIURI uri,
+ in boolean exactHost);
+};
diff --git a/netwerk/base/nsIPermissionManager.idl b/netwerk/base/nsIPermissionManager.idl
new file mode 100644
index 0000000000..b61817d4cf
--- /dev/null
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains an interface to the Permission Manager,
+ * used to persistenly store permissions for different object types (cookies,
+ * images etc) on a site-by-site basis.
+ *
+ * This service broadcasts the following notification when the permission list
+ * is changed:
+ *
+ * topic : "perm-changed" (PERM_CHANGE_NOTIFICATION)
+ * broadcast whenever the permission list changes in some way. there
+ * are four possible data strings for this notification; one
+ * notification will be broadcast for each change, and will involve
+ * a single permission.
+ * subject: an nsIPermission interface pointer representing the permission object
+ * that changed.
+ * data : "deleted"
+ * a permission was deleted. the subject is the deleted permission.
+ * "added"
+ * a permission was added. the subject is the added permission.
+ * "changed"
+ * a permission was changed. the subject is the new permission.
+ * "cleared"
+ * the entire permission list was cleared. the subject is null.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIObserver;
+interface nsIPrincipal;
+interface mozIDOMWindow;
+interface nsIPermission;
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(4dcb3851-eba2-4e42-b236-82d2596fca22)]
+interface nsIPermissionManager : nsISupports
+{
+ /**
+ * Predefined return values for the testPermission method and for
+ * the permission param of the add method
+ * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+ * default permission when no entry is found for a host, and
+ * should not be used by consumers to indicate otherwise.
+ */
+ const uint32_t UNKNOWN_ACTION = 0;
+ const uint32_t ALLOW_ACTION = 1;
+ const uint32_t DENY_ACTION = 2;
+ const uint32_t PROMPT_ACTION = 3;
+
+ /**
+ * Predefined expiration types for permissions. Permissions can be permanent
+ * (never expire), expire at the end of the session, or expire at a specified
+ * time. Permissions that expire at the end of a session may also have a
+ * specified expiration time.
+ */
+ const uint32_t EXPIRE_NEVER = 0;
+ const uint32_t EXPIRE_SESSION = 1;
+ const uint32_t EXPIRE_TIME = 2;
+
+ /**
+ * Add permission information for a given URI and permission type. This
+ * operation will cause the type string to be registered if it does not
+ * currently exist. If a permission already exists for a given type, it
+ * will be modified.
+ *
+ * @param uri the uri to add the permission for
+ * @param type a case-sensitive ASCII string, identifying the consumer.
+ * Consumers should choose this string to be unique, with
+ * respect to other consumers.
+ * @param permission an integer representing the desired action (e.g. allow
+ * or deny). The interpretation of this number is up to the
+ * consumer, and may represent different actions for different
+ * types. Consumers may use one of the enumerated permission
+ * actions defined above, for convenience.
+ * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+ * default permission when no entry is found for a host, and
+ * should not be used by consumers to indicate otherwise.
+ * @param expiretype a constant defining whether this permission should
+ * never expire (EXPIRE_NEVER), expire at the end of the
+ * session (EXPIRE_SESSION), or expire at a specified time
+ * (EXPIRE_TIME).
+ * @param expiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since Jan 1 1970 0:00:00).
+ */
+ void add(in nsIURI uri,
+ in string type,
+ in uint32_t permission,
+ [optional] in uint32_t expireType,
+ [optional] in int64_t expireTime);
+
+ /**
+ * Get all custom permissions for a given URI. This will return
+ * an enumerator of all permissions which are not set to default
+ * and which belong to the matching prinicpal of the given URI.
+ *
+ * @param uri the URI to get all permissions for
+ */
+ nsISimpleEnumerator getAllForURI(in nsIURI uri);
+
+ /**
+ * Add permission information for a given principal.
+ * It is internally calling the other add() method using the nsIURI from the
+ * principal.
+ * Passing a system principal will be a no-op because they will always be
+ * granted permissions.
+ */
+ void addFromPrincipal(in nsIPrincipal principal, in string typed,
+ in uint32_t permission,
+ [optional] in uint32_t expireType,
+ [optional] in int64_t expireTime);
+
+ /**
+ * Remove permission information for a given URI and permission type. This will
+ * remove the permission for the entire host described by the uri, acting as the
+ * opposite operation to the add() method.
+ *
+ * @param uri the uri to remove the permission for
+ * @param type a case-sensitive ASCII string, identifying the consumer.
+ * The type must have been previously registered using the
+ * add() method.
+ */
+ void remove(in nsIURI uri,
+ in string type);
+
+ /**
+ * Remove permission information for a given principal.
+ * This is internally calling remove() with the host from the principal's URI.
+ * Passing system principal will be a no-op because we never add them to the
+ * database.
+ */
+ void removeFromPrincipal(in nsIPrincipal principal, in string type);
+
+ /**
+ * Remove the given permission from the permission manager.
+ *
+ * @param perm a permission obtained from the permission manager.
+ */
+ void removePermission(in nsIPermission perm);
+
+ /**
+ * Clear permission information for all websites.
+ */
+ void removeAll();
+
+ /**
+ * Clear all permission information added since the specified time.
+ */
+ void removeAllSince(in int64_t since);
+
+ /**
+ * Test whether a website has permission to perform the given action.
+ * @param uri the uri to be tested
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testPermission(in nsIURI uri,
+ in string type);
+
+ /**
+ * Test whether the principal has the permission to perform a given action.
+ * System principals will always have permissions granted.
+ */
+ uint32_t testPermissionFromPrincipal(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Test whether the principal associated with the window's document has the
+ * permission to perform a given action. System principals will always
+ * have permissions granted.
+ */
+ uint32_t testPermissionFromWindow(in mozIDOMWindow window,
+ in string type);
+
+ /**
+ * Test whether a website has permission to perform the given action.
+ * This requires an exact hostname match, subdomains are not a match.
+ * @param uri the uri to be tested
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testExactPermission(in nsIURI uri,
+ in string type);
+
+ /**
+ * See testExactPermission() above.
+ * System principals will always have permissions granted.
+ */
+ uint32_t testExactPermissionFromPrincipal(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Test whether a website has permission to perform the given action
+ * ignoring active sessions.
+ * System principals will always have permissions granted.
+ *
+ * @param principal the principal
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testExactPermanentPermission(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Get the permission object associated with the given principal and action.
+ * @param principal The principal
+ * @param type A case-sensitive ASCII string identifying the consumer
+ * @param exactHost If true, only the specific host will be matched,
+ * @see testExactPermission. If false, subdomains will
+ * also be searched, @see testPermission.
+ * @returns The matching permission object, or null if no matching object
+ * was found. No matching object is equivalent to UNKNOWN_ACTION.
+ * @note Clients in general should prefer the test* methods unless they
+ * need to know the specific stored details.
+ * @note This method will always return null for the system principal.
+ */
+ nsIPermission getPermissionObject(in nsIPrincipal principal,
+ in string type,
+ in boolean exactHost);
+
+ /**
+ * Allows enumeration of all stored permissions
+ * @return an nsISimpleEnumerator interface that allows access to
+ * nsIPermission objects
+ */
+ readonly attribute nsISimpleEnumerator enumerator;
+
+ /**
+ * Remove all permissions that will match the origin pattern.
+ */
+ void removePermissionsWithAttributes(in DOMString patternAsJSON);
+
+ /**
+ * If the current permission is set to expire, reset the expiration time. If
+ * there is no permission or the current permission does not expire, this
+ * method will silently return.
+ *
+ * @param sessionExpiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since
+ * Jan 1 1970 0:00:00), if it is currently
+ * EXPIRE_SESSION.
+ * @param sessionExpiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since
+ * Jan 1 1970 0:00:00), if it is currently
+ * EXPIRE_TIME.
+ */
+ void updateExpireTime(in nsIPrincipal principal,
+ in string type,
+ in boolean exactHost,
+ in uint64_t sessionExpireTime,
+ in uint64_t persistentExpireTime);
+
+ /**
+ * Remove all current permission settings and get permission settings from
+ * chrome process.
+ */
+ void refreshPermission();
+};
+
+%{ C++
+#define NS_PERMISSIONMANAGER_CONTRACTID "@mozilla.org/permissionmanager;1"
+
+#define PERM_CHANGE_NOTIFICATION "perm-changed"
+%}
diff --git a/netwerk/base/nsIPrivateBrowsingChannel.idl b/netwerk/base/nsIPrivateBrowsingChannel.idl
new file mode 100644
index 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..68f8bf0597
--- /dev/null
+++ b/netwerk/base/nsIProgressEventSink.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIRequest;
+
+/**
+ * nsIProgressEventSink
+ *
+ * This interface is used to asynchronously convey channel status and progress
+ * information that is generally not critical to the processing of the channel.
+ * The information is intended to be displayed to the user in some meaningful
+ * way.
+ *
+ * An implementation of this interface can be passed to a channel via the
+ * channel's notificationCallbacks attribute. See nsIChannel for more info.
+ *
+ * The channel will begin passing notifications to the progress event sink
+ * after its asyncOpen method has been called. Notifications will cease once
+ * the channel calls its listener's onStopRequest method or once the channel
+ * is canceled (via nsIRequest::cancel).
+ *
+ * NOTE: This interface is actually not specific to channels and may be used
+ * with other implementations of nsIRequest.
+ */
+[scriptable, uuid(87d55fba-cb7e-4f38-84c1-5c6c2b2a55e9)]
+interface nsIProgressEventSink : nsISupports
+{
+ /**
+ * Called to notify the event sink that progress has occurred for the
+ * given request.
+ *
+ * @param aRequest
+ * the request being observed (may QI to nsIChannel).
+ * @param aContext
+ * if aRequest is a channel, then this parameter is the listener
+ * context passed to nsIChannel::asyncOpen.
+ * @param aProgress
+ * numeric value in the range 0 to aProgressMax indicating the
+ * number of bytes transfered thus far.
+ * @param aProgressMax
+ * numeric value indicating maximum number of bytes that will be
+ * transfered (or -1 if total is unknown).
+ */
+ void onProgress(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in long long aProgress,
+ in long long aProgressMax);
+
+ /**
+ * Called to notify the event sink with a status message for the given
+ * request.
+ *
+ * @param aRequest
+ * the request being observed (may QI to nsIChannel).
+ * @param aContext
+ * if aRequest is a channel, then this parameter is the listener
+ * context passed to nsIChannel::asyncOpen.
+ * @param aStatus
+ * status code (not necessarily an error code) indicating the
+ * state of the channel (usually the state of the underlying
+ * transport). see nsISocketTransport for socket specific status
+ * codes.
+ * @param aStatusArg
+ * status code argument to be used with the string bundle service
+ * to convert the status message into localized, human readable
+ * text. the meaning of this parameter is specific to the value
+ * of the status code. for socket status codes, this parameter
+ * indicates the host:port associated with the status code.
+ */
+ void onStatus(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsresult aStatus,
+ in wstring aStatusArg);
+
+};
diff --git a/netwerk/base/nsIPrompt.idl b/netwerk/base/nsIPrompt.idl
new file mode 100644
index 0000000000..e68c1a21c1
--- /dev/null
+++ b/netwerk/base/nsIPrompt.idl
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is the prompt interface which can be used without knowlege of a
+ * parent window. The parentage is hidden by the GetInterface though
+ * which it is gotten. This interface is identical to nsIPromptService
+ * but without the parent nsIDOMWindow parameter. See nsIPromptService
+ * for all documentation.
+ *
+ * Accesskeys can be attached to buttons and checkboxes by inserting
+ * an & before the accesskey character. For a real &, use && instead.
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a63f70c0-148b-11d3-9333-00104ba0fd40)]
+interface nsIPrompt : nsISupports
+{
+ void alert(in wstring dialogTitle,
+ in wstring text);
+
+ void alertCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean confirm(in wstring dialogTitle,
+ in wstring text);
+
+ boolean confirmCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ const unsigned long BUTTON_POS_0 = 1;
+ const unsigned long BUTTON_POS_1 = 1 << 8;
+ const unsigned long BUTTON_POS_2 = 1 << 16;
+
+ const unsigned long BUTTON_TITLE_OK = 1;
+ const unsigned long BUTTON_TITLE_CANCEL = 2;
+ const unsigned long BUTTON_TITLE_YES = 3;
+ const unsigned long BUTTON_TITLE_NO = 4;
+ const unsigned long BUTTON_TITLE_SAVE = 5;
+ const unsigned long BUTTON_TITLE_DONT_SAVE = 6;
+ const unsigned long BUTTON_TITLE_REVERT = 7;
+
+ const unsigned long BUTTON_TITLE_IS_STRING = 127;
+
+ const unsigned long BUTTON_POS_0_DEFAULT = 0 << 24;
+ const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24;
+ const unsigned long BUTTON_POS_2_DEFAULT = 2 << 24;
+
+ /* used for security dialogs, buttons are initially disabled */
+ const unsigned long BUTTON_DELAY_ENABLE = 1 << 26;
+
+ const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) +
+ (BUTTON_TITLE_CANCEL * BUTTON_POS_1);
+ const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
+ (BUTTON_TITLE_NO * BUTTON_POS_1);
+
+ int32_t confirmEx(in wstring dialogTitle,
+ in wstring text,
+ in unsigned long buttonFlags,
+ in wstring button0Title,
+ in wstring button1Title,
+ in wstring button2Title,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ inout wstring value,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring password,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring username,
+ inout wstring password,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean select(in wstring dialogTitle,
+ in wstring text,
+ in uint32_t count,
+ [array, size_is(count)] in wstring selectList,
+ out long outSelection);
+};
diff --git a/netwerk/base/nsIProtocolHandler.idl b/netwerk/base/nsIProtocolHandler.idl
new file mode 100644
index 0000000000..95dc00ecef
--- /dev/null
+++ b/netwerk/base/nsIProtocolHandler.idl
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "nsCOMPtr.h"
+%}
+
+interface nsIURI;
+interface nsIChannel;
+interface nsILoadInfo;
+
+/**
+ * nsIProtocolHandlerWithDynamicFlags
+ *
+ * Protocols that wish to return different flags depending on the URI should
+ * implement this interface.
+ */
+[scriptable, builtinclass, uuid(65a8e823-0591-4fc0-a56a-03265e0a4ce8)]
+interface nsIProtocolHandlerWithDynamicFlags : nsISupports
+{
+ /*
+ * Returns protocol flags for the given URI, which may be different from the
+ * flags for another URI of the same scheme.
+ */
+ unsigned long getFlagsForURI(in nsIURI aURI);
+};
+
+/**
+ * nsIProtocolHandler
+ */
+[scriptable, uuid(a87210e6-7c8c-41f7-864d-df809015193e)]
+interface nsIProtocolHandler : nsISupports
+{
+ /**
+ * The scheme of this protocol (e.g., "file").
+ */
+ readonly attribute ACString scheme;
+
+ /**
+ * The default port is the port that this protocol normally uses.
+ * If a port does not make sense for the protocol (e.g., "about:")
+ * then -1 will be returned.
+ */
+ readonly attribute long defaultPort;
+
+ /**
+ * Returns the protocol specific flags (see flag definitions below).
+ */
+ readonly attribute unsigned long protocolFlags;
+
+%{C++
+ // Helper method to get the protocol flags in the right way.
+ nsresult DoGetProtocolFlags(nsIURI* aURI, uint32_t* aFlags)
+ {
+ nsCOMPtr<nsIProtocolHandlerWithDynamicFlags> dh = do_QueryInterface(this);
+ return dh ? dh->GetFlagsForURI(aURI, aFlags) : GetProtocolFlags(aFlags);
+ }
+%}
+
+ /**
+ * Makes a URI object that is suitable for loading by this protocol,
+ * where the URI string is given as an UTF-8 string. The caller may
+ * provide the charset from which the URI string originated, so that
+ * the URI string can be translated back to that charset (if necessary)
+ * before communicating with, for example, the origin server of the URI
+ * string. (Many servers do not support UTF-8 IRIs at the present time,
+ * so we must be careful about tracking the native charset of the origin
+ * server.)
+ *
+ * @param aSpec - the URI string in UTF-8 encoding. depending
+ * on the protocol implementation, unicode character
+ * sequences may or may not be %xx escaped.
+ * @param aOriginCharset - the charset of the document from which this URI
+ * string originated. this corresponds to the
+ * charset that should be used when communicating
+ * this URI to an origin server, for example. if
+ * null, then UTF-8 encoding is assumed (i.e.,
+ * no charset transformation from aSpec).
+ * @param aBaseURI - if null, aSpec must specify an absolute URI.
+ * otherwise, aSpec may be resolved relative
+ * to aBaseURI, depending on the protocol.
+ * If the protocol has no concept of relative
+ * URI aBaseURI will simply be ignored.
+ */
+ nsIURI newURI(in AUTF8String aSpec,
+ [optional] in string aOriginCharset,
+ [optional] in nsIURI aBaseURI);
+
+ /**
+ * Constructs a new channel from the given URI for this protocol handler and
+ * sets the loadInfo for the constructed channel.
+ */
+ nsIChannel newChannel2(in nsIURI aURI, in nsILoadInfo aLoadinfo);
+
+ /**
+ * Constructs a new channel from the given URI for this protocol handler.
+ */
+ nsIChannel newChannel(in nsIURI aURI);
+
+ /**
+ * Allows a protocol to override blacklisted ports.
+ *
+ * This method will be called when there is an attempt to connect to a port
+ * that is blacklisted. For example, for most protocols, port 25 (Simple Mail
+ * Transfer) is banned. When a URI containing this "known-to-do-bad-things"
+ * port number is encountered, this function will be called to ask if the
+ * protocol handler wants to override the ban.
+ */
+ boolean allowPort(in long port, in string scheme);
+
+
+ /**************************************************************************
+ * Constants for the protocol flags (the first is the default mask, the
+ * others are deviations):
+ *
+ * NOTE: Implementation must ignore any flags they do not understand.
+ */
+
+ /**
+ * standard full URI with authority component and concept of relative
+ * URIs (http, ftp, ...)
+ */
+ const unsigned long URI_STD = 0;
+
+ /**
+ * no concept of relative URIs (about, javascript, finger, ...)
+ */
+ const unsigned long URI_NORELATIVE = (1<<0);
+
+ /**
+ * no authority component (file, ...)
+ */
+ const unsigned long URI_NOAUTH = (1<<1);
+
+ /**
+ * This protocol handler can be proxied via a proxy (socks or http)
+ * (e.g., irc, smtp, http, etc.). If the protocol supports transparent
+ * proxying, the handler should implement nsIProxiedProtocolHandler.
+ *
+ * If it supports only HTTP proxying, then it need not support
+ * nsIProxiedProtocolHandler, but should instead set the ALLOWS_PROXY_HTTP
+ * flag (see below).
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+ const unsigned long ALLOWS_PROXY = (1<<2);
+
+ /**
+ * This protocol handler can be proxied using a http proxy (e.g., http,
+ * ftp, etc.). nsIIOService::newChannelFromURI will feed URIs from this
+ * protocol handler to the HTTP protocol handler instead. This flag is
+ * ignored if ALLOWS_PROXY is not set.
+ */
+ const unsigned long ALLOWS_PROXY_HTTP = (1<<3);
+
+ /**
+ * The URIs for this protocol have no inherent security context, so
+ * documents loaded via this protocol should inherit the security context
+ * from the document that loads them.
+ */
+ const unsigned long URI_INHERITS_SECURITY_CONTEXT = (1<<4);
+
+ /**
+ * "Automatic" loads that would replace the document (e.g. <meta> refresh,
+ * certain types of XLinks, possibly other loads that the application
+ * decides are not user triggered) are not allowed if the originating (NOT
+ * the target) URI has this protocol flag. Note that the decision as to
+ * what constitutes an "automatic" load is made externally, by the caller
+ * of nsIScriptSecurityManager::CheckLoadURI. See documentation for that
+ * method for more information.
+ *
+ * A typical protocol that might want to set this flag is a protocol that
+ * shows highly untrusted content in a viewing area that the user expects
+ * to have a lot of control over, such as an e-mail reader.
+ */
+ const unsigned long URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT = (1<<5);
+
+ /**
+ * +-------------------------------------------------------------------+
+ * | |
+ * | ALL PROTOCOL HANDLERS MUST SET ONE OF THE FOLLOWING FIVE FLAGS. |
+ * | |
+ * +-------------------------------------------------------------------+
+ *
+ * These flags are used to determine who is allowed to load URIs for this
+ * protocol. Note that if a URI is nested, only the flags for the
+ * innermost URI matter. See nsINestedURI.
+ *
+ * If none of these five flags are set, the URI must be treated as if it
+ * had the URI_LOADABLE_BY_ANYONE flag set, for compatibility with protocol
+ * handlers written against Gecko 1.8 or earlier. In this case, there may
+ * be run-time warning messages indicating that a "default insecure"
+ * assumption is being made. At some point in the futures (Mozilla 2.0,
+ * most likely), these warnings will become errors.
+ */
+
+ /**
+ * The URIs for this protocol can be loaded by anyone. For example, any
+ * website should be allowed to trigger a load of a URI for this protocol.
+ * Web-safe protocols like "http" should set this flag.
+ */
+ const unsigned long URI_LOADABLE_BY_ANYONE = (1<<6);
+
+ /**
+ * The URIs for this protocol are UNSAFE if loaded by untrusted (web)
+ * content and may only be loaded by privileged code (for example, code
+ * which has the system principal). Various internal protocols should set
+ * this flag.
+ */
+ const unsigned long URI_DANGEROUS_TO_LOAD = (1<<7);
+
+ /**
+ * The URIs for this protocol point to resources that are part of the
+ * application's user interface. There are cases when such resources may
+ * be made accessible to untrusted content such as web pages, so this is
+ * less restrictive than URI_DANGEROUS_TO_LOAD but more restrictive than
+ * URI_LOADABLE_BY_ANYONE. See the documentation for
+ * nsIScriptSecurityManager::CheckLoadURI.
+ */
+ const unsigned long URI_IS_UI_RESOURCE = (1<<8);
+
+ /**
+ * Loading of URIs for this protocol from other origins should only be
+ * allowed if those origins should have access to the local filesystem.
+ * It's up to the application to decide what origins should have such
+ * access. Protocols like "file" that point to local data should set this
+ * flag.
+ */
+ const unsigned long URI_IS_LOCAL_FILE = (1<<9);
+
+ /**
+ * The URIs for this protocol can be loaded only by callers with a
+ * principal that subsumes this uri. For example, privileged code and
+ * websites that are same origin as this uri.
+ */
+ const unsigned long URI_LOADABLE_BY_SUBSUMERS = (1<<10);
+
+ /**
+ * Channels using this protocol never call OnDataAvailable
+ * on the listener passed to AsyncOpen and they therefore
+ * do not return any data that we can use.
+ */
+ const unsigned long URI_DOES_NOT_RETURN_DATA = (1<<11);
+
+ /**
+ * URIs for this protocol are considered to be local resources. This could
+ * be a local file (URI_IS_LOCAL_FILE), a UI resource (URI_IS_UI_RESOURCE),
+ * or something else that would not hit the network.
+ */
+ const unsigned long URI_IS_LOCAL_RESOURCE = (1<<12);
+
+ /**
+ * URIs for this protocol execute script when they are opened.
+ */
+ const unsigned long URI_OPENING_EXECUTES_SCRIPT = (1<<13);
+
+ /**
+ * Loading channels from this protocol has side-effects that make
+ * it unsuitable for saving to a local file.
+ */
+ const unsigned long URI_NON_PERSISTABLE = (1<<14);
+
+ /**
+ * This protocol handler forbids accessing cookies e.g. for mail related
+ * protocols.
+ */
+ const unsigned long URI_FORBIDS_COOKIE_ACCESS = (1<<15);
+
+ /**
+ * URIs for this protocol require the webapps permission on the principal
+ * when opening URIs for a different domain. See bug#773886
+ */
+ const unsigned long URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM = (1<<16);
+
+ /**
+ * Channels for this protocol don't need to spin the event loop to handle
+ * Open() and reads on the resulting stream.
+ */
+ const unsigned long URI_SYNC_LOAD_IS_OK = (1<<17);
+
+ /**
+ * URI is secure to load in an https page and should not be blocked
+ * by nsMixedContentBlocker
+ */
+ const unsigned long URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT = (1<<18);
+
+ /**
+ * This URI may be fetched and the contents are visible to anyone. This is
+ * semantically equivalent to the resource being served with all-access CORS
+ * headers.
+ */
+ const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 19);
+
+ /**
+ * If this flag is set, then the origin for this protocol is the full URI
+ * spec, not just the scheme + host + port.
+ */
+ const unsigned long ORIGIN_IS_FULL_SPEC = (1 << 20);
+
+ /**
+ * If this flag is set, the URI does not always allow content using the same
+ * protocol to link to it.
+ */
+ const unsigned long URI_SCHEME_NOT_SELF_LINKABLE = (1 << 21);
+};
+
+%{C++
+/**
+ * Protocol handlers are registered with XPCOM under the following CONTRACTID prefix:
+ */
+#define NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "@mozilla.org/network/protocol;1?name="
+/**
+ * For example, "@mozilla.org/network/protocol;1?name=http"
+ */
+
+#define IS_ORIGIN_IS_FULL_SPEC_DEFINED 1
+%}
diff --git a/netwerk/base/nsIProtocolProxyCallback.idl b/netwerk/base/nsIProtocolProxyCallback.idl
new file mode 100644
index 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..8798a49b4a
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyFilter.idl
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIProtocolProxyService;
+interface nsIProxyInfo;
+interface nsIURI;
+
+/**
+ * This interface is used to apply filters to the proxies selected for a given
+ * URI. Use nsIProtocolProxyService::registerFilter to hook up instances of
+ * this interface. See also nsIProtocolProxyChannelFilter.
+ */
+[scriptable, uuid(f424abd3-32b4-456c-9f45-b7e3376cb0d1)]
+interface nsIProtocolProxyFilter : nsISupports
+{
+ /**
+ * This method is called to apply proxy filter rules for the given URI
+ * and proxy object (or list of proxy objects).
+ *
+ * @param aProxyService
+ * A reference to the Protocol Proxy Service. This is passed so that
+ * implementations may easily access methods such as newProxyInfo.
+ * @param aURI
+ * The URI for which these proxy settings apply.
+ * @param aProxy
+ * The proxy (or list of proxies) that would be used by default for
+ * the given URI. This may be null.
+ *
+ * @return The proxy (or list of proxies) that should be used in place of
+ * aProxy. This can be just be aProxy if the filter chooses not to
+ * modify the proxy. It can also be null to indicate that a direct
+ * connection should be used. Use aProxyService.newProxyInfo to
+ * construct nsIProxyInfo objects.
+ */
+ nsIProxyInfo applyFilter(in nsIProtocolProxyService aProxyService,
+ in nsIURI aURI, in nsIProxyInfo aProxy);
+};
+
+/**
+ * This interface is used to apply filters to the proxies selected for a given
+ * channel. Use nsIProtocolProxyService::registerChannelFilter to hook up instances of
+ * this interface. See also nsIProtocolProxyFilter.
+ */
+[scriptable, uuid(245b0880-82c5-4e6e-be6d-bc586aa55a90)]
+interface nsIProtocolProxyChannelFilter : nsISupports
+{
+ /**
+ * This method is called to apply proxy filter rules for the given channel
+ * and proxy object (or list of proxy objects).
+ *
+ * @param aProxyService
+ * A reference to the Protocol Proxy Service. This is passed so that
+ * implementations may easily access methods such as newProxyInfo.
+ * @param aChannel
+ * The channel for which these proxy settings apply.
+ * @param aProxy
+ * The proxy (or list of proxies) that would be used by default for
+ * the given channel. This may be null.
+ *
+ * @return The proxy (or list of proxies) that should be used in place of
+ * aProxy. This can be just be aProxy if the filter chooses not to
+ * modify the proxy. It can also be null to indicate that a direct
+ * connection should be used. Use aProxyService.newProxyInfo to
+ * construct nsIProxyInfo objects.
+ */
+ nsIProxyInfo applyFilter(in nsIProtocolProxyService aProxyService,
+ in nsIChannel aChannel, in nsIProxyInfo aProxy);
+};
diff --git a/netwerk/base/nsIProtocolProxyService.idl b/netwerk/base/nsIProtocolProxyService.idl
new file mode 100644
index 0000000000..7bbaa8d859
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService.idl
@@ -0,0 +1,281 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICancelable;
+interface nsIProtocolProxyCallback;
+interface nsIProtocolProxyFilter;
+interface nsIProtocolProxyChannelFilter;
+interface nsIProxyInfo;
+interface nsIChannel;
+interface nsIURI;
+
+/**
+ * nsIProtocolProxyService provides methods to access information about
+ * various network proxies.
+ */
+[scriptable, uuid(ef57c8b6-e09d-4cd4-9222-2a5d2402e15d)]
+interface nsIProtocolProxyService : nsISupports
+{
+ /** Flag 1 << 0 is unused **/
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to prefer the SOCKS proxy
+ * to HTTP ones.
+ */
+ const unsigned long RESOLVE_PREFER_SOCKS_PROXY = 1 << 1;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to not analyze the uri's
+ * scheme specific proxy. When this flag is set the main HTTP proxy is the
+ * preferred one.
+ *
+ * NOTE: if RESOLVE_PREFER_SOCKS_PROXY is set then the SOCKS proxy is
+ * the preferred one.
+ *
+ * NOTE: if RESOLVE_PREFER_HTTPS_PROXY is set then the HTTPS proxy
+ * is the preferred one.
+ */
+ const unsigned long RESOLVE_IGNORE_URI_SCHEME = 1 << 2;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to prefer the HTTPS proxy
+ * to the others HTTP ones.
+ *
+ * NOTE: RESOLVE_PREFER_SOCKS_PROXY takes precedence over this flag.
+ *
+ * NOTE: This flag implies RESOLVE_IGNORE_URI_SCHEME.
+ */
+ const unsigned long RESOLVE_PREFER_HTTPS_PROXY =
+ (1 << 3) | RESOLVE_IGNORE_URI_SCHEME;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to that all methods will be tunneled via
+ * CONNECT through the http proxy.
+ */
+ const unsigned long RESOLVE_ALWAYS_TUNNEL = (1 << 4);
+
+ /**
+ * This method returns via callback a nsIProxyInfo instance that identifies
+ * a proxy to be used for the given channel. Otherwise, this method returns
+ * null indicating that a direct connection should be used.
+ *
+ * @param aChannelOrURI
+ * The channel for which a proxy is to be found, or, if no channel is
+ * available, a URI indicating the same. This method will return
+ * NS_ERROR_NOINTERFACE if this argument isn't either an nsIURI or an
+ * nsIChannel.
+ * @param aFlags
+ * A bit-wise combination of the RESOLVE_ flags defined above. Pass
+ * 0 to specify the default behavior. Any additional bits that do
+ * not correspond to a RESOLVE_ flag are reserved for future use.
+ * @param aCallback
+ * The object to be notified when the result is available.
+ *
+ * @return An object that can be used to cancel the asychronous operation.
+ * If canceled, the cancelation status (aReason) will be forwarded
+ * to the callback's onProxyAvailable method via the aStatus param.
+ *
+ * NOTE: If this proxy is unavailable, getFailoverForProxy may be called
+ * to determine the correct secondary proxy to be used.
+ *
+ * NOTE: If the protocol handler for the given URI supports
+ * nsIProxiedProtocolHandler, then the nsIProxyInfo instance returned from
+ * resolve may be passed to the newProxiedChannel method to create a
+ * nsIChannel to the given URI that uses the specified proxy.
+ *
+ * NOTE: However, if the nsIProxyInfo type is "http", then it means that
+ * the given URI should be loaded using the HTTP protocol handler, which
+ * also supports nsIProxiedProtocolHandler.
+ *
+ * @see nsIProxiedProtocolHandler::newProxiedChannel
+ */
+ nsICancelable asyncResolve(in nsISupports aChannelOrURI, in unsigned long aFlags,
+ in nsIProtocolProxyCallback aCallback);
+
+ /**
+ * This method may be called to construct a nsIProxyInfo instance from
+ * the given parameters. This method may be useful in conjunction with
+ * nsISocketTransportService::createTransport for creating, for example,
+ * a SOCKS connection.
+ *
+ * @param aType
+ * The proxy type. This is a string value that identifies the proxy
+ * type. Standard values include:
+ * "http" - specifies a HTTP proxy
+ * "https" - specifies HTTP proxying over TLS connection to proxy
+ * "socks" - specifies a SOCKS version 5 proxy
+ * "socks4" - specifies a SOCKS version 4 proxy
+ * "direct" - specifies a direct connection (useful for failover)
+ * The type name is case-insensitive. Other string values may be
+ * possible, and new types may be defined by a future version of
+ * this interface.
+ * @param aHost
+ * The proxy hostname or IP address.
+ * @param aPort
+ * The proxy port.
+ * @param aFlags
+ * Flags associated with this connection. See nsIProxyInfo.idl
+ * for currently defined flags.
+ * @param aFailoverTimeout
+ * Specifies the length of time (in seconds) to ignore this proxy if
+ * this proxy fails. Pass UINT32_MAX to specify the default
+ * timeout value, causing nsIProxyInfo::failoverTimeout to be
+ * assigned the default value.
+ * @param aFailoverProxy
+ * Specifies the next proxy to try if this proxy fails. This
+ * parameter may be null.
+ */
+ nsIProxyInfo newProxyInfo(in ACString aType, in AUTF8String aHost,
+ in long aPort, in unsigned long aFlags,
+ in unsigned long aFailoverTimeout,
+ in nsIProxyInfo aFailoverProxy);
+
+ /**
+ * This method may be called to construct a nsIProxyInfo instance for
+ * with the specified username and password.
+ * Currently implemented for SOCKS proxies only.
+ * @param aType
+ * The proxy type. This is a string value that identifies the proxy
+ * type. Standard values include:
+ * "socks" - specifies a SOCKS version 5 proxy
+ * "socks4" - specifies a SOCKS version 4 proxy
+ * The type name is case-insensitive. Other string values may be
+ * possible, and new types may be defined by a future version of
+ * this interface.
+ * @param aHost
+ * The proxy hostname or IP address.
+ * @param aPort
+ * The proxy port.
+ * @param aUsername
+ * The proxy username
+ * @param aPassword
+ * The proxy password
+ * @param aFlags
+ * Flags associated with this connection. See nsIProxyInfo.idl
+ * for currently defined flags.
+ * @param aFailoverTimeout
+ * Specifies the length of time (in seconds) to ignore this proxy if
+ * this proxy fails. Pass UINT32_MAX to specify the default
+ * timeout value, causing nsIProxyInfo::failoverTimeout to be
+ * assigned the default value.
+ * @param aFailoverProxy
+ * Specifies the next proxy to try if this proxy fails. This
+ * parameter may be null.
+ */
+ nsIProxyInfo newProxyInfoWithAuth(in ACString aType, in AUTF8String aHost,
+ in long aPort,
+ in ACString aUsername, in ACString aPassword,
+ in unsigned long aFlags,
+ in unsigned long aFailoverTimeout,
+ in nsIProxyInfo aFailoverProxy);
+
+ /**
+ * If the proxy identified by aProxyInfo is unavailable for some reason,
+ * this method may be called to access an alternate proxy that may be used
+ * instead. As a side-effect, this method may affect future result values
+ * from resolve/asyncResolve as well as from getFailoverForProxy.
+ *
+ * @param aProxyInfo
+ * The proxy that was unavailable.
+ * @param aURI
+ * The URI that was originally passed to resolve/asyncResolve.
+ * @param aReason
+ * The error code corresponding to the proxy failure. This value
+ * may be used to tune the delay before this proxy is used again.
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if there is no alternate proxy available.
+ */
+ nsIProxyInfo getFailoverForProxy(in nsIProxyInfo aProxyInfo,
+ in nsIURI aURI,
+ in nsresult aReason);
+
+ /**
+ * This method may be used to register a proxy filter instance. Each proxy
+ * filter is registered with an associated position that determines the
+ * order in which the filters are applied (starting from position 0). When
+ * resolve/asyncResolve is called, it generates a list of proxies for the
+ * given URI, and then it applies the proxy filters. The filters have the
+ * opportunity to modify the list of proxies.
+ *
+ * If two filters register for the same position, then the filters will be
+ * visited in the order in which they were registered.
+ *
+ * If the filter is already registered, then its position will be updated.
+ *
+ * After filters have been run, any disabled or disallowed proxies will be
+ * removed from the list. A proxy is disabled if it had previously failed-
+ * over to another proxy (see getFailoverForProxy). A proxy is disallowed,
+ * for example, if it is a HTTP proxy and the nsIProtocolHandler for the
+ * queried URI does not permit proxying via HTTP.
+ *
+ * If a nsIProtocolHandler disallows all proxying, then filters will never
+ * have a chance to intercept proxy requests for such URLs.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyFilter instance to be registered.
+ * @param aPosition
+ * The position of the filter.
+ *
+ * NOTE: It is possible to construct filters that compete with one another
+ * in undesirable ways. This API does not attempt to protect against such
+ * problems. It is recommended that any extensions that choose to call
+ * this method make their position value configurable at runtime (perhaps
+ * via the preferences service).
+ */
+ void registerFilter(in nsIProtocolProxyFilter aFilter,
+ in unsigned long aPosition);
+
+ /**
+ * Similar to registerFilter, but accepts an nsIProtocolProxyChannelFilter,
+ * which selects proxies according to channel rather than URI.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyChannelFilter instance to be registered.
+ * @param aPosition
+ * The position of the filter.
+ */
+ void registerChannelFilter(in nsIProtocolProxyChannelFilter aFilter,
+ in unsigned long aPosition);
+
+ /**
+ * This method may be used to unregister a proxy filter instance. All
+ * filters will be automatically unregistered at XPCOM shutdown.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyFilter instance to be unregistered.
+ */
+ void unregisterFilter(in nsIProtocolProxyFilter aFilter);
+
+ /**
+ * This method may be used to unregister a proxy channel filter instance. All
+ * filters will be automatically unregistered at XPCOM shutdown.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyChannelFilter instance to be unregistered.
+ */
+ void unregisterChannelFilter(in nsIProtocolProxyChannelFilter aFilter);
+
+ /**
+ * These values correspond to the possible integer values for the
+ * network.proxy.type preference.
+ */
+ const unsigned long PROXYCONFIG_DIRECT = 0;
+ const unsigned long PROXYCONFIG_MANUAL = 1;
+ const unsigned long PROXYCONFIG_PAC = 2;
+ const unsigned long PROXYCONFIG_WPAD = 4;
+ const unsigned long PROXYCONFIG_SYSTEM = 5;
+
+ /**
+ * This attribute specifies the current type of proxy configuration.
+ */
+ readonly attribute unsigned long proxyConfigType;
+};
diff --git a/netwerk/base/nsIProtocolProxyService2.idl b/netwerk/base/nsIProtocolProxyService2.idl
new file mode 100644
index 0000000000..6cd125e586
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService2.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolProxyService.idl"
+
+/**
+ * An extension of nsIProtocolProxyService
+ */
+[scriptable, uuid(b2e5b2c0-e21e-4845-b336-be6d60a38951)]
+interface nsIProtocolProxyService2 : nsIProtocolProxyService
+{
+ /**
+ * Call this method to cause the PAC file (if any is configured) to be
+ * reloaded. The PAC file is loaded asynchronously.
+ */
+ void reloadPAC();
+
+ /**
+ * This method is identical to asyncResolve() except:
+ * - it only accepts an nsIChannel, not an nsIURI;
+ * - it may execute the callback function immediately (i.e from the stack
+ * of asyncResolve2()) if it is immediately ready to run.
+ * The nsICancelable return value will be null in that case.
+ */
+ nsICancelable asyncResolve2(in nsIChannel aChannel, in unsigned long aFlags,
+ in nsIProtocolProxyCallback aCallback);
+};
diff --git a/netwerk/base/nsIProxiedChannel.idl b/netwerk/base/nsIProxiedChannel.idl
new file mode 100644
index 0000000000..69fc34650c
--- /dev/null
+++ b/netwerk/base/nsIProxiedChannel.idl
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIProxyInfo;
+
+/**
+ * An interface for accessing the proxy info that a channel was
+ * constructed with.
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+[scriptable, uuid(6238f134-8c3f-4354-958f-dfd9d54a4446)]
+interface nsIProxiedChannel : nsISupports
+{
+ /**
+ * Gets the proxy info the channel was constructed with. null or a
+ * proxyInfo with type "direct" mean no proxy.
+ *
+ * The returned proxy info must not be modified.
+ */
+ readonly attribute nsIProxyInfo proxyInfo;
+};
+
+
diff --git a/netwerk/base/nsIProxiedProtocolHandler.idl b/netwerk/base/nsIProxiedProtocolHandler.idl
new file mode 100644
index 0000000000..604177c124
--- /dev/null
+++ b/netwerk/base/nsIProxiedProtocolHandler.idl
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIChannel;
+interface nsIURI;
+interface nsIProxyInfo;
+interface nsILoadInfo;
+
+[scriptable, uuid(3756047a-fa2b-4b45-9948-3b5f8fc375e7)]
+interface nsIProxiedProtocolHandler : nsIProtocolHandler
+{
+ /** Create a new channel with the given proxyInfo
+ *
+ * @param uri the channel uri
+ * @param proxyInfo any proxy information that has already been determined,
+ * or null if channel should later determine the proxy on its own using
+ * proxyResolveFlags/proxyURI
+ * @param proxyResolveFlags used if the proxy is later determined
+ * from nsIProtocolProxyService::asyncResolve
+ * @param proxyURI used if the proxy is later determined from
+ * nsIProtocolProxyService::asyncResolve with this as the proxyURI name.
+ * Generally this is the same as uri (or null which has the same
+ * effect), except in the case of websockets which wants to bootstrap
+ * to an http:// channel but make its proxy determination based on
+ * a ws:// uri.
+ * @param aLoadInfo used to evaluate who initated the resource request.
+ */
+ nsIChannel newProxiedChannel2(in nsIURI uri, in nsIProxyInfo proxyInfo,
+ in unsigned long proxyResolveFlags,
+ in nsIURI proxyURI,
+ in nsILoadInfo aLoadInfo);
+
+ /** Create a new channel with the given proxyInfo
+ *
+ * @param uri the channel uri
+ * @param proxyInfo any proxy information that has already been determined,
+ * or null if channel should later determine the proxy on its own using
+ * proxyResolveFlags/proxyURI
+ * @param proxyResolveFlags used if the proxy is later determined
+ * from nsIProtocolProxyService::asyncResolve
+ * @param proxyURI used if the proxy is later determined from
+ * nsIProtocolProxyService::asyncResolve with this as the proxyURI name.
+ * Generally this is the same as uri (or null which has the same
+ * effect), except in the case of websockets which wants to bootstrap
+ * to an http:// channel but make its proxy determination based on
+ * a ws:// uri.
+ */
+ nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo,
+ in unsigned long proxyResolveFlags,
+ in nsIURI proxyURI);
+};
diff --git a/netwerk/base/nsIProxyInfo.idl b/netwerk/base/nsIProxyInfo.idl
new file mode 100644
index 0000000000..46ab438b2a
--- /dev/null
+++ b/netwerk/base/nsIProxyInfo.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface identifies a proxy server.
+ */
+[scriptable, uuid(63fff172-2564-4138-96c6-3ae7d245fbed)]
+interface nsIProxyInfo : nsISupports
+{
+ /**
+ * This attribute specifies the hostname of the proxy server.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * This attribute specifies the port number of the proxy server.
+ */
+ readonly attribute long port;
+
+ /**
+ * This attribute specifies the type of the proxy server as an ASCII string.
+ *
+ * Some special values for this attribute include (but are not limited to)
+ * the following:
+ * "http" HTTP proxy (or SSL CONNECT for HTTPS)
+ * "https" HTTP proxying over TLS connection to proxy
+ * "socks" SOCKS v5 proxy
+ * "socks4" SOCKS v4 proxy
+ * "direct" no proxy
+ * "unknown" unknown proxy (see nsIProtocolProxyService::resolve)
+ *
+ * A future version of this interface may define additional types.
+ */
+ readonly attribute ACString type;
+
+ /**
+ * This attribute specifies flags that modify the proxy type. The value of
+ * this attribute is the bit-wise combination of the Proxy Flags defined
+ * below. Any undefined bits are reserved for future use.
+ */
+ readonly attribute unsigned long flags;
+
+ /**
+ * This attribute specifies flags that were used by nsIProxyProtocolService when
+ * creating this ProxyInfo element.
+ */
+ readonly attribute unsigned long resolveFlags;
+
+ /**
+ * Specifies a proxy username.
+ */
+ readonly attribute ACString username;
+
+ /**
+ * Specifies a proxy password.
+ */
+ readonly attribute ACString password;
+
+ /**
+ * This attribute specifies the failover timeout in seconds for this proxy.
+ * If a nsIProxyInfo is reported as failed via nsIProtocolProxyService::
+ * getFailoverForProxy, then the failed proxy will not be used again for this
+ * many seconds.
+ */
+ readonly attribute unsigned long failoverTimeout;
+
+ /**
+ * This attribute specifies the proxy to failover to when this proxy fails.
+ */
+ attribute nsIProxyInfo failoverProxy;
+
+
+ /****************************************************************************
+ * The following "Proxy Flags" may be bit-wise combined to construct the
+ * flags attribute defined on this interface. All unspecified bits are
+ * reserved for future use.
+ */
+
+ /**
+ * This flag is set if the proxy is to perform name resolution itself. If
+ * this is the case, the hostname is used in some fashion, and we shouldn't
+ * do any form of DNS lookup ourselves.
+ */
+ const unsigned short TRANSPARENT_PROXY_RESOLVES_HOST = 1 << 0;
+};
diff --git a/netwerk/base/nsIRandomGenerator.idl b/netwerk/base/nsIRandomGenerator.idl
new file mode 100644
index 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..c5cce0b1ca
--- /dev/null
+++ b/netwerk/base/nsIRedirectChannelRegistrar.idl
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIParentChannel;
+
+/**
+ * Used on the chrome process as a service to join channel implementation
+ * and parent IPC protocol side under a unique id. Provides this way a generic
+ * communication while redirecting to various protocols.
+ *
+ * See also nsIChildChannel and nsIParentChannel.
+ */
+
+[scriptable, uuid (efa36ea2-5b07-46fc-9534-a5acb8b77b72)]
+interface nsIRedirectChannelRegistrar : nsISupports
+{
+ /**
+ * Register the redirect target channel and obtain a unique ID for that
+ * channel.
+ *
+ * Primarily used in HttpChannelParentListener::AsyncOnChannelRedirect to get
+ * a channel id sent to the HttpChannelChild being redirected.
+ */
+ uint32_t registerChannel(in nsIChannel channel);
+
+ /**
+ * First, search for the channel registered under the id. If found return
+ * it. Then, register under the same id the parent side of IPC protocol
+ * to let it be later grabbed back by the originator of the redirect and
+ * notifications from the real channel could be forwarded to this parent
+ * channel.
+ *
+ * Primarily used in parent side of an IPC protocol implementation
+ * in reaction to nsIChildChannel.connectParent(id) called from the child
+ * process.
+ */
+ nsIChannel linkChannels(in uint32_t id, in nsIParentChannel channel);
+
+ /**
+ * Returns back the channel previously registered under the ID with
+ * registerChannel method.
+ *
+ * Primarilly used in chrome IPC side of protocols when attaching a redirect
+ * target channel to an existing 'real' channel implementation.
+ */
+ nsIChannel getRegisteredChannel(in uint32_t id);
+
+ /**
+ * Returns the stream listener that shall be attached to the redirect target
+ * channel, all notification from the redirect target channel will be
+ * forwarded to this stream listener.
+ *
+ * Primarilly used in HttpChannelParentListener::OnRedirectResult callback
+ * to grab the created parent side of the channel and forward notifications
+ * to it.
+ */
+ nsIParentChannel getParentChannel(in uint32_t id);
+
+ /**
+ * To not force all channel implementations to support weak reference
+ * consumers of this service must ensure release of registered channels them
+ * self. This releases both the real and parent channel registered under
+ * the id.
+ *
+ * Primarilly used in HttpChannelParentListener::OnRedirectResult callback.
+ */
+ void deregisterChannels(in uint32_t id);
+};
diff --git a/netwerk/base/nsIRedirectResultListener.idl b/netwerk/base/nsIRedirectResultListener.idl
new file mode 100644
index 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..5446121522
--- /dev/null
+++ b/netwerk/base/nsIRequest.idl
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILoadGroup;
+
+typedef unsigned long nsLoadFlags;
+
+/**
+ * nsIRequest
+ */
+[scriptable, uuid(ef6bfbd2-fd46-48d8-96b7-9f8f0fd387fe)]
+interface nsIRequest : nsISupports
+{
+ /**
+ * The name of the request. Often this is the URI of the request.
+ */
+ readonly attribute AUTF8String name;
+
+ /**
+ * Indicates whether the request is pending. nsIRequest::isPending is
+ * true when there is an outstanding asynchronous event that will make
+ * the request no longer be pending. Requests do not necessarily start
+ * out pending; in some cases, requests have to be explicitly initiated
+ * (e.g. nsIChannel implementations are only pending once asyncOpen
+ * returns successfully).
+ *
+ * Requests can become pending multiple times during their lifetime.
+ *
+ * @return TRUE if the request has yet to reach completion.
+ * @return FALSE if the request has reached completion (e.g., after
+ * OnStopRequest has fired).
+ * @note Suspended requests are still considered pending.
+ */
+ boolean isPending();
+
+ /**
+ * The error status associated with the request.
+ */
+ readonly attribute nsresult status;
+
+ /**
+ * Cancels the current request. This will close any open input or
+ * output streams and terminate any async requests. Users should
+ * normally pass NS_BINDING_ABORTED, although other errors may also
+ * be passed. The error passed in will become the value of the
+ * status attribute.
+ *
+ * Implementations must not send any notifications (e.g. via
+ * nsIRequestObserver) synchronously from this function. Similarly,
+ * removal from the load group (if any) must also happen asynchronously.
+ *
+ * Requests that use nsIStreamListener must not call onDataAvailable
+ * anymore after cancel has been called.
+ *
+ * @param aStatus the reason for canceling this request.
+ *
+ * NOTE: most nsIRequest implementations expect aStatus to be a
+ * failure code; however, some implementations may allow aStatus to
+ * be a success code such as NS_OK. In general, aStatus should be
+ * a failure code.
+ */
+ void cancel(in nsresult aStatus);
+
+ /**
+ * Suspends the current request. This may have the effect of closing
+ * any underlying transport (in order to free up resources), although
+ * any open streams remain logically opened and will continue delivering
+ * data when the transport is resumed.
+ *
+ * Calling cancel() on a suspended request must not send any
+ * notifications (such as onstopRequest) until the request is resumed.
+ *
+ * NOTE: some implementations are unable to immediately suspend, and
+ * may continue to deliver events already posted to an event queue. In
+ * general, callers should be capable of handling events even after
+ * suspending a request.
+ */
+ void suspend();
+
+ /**
+ * Resumes the current request. This may have the effect of re-opening
+ * any underlying transport and will resume the delivery of data to
+ * any open streams.
+ */
+ void resume();
+
+ /**
+ * The load group of this request. While pending, the request is a
+ * member of the load group. It is the responsibility of the request
+ * to implement this policy.
+ */
+ attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load flags of this request. Bits 0-15 are reserved.
+ *
+ * When added to a load group, this request's load flags are merged with
+ * the load flags of the load group.
+ */
+ attribute nsLoadFlags loadFlags;
+
+ /**
+ * Mask defining the bits reserved for nsIRequest LoadFlags
+ */
+ const unsigned long LOAD_REQUESTMASK = 0xFFFF;
+
+ /**************************************************************************
+ * Listed below are the various load flags which may be or'd together.
+ */
+
+ /**
+ * No special load flags:
+ */
+ const unsigned long LOAD_NORMAL = 0;
+
+ /**
+ * Do not deliver status notifications to the nsIProgressEventSink and
+ * do not block the loadgroup from completing (should this load belong to one).
+ * Note: Progress notifications will still be delivered.
+ */
+ const unsigned long LOAD_BACKGROUND = 1 << 0;
+
+ /**************************************************************************
+ * The following flags control the flow of data into the cache.
+ */
+
+ /**
+ * This flag prevents loading of the request with an HTTP pipeline.
+ * Generally this is because the resource is expected to take a
+ * while to load and may cause head of line blocking problems.
+ */
+ const unsigned long INHIBIT_PIPELINE = 1 << 6;
+
+ /**
+ * This flag prevents caching of any kind. It does not, however, prevent
+ * cached content from being used to satisfy this request.
+ */
+ const unsigned long INHIBIT_CACHING = 1 << 7;
+
+ /**
+ * This flag prevents caching on disk (or other persistent media), which
+ * may be needed to preserve privacy.
+ */
+ const unsigned long INHIBIT_PERSISTENT_CACHING = 1 << 8;
+
+ /**************************************************************************
+ * The following flags control what happens when the cache contains data
+ * that could perhaps satisfy this request. They are listed in descending
+ * order of precidence.
+ */
+
+ /**
+ * Force an end-to-end download of content data from the origin server.
+ * This flag is used for a shift-reload.
+ */
+ const unsigned long LOAD_BYPASS_CACHE = 1 << 9;
+
+ /**
+ * Attempt to force a load from the cache, bypassing ALL validation logic
+ * (note: this is stronger than VALIDATE_NEVER, which still validates for
+ * certain conditions).
+ *
+ * If the resource is not present in cache, it will be loaded from the
+ * network. Combine this flag with LOAD_ONLY_FROM_CACHE if you wish to
+ * perform cache-only loads without validation checks.
+ *
+ * This flag is used when browsing via history. It is not recommended for
+ * normal browsing as it may likely violate reasonable assumptions made by
+ * the server and confuse users.
+ */
+ const unsigned long LOAD_FROM_CACHE = 1 << 10;
+
+ /**
+ * The following flags control the frequency of cached content validation
+ * when neither LOAD_BYPASS_CACHE or LOAD_FROM_CACHE are set. By default,
+ * cached content is automatically validated if necessary before reuse.
+ *
+ * VALIDATE_ALWAYS forces validation of any cached content independent of
+ * its expiration time (unless it is https with Cache-Control: immutable)
+ *
+ * VALIDATE_NEVER disables validation of cached content, unless it arrived
+ * with the "Cache: no-store" header, or arrived via HTTPS with the
+ * "Cache: no-cache" header.
+ *
+ * VALIDATE_ONCE_PER_SESSION disables validation of expired content,
+ * provided it has already been validated (at least once) since the start
+ * of this session.
+ *
+ * NOTE TO IMPLEMENTORS:
+ * These flags are intended for normal browsing, and they should therefore
+ * not apply to content that must be validated before each use. Consider,
+ * for example, a HTTP response with a "Cache-control: no-cache" header.
+ * According to RFC2616, this response must be validated before it can
+ * be taken from a cache. Breaking this requirement could result in
+ * incorrect and potentially undesirable side-effects.
+ */
+ const unsigned long VALIDATE_ALWAYS = 1 << 11;
+ const unsigned long VALIDATE_NEVER = 1 << 12;
+ const unsigned long VALIDATE_ONCE_PER_SESSION = 1 << 13;
+
+ /**
+ * When set, this flag indicates that no user-specific data should be added
+ * to the request when opened. This means that things like authorization
+ * tokens or cookie headers should not be added.
+ */
+ const unsigned long LOAD_ANONYMOUS = 1 << 14;
+
+ /**
+ * When set, this flag indicates that caches of network connections,
+ * particularly HTTP persistent connections, should not be used.
+ */
+ const unsigned long LOAD_FRESH_CONNECTION = 1 << 15;
+};
diff --git a/netwerk/base/nsIRequestContext.idl b/netwerk/base/nsIRequestContext.idl
new file mode 100644
index 0000000000..b40ba7d18f
--- /dev/null
+++ b/netwerk/base/nsIRequestContext.idl
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+// Forward-declare mozilla::net::SpdyPushCache
+namespace mozilla {
+namespace net {
+class SpdyPushCache;
+}
+}
+%}
+
+[ptr] native SpdyPushCachePtr(mozilla::net::SpdyPushCache);
+
+/**
+ * The nsIRequestContext is used to maintain state about connections
+ * that are in some way associated with each other (often by being part
+ * of the same load group) and how they interact with blocking items like
+ * HEAD css/js loads.
+ *
+ * This used to be known as nsILoadGroupConnectionInfo and nsISchedulingContext.
+ */
+[scriptable, uuid(658e3e6e-8633-4b1a-8d66-fa9f72293e63)]
+interface nsIRequestContext : nsISupports
+{
+ /**
+ * A unique identifier for this request context
+ */
+ [noscript] readonly attribute nsID ID;
+
+ /**
+ * Number of active blocking transactions associated with this context
+ */
+ readonly attribute unsigned long blockingTransactionCount;
+
+ /**
+ * Increase the number of active blocking transactions associated
+ * with this context by one.
+ */
+ void addBlockingTransaction();
+
+ /**
+ * Decrease the number of active blocking transactions associated
+ * with this context by one. The return value is the number of remaining
+ * blockers.
+ */
+ unsigned long removeBlockingTransaction();
+
+ /**
+ * This gives out a weak pointer to the push cache.
+ * The nsIRequestContext implementation owns the cache
+ * and will destroy it when overwritten or when the context
+ * ends.
+ */
+ [noscript] attribute SpdyPushCachePtr spdyPushCache;
+
+ /**
+ * This holds a cached value of the user agent override.
+ */
+ [noscript] attribute ACString userAgentOverride;
+};
+
+/**
+ * The nsIRequestContextService is how anyone gets access to a request
+ * context when they haven't been explicitly given a strong reference to an
+ * existing one. It is responsible for creating and handing out strong
+ * references to nsIRequestContexts, but only keeps weak references itself.
+ * The shared request context will go away once no one else is keeping a
+ * reference to it. If you ask for a request context that has no one else
+ * holding a reference to it, you'll get a brand new request context. Anyone
+ * who asks for the same request context while you're holding a reference
+ * will get a reference to the same request context you have.
+ */
+[uuid(7fcbf4da-d828-4acc-b144-e5435198f727)]
+interface nsIRequestContextService : nsISupports
+{
+ /**
+ * Get an existing request context from its ID
+ */
+ nsIRequestContext getRequestContext(in nsIDRef id);
+
+ /**
+ * Create a new request context identifier
+ */
+ nsID newRequestContextID();
+
+ /**
+ * Remove an existing request context from its ID
+ */
+ void removeRequestContext(in nsIDRef id);
+};
diff --git a/netwerk/base/nsIRequestObserver.idl b/netwerk/base/nsIRequestObserver.idl
new file mode 100644
index 0000000000..5ab94001f0
--- /dev/null
+++ b/netwerk/base/nsIRequestObserver.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIRequest;
+
+/**
+ * nsIRequestObserver
+ */
+[scriptable, uuid(fd91e2e0-1481-11d3-9333-00104ba0fd40)]
+interface nsIRequestObserver : nsISupports
+{
+ /**
+ * Called to signify the beginning of an asynchronous request.
+ *
+ * @param aRequest request being observed
+ * @param aContext user defined context
+ *
+ * An exception thrown from onStartRequest has the side-effect of
+ * causing the request to be canceled.
+ */
+ void onStartRequest(in nsIRequest aRequest,
+ in nsISupports aContext);
+
+ /**
+ * Called to signify the end of an asynchronous request. This
+ * call is always preceded by a call to onStartRequest.
+ *
+ * @param aRequest request being observed
+ * @param aContext user defined context
+ * @param aStatusCode reason for stopping (NS_OK if completed successfully)
+ *
+ * An exception thrown from onStopRequest is generally ignored.
+ */
+ void onStopRequest(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsresult aStatusCode);
+};
diff --git a/netwerk/base/nsIRequestObserverProxy.idl b/netwerk/base/nsIRequestObserverProxy.idl
new file mode 100644
index 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..c58c14cc70
--- /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..396aa42f82
--- /dev/null
+++ b/netwerk/base/nsISecureBrowserUI.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIDOMElement;
+interface nsIDocShell;
+
+[scriptable, uuid(718c662a-f810-4a80-a6c9-0b1810ecade2)]
+interface nsISecureBrowserUI : nsISupports
+{
+ void init(in mozIDOMWindowProxy window);
+ void setDocShell(in nsIDocShell docShell);
+
+ readonly attribute unsigned long state;
+};
+
+%{C++
+#define NS_SECURE_BROWSER_UI_CONTRACTID "@mozilla.org/secure_browser_ui;1"
+%}
diff --git a/netwerk/base/nsISecurityEventSink.idl b/netwerk/base/nsISecurityEventSink.idl
new file mode 100644
index 0000000000..5687530234
--- /dev/null
+++ b/netwerk/base/nsISecurityEventSink.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIURI;
+
+[scriptable, uuid(a71aee68-dd38-4736-bd79-035fea1a1ec6)]
+interface nsISecurityEventSink : nsISupports
+{
+
+ /**
+ * Fired when a security change occurs due to page transitions,
+ * or end document load. This interface should be called by
+ * a security package (eg Netscape Personal Security Manager)
+ * to notify nsIWebProgressListeners that security state has
+ * changed. State flags are in nsIWebProgressListener.idl
+ */
+
+ void onSecurityChange(in nsISupports i_Context, in unsigned long state);
+};
+
+
+
+
diff --git a/netwerk/base/nsISecurityInfoProvider.idl b/netwerk/base/nsISecurityInfoProvider.idl
new file mode 100644
index 0000000000..0cf83e69c5
--- /dev/null
+++ b/netwerk/base/nsISecurityInfoProvider.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b8cc9126-9319-4415-afd9-b82220d453ed)]
+interface nsISecurityInfoProvider : nsISupports
+{
+ /**
+ * The security info for this provider, if any.
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * Whether this provider has transferred data. If it hasn't, its
+ * security info should be ignored.
+ */
+ readonly attribute boolean hasTransferredData;
+};
diff --git a/netwerk/base/nsISensitiveInfoHiddenURI.idl b/netwerk/base/nsISensitiveInfoHiddenURI.idl
new file mode 100644
index 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..fa54104c33
--- /dev/null
+++ b/netwerk/base/nsIServerSocket.idl
@@ -0,0 +1,237 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIServerSocketListener;
+interface nsISocketTransport;
+
+native PRNetAddr(union PRNetAddr);
+[ptr] native PRNetAddrPtr(union PRNetAddr);
+
+typedef unsigned long nsServerSocketFlag;
+
+/**
+ * nsIServerSocket
+ *
+ * An interface to a server socket that can accept incoming connections.
+ */
+[scriptable, uuid(7a9c39cb-a13f-4eef-9bdf-a74301628742)]
+interface nsIServerSocket : nsISupports
+{
+ /**
+ * @name Server Socket Flags
+ * These flags define various socket options.
+ * @{
+ */
+ /// The server socket will only respond to connections on the
+ /// local loopback interface. Otherwise, it will accept connections
+ /// from any interface. To specify a particular network interface,
+ /// use initWithAddress.
+ const nsServerSocketFlag LoopbackOnly = 0x00000001;
+ /// The server socket will not be closed when Gecko is set
+ /// offline.
+ const nsServerSocketFlag KeepWhenOffline = 0x00000002;
+ /** @} */
+
+ /**
+ * init
+ *
+ * This method initializes a server socket.
+ *
+ * @param aPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the server socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in long aBackLog);
+
+ /**
+ * initSpecialConnection
+ *
+ * This method initializes a server socket and offers the ability to have
+ * that socket not get terminated if Gecko is set offline.
+ *
+ * @param aPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aFlags
+ * Flags for the socket.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void initSpecialConnection(in long aPort,
+ in nsServerSocketFlag aFlags,
+ in long aBackLog);
+
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a server socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this server socket should be bound.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ [noscript] void initWithAddress([const] in PRNetAddrPtr aAddr, in long aBackLog);
+
+ /**
+ * initWithFilename
+ *
+ * This method initializes a Unix domain or "local" server socket. Such
+ * a socket has a name in the filesystem, like an ordinary file. To
+ * connect, a client supplies the socket's filename, and the usual
+ * permission checks on socket apply.
+ *
+ * This makes Unix domain sockets useful for communication between the
+ * programs being run by a specific user on a single machine: the
+ * operating system takes care of authentication, and the user's home
+ * directory or profile directory provide natural per-user rendezvous
+ * points.
+ *
+ * Since Unix domain sockets are always local to the machine, they are
+ * not affected by the nsIIOService's 'offline' flag.
+ *
+ * The system-level socket API may impose restrictions on the length of
+ * the filename that are stricter than those of the underlying
+ * filesystem. If the file name is too long, this returns
+ * NS_ERROR_FILE_NAME_TOO_LONG.
+ *
+ * All components of the path prefix of |aPath| must name directories;
+ * otherwise, this returns NS_ERROR_FILE_NOT_DIRECTORY.
+ *
+ * This call requires execute permission on all directories containing
+ * the one in which the socket is to be created, and write and execute
+ * permission on the directory itself. Otherwise, this returns
+ * NS_ERROR_CONNECTION_REFUSED.
+ *
+ * This call creates the socket's directory entry. There must not be
+ * any existing entry with the given name. If there is, this returns
+ * NS_ERROR_SOCKET_ADDRESS_IN_USE.
+ *
+ * On systems that don't support Unix domain sockets at all, this
+ * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * @param aPath nsIFile
+ * The file name at which the socket should be created.
+ *
+ * @param aPermissions unsigned long
+ * Unix-style permission bits to be applied to the new socket.
+ *
+ * Note about permissions: Linux's unix(7) man page claims that some
+ * BSD-derived systems ignore permissions on UNIX-domain sockets;
+ * NetBSD's bind(2) man page agrees, but says it does check now (dated
+ * 2005). POSIX has required 'connect' to fail if write permission on
+ * the socket itself is not granted since 2003 (Issue 6). NetBSD says
+ * that the permissions on the containing directory (execute) have
+ * always applied, so creating sockets in appropriately protected
+ * directories should be secure on both old and new systems.
+ */
+ void initWithFilename(in nsIFile aPath, in unsigned long aPermissions,
+ in long aBacklog);
+
+ /**
+ * close
+ *
+ * This method closes a server socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this server socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the server socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIServerSocketListener aListener);
+
+ /**
+ * Returns the port of this server socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this server socket is bound. Since a
+ * server socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a server socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] PRNetAddr getAddress();
+};
+
+/**
+ * nsIServerSocketListener
+ *
+ * This interface is notified whenever a server socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(836d98ec-fee2-4bde-b609-abd5e966eabd)]
+interface nsIServerSocketListener : nsISupports
+{
+ /**
+ * onSocketAccepted
+ *
+ * This method is called when a client connection is accepted.
+ *
+ * @param aServ
+ * The server socket.
+ * @param aTransport
+ * The connected socket transport.
+ */
+ void onSocketAccepted(in nsIServerSocket aServ,
+ in nsISocketTransport aTransport);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The server socket is effectively dead after this notification.
+ *
+ * @param aServ
+ * The server socket.
+ * @param aStatus
+ * The reason why the server socket stopped listening. If the
+ * server socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIServerSocket aServ, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsISimpleStreamListener.idl b/netwerk/base/nsISimpleStreamListener.idl
new file mode 100644
index 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..6395d6b5f8
--- /dev/null
+++ b/netwerk/base/nsISocketTransport.idl
@@ -0,0 +1,256 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsITransport.idl"
+
+interface nsIInterfaceRequestor;
+interface nsINetAddr;
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+native NeckoOriginAttributes(mozilla::NeckoOriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes);
+
+/**
+ * nsISocketTransport
+ *
+ * NOTE: Connection setup is triggered by opening an input or output stream,
+ * it does not start on its own. Completion of the connection setup is
+ * indicated by a STATUS_CONNECTED_TO notification to the event sink (if set).
+ *
+ * NOTE: This is a free-threaded interface, meaning that the methods on
+ * this interface may be called from any thread.
+ */
+[scriptable, uuid(79221831-85e2-43a8-8152-05d77d6fde31)]
+interface nsISocketTransport : nsITransport
+{
+ /**
+ * Get the peer's host for the underlying socket connection.
+ * For Unix domain sockets, this is a pathname, or the empty string for
+ * unnamed and abstract socket addresses.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * Get the port for the underlying socket connection.
+ * For Unix domain sockets, this is zero.
+ */
+ readonly attribute long port;
+
+ /**
+ * The origin attributes are used to create sockets. The first party domain
+ * will eventually be used to isolate OCSP cache and is only non-empty when
+ * "privacy.firstparty.isolate" is enabled. Setting this is the only way to
+ * carry origin attributes down to NSPR layers which are final consumers.
+ * It must be set before the socket transport is built.
+ */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ attribute jsval originAttributes;
+
+ [noscript, nostdcall, binaryname(GetOriginAttributes)]
+ NeckoOriginAttributes binaryGetOriginAttributes();
+
+ [noscript, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+
+ /**
+ * The platform-specific network interface id that this socket
+ * associated with. Note that this attribute can be only accessed
+ * in the socket thread.
+ */
+ attribute ACString networkInterfaceId;
+
+ /**
+ * Returns the IP address of the socket connection peer. This
+ * attribute is defined only once a connection has been established.
+ */
+ [noscript] NetAddr getPeerAddr();
+
+ /**
+ * Returns the IP address of the initiating end. This attribute
+ * is defined only once a connection has been established.
+ */
+ [noscript] NetAddr getSelfAddr();
+
+ /**
+ * Bind to a specific local address.
+ */
+ [noscript] void bind(in NetAddrPtr aLocalAddr);
+
+ /**
+ * Returns a scriptable version of getPeerAddr. This attribute is defined
+ * only once a connection has been established.
+ */
+ nsINetAddr getScriptablePeerAddr();
+
+ /**
+ * Returns a scriptable version of getSelfAddr. This attribute is defined
+ * only once a connection has been established.
+ */
+ nsINetAddr getScriptableSelfAddr();
+
+ /**
+ * Security info object returned from the secure socket provider. This
+ * object supports nsISSLSocketControl, nsITransportSecurityInfo, and
+ * possibly other interfaces.
+ *
+ * This attribute is only available once the socket is connected.
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * Security notification callbacks passed to the secure socket provider
+ * via nsISSLSocketControl at socket creation time.
+ *
+ * NOTE: this attribute cannot be changed once a stream has been opened.
+ */
+ attribute nsIInterfaceRequestor securityCallbacks;
+
+ /**
+ * Test if this socket transport is (still) connected.
+ */
+ boolean isAlive();
+
+ /**
+ * Socket timeouts in seconds. To specify no timeout, pass UINT32_MAX
+ * as aValue to setTimeout. The implementation may truncate timeout values
+ * to a smaller range of values (e.g., 0 to 0xFFFF).
+ */
+ unsigned long getTimeout(in unsigned long aType);
+ void setTimeout(in unsigned long aType, in unsigned long aValue);
+
+ /**
+ * Values for the aType parameter passed to get/setTimeout.
+ */
+ const unsigned long TIMEOUT_CONNECT = 0;
+ const unsigned long TIMEOUT_READ_WRITE = 1;
+
+ /**
+ * nsITransportEventSink status codes.
+ *
+ * Although these look like XPCOM error codes and are passed in an nsresult
+ * variable, they are *not* error codes. Note that while they *do* overlap
+ * with existing error codes in Necko, these status codes are confined
+ * within a very limited context where no error codes may appear, so there
+ * is no ambiguity.
+ *
+ * The values of these status codes must never change.
+ *
+ * The status codes appear in near-chronological order (not in numeric
+ * order). STATUS_RESOLVING may be skipped if the host does not need to be
+ * resolved. STATUS_WAITING_FOR is an optional status code, which the impl
+ * of this interface may choose not to generate.
+ *
+ * In C++, these constants have a type of uint32_t, so C++ callers must use
+ * the NS_NET_STATUS_* constants defined below, which have a type of
+ * nsresult.
+ */
+ const unsigned long STATUS_RESOLVING = 0x804b0003;
+ const unsigned long STATUS_RESOLVED = 0x804b000b;
+ const unsigned long STATUS_CONNECTING_TO = 0x804b0007;
+ const unsigned long STATUS_CONNECTED_TO = 0x804b0004;
+ const unsigned long STATUS_SENDING_TO = 0x804b0005;
+ const unsigned long STATUS_WAITING_FOR = 0x804b000a;
+ const unsigned long STATUS_RECEIVING_FROM = 0x804b0006;
+
+ /**
+ * connectionFlags is a bitmask that can be used to modify underlying
+ * behavior of the socket connection. See the flags below.
+ */
+ attribute unsigned long connectionFlags;
+
+ /**
+ * Values for the connectionFlags
+ *
+ * When making a new connection BYPASS_CACHE will force the Necko DNS
+ * cache entry to be refreshed with a new call to NSPR if it is set before
+ * opening the new stream.
+ */
+ const unsigned long BYPASS_CACHE = (1 << 0);
+
+ /**
+ * When setting this flag, the socket will not apply any
+ * credentials when establishing a connection. For example,
+ * an SSL connection would not send any client-certificates
+ * if this flag is set.
+ */
+ const unsigned long ANONYMOUS_CONNECT = (1 << 1);
+
+ /**
+ * If set, we will skip all IPv6 addresses the host may have and only
+ * connect to IPv4 ones.
+ */
+ const unsigned long DISABLE_IPV6 = (1 << 2);
+
+ /**
+ * If set, indicates that the connection was initiated from a source
+ * defined as being private in the sense of Private Browsing. Generally,
+ * there should be no state shared between connections that are private
+ * and those that are not; it is OK for multiple private connections
+ * to share state with each other, and it is OK for multiple non-private
+ * connections to share state with each other.
+ */
+ const unsigned long NO_PERMANENT_STORAGE = (1 << 3);
+
+ /**
+ * If set, we will skip all IPv4 addresses the host may have and only
+ * connect to IPv6 ones.
+ */
+ const unsigned long DISABLE_IPV4 = (1 << 4);
+
+ /**
+ * If set, indicates that the socket should not connect if the hostname
+ * resolves to an RFC1918 address or IPv6 equivalent.
+ */
+ const unsigned long DISABLE_RFC1918 = (1 << 5);
+
+ /**
+ * This flag is an explicit opt-in that allows a normally secure socket
+ * provider to use, at its discretion, an insecure algorithm. e.g.
+ * a TLS socket without authentication.
+ */
+ const unsigned long MITM_OK = (1 << 6);
+
+ /**
+ * If set, do not use newer protocol features that might have interop problems
+ * on the Internet. Intended only for use with critical infra like the updater.
+ * default is false.
+ */
+ const unsigned long BE_CONSERVATIVE = (1 << 7);
+
+ /**
+ * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or
+ * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported
+ * services require expedited-forwarding).
+ * Not setting this value will leave the socket with the default
+ * ToS value, which on most systems if IPTOS_CLASS_CS0 (formerly
+ * IPTOS_PREC_ROUTINE).
+ */
+ attribute octet QoSBits;
+
+ /**
+ * TCP send and receive buffer sizes. A value of 0 means OS level
+ * auto-tuning is in effect.
+ */
+ attribute unsigned long recvBufferSize;
+ attribute unsigned long sendBufferSize;
+
+ /**
+ * TCP keepalive configuration (support varies by platform).
+ * Note that the attribute as well as the setter can only accessed
+ * in the socket thread.
+ */
+ attribute boolean keepaliveEnabled;
+ void setKeepaliveVals(in long keepaliveIdleTime,
+ in long keepaliveRetryInterval);
+};
diff --git a/netwerk/base/nsISocketTransportService.idl b/netwerk/base/nsISocketTransportService.idl
new file mode 100644
index 0000000000..06350b532f
--- /dev/null
+++ b/netwerk/base/nsISocketTransportService.idl
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsISocketTransport;
+interface nsIProxyInfo;
+interface nsIRunnable;
+
+%{C++
+class nsASocketHandler;
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+[ptr] native nsASocketHandlerPtr(nsASocketHandler);
+
+[scriptable, uuid(ad56b25f-e6bb-4db3-9f7b-5b7db33fd2b1)]
+interface nsISocketTransportService : nsISupports
+{
+ /**
+ * Creates a transport for a specified host and port.
+ *
+ * @param aSocketTypes
+ * array of socket type strings. null if using default socket type.
+ * @param aTypeCount
+ * specifies length of aSocketTypes.
+ * @param aHost
+ * specifies the target hostname or IP address literal of the peer
+ * for this socket.
+ * @param aPort
+ * specifies the target port of the peer for this socket.
+ * @param aProxyInfo
+ * specifies the transport-layer proxy type to use. null if no
+ * proxy. used for communicating information about proxies like
+ * SOCKS (which are transparent to upper protocols).
+ *
+ * @see nsIProxiedProtocolHandler
+ * @see nsIProtocolProxyService::GetProxyInfo
+ *
+ * NOTE: this function can be called from any thread
+ */
+ nsISocketTransport createTransport([array, size_is(aTypeCount)]
+ in string aSocketTypes,
+ in unsigned long aTypeCount,
+ in AUTF8String aHost,
+ in long aPort,
+ in nsIProxyInfo aProxyInfo);
+
+ /**
+ * Create a transport built on a Unix domain socket, connecting to the
+ * given filename.
+ *
+ * Since Unix domain sockets are always local to the machine, they are
+ * not affected by the nsIIOService's 'offline' flag.
+ *
+ * On systems that don't support Unix domain sockets at all, this
+ * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * The system-level socket API may impose restrictions on the length of
+ * the filename that are stricter than those of the underlying
+ * filesystem. If the file name is too long, this returns
+ * NS_ERROR_FILE_NAME_TOO_LONG.
+ *
+ * The |aPath| parameter must specify an existing directory entry.
+ * Otherwise, this returns NS_ERROR_FILE_NOT_FOUND.
+ *
+ * The program must have search permission on all components of the
+ * path prefix of |aPath|, and read and write permission on |aPath|
+ * itself. Without such permission, this returns
+ * NS_ERROR_CONNECTION_REFUSED.
+ *
+ * The |aPath| parameter must refer to a unix-domain socket. Otherwise,
+ * this returns NS_ERROR_CONNECTION_REFUSED. (POSIX specifies
+ * ECONNREFUSED when "the target address was not listening for
+ * connections", and this is what Linux returns.)
+ *
+ * @param aPath
+ * The file name of the Unix domain socket to which we should
+ * connect.
+ */
+ nsISocketTransport createUnixDomainTransport(in nsIFile aPath);
+
+ /**
+ * Adds a new socket to the list of controlled sockets.
+ *
+ * This will fail with the error code NS_ERROR_NOT_AVAILABLE if the maximum
+ * number of sockets is already reached.
+ * In this case, the notifyWhenCanAttachSocket method should be used.
+ *
+ * @param aFd
+ * Open file descriptor of the socket to control.
+ * @param aHandler
+ * Socket handler that will receive notifications when the socket is
+ * ready or detached.
+ *
+ * NOTE: this function may only be called from an event dispatch on the
+ * socket thread.
+ */
+ [noscript] void attachSocket(in PRFileDescPtr aFd,
+ in nsASocketHandlerPtr aHandler);
+
+ /**
+ * if the number of sockets reaches the limit, then consumers can be
+ * notified when the number of sockets becomes less than the limit. the
+ * notification is asynchronous, delivered via the given nsIRunnable
+ * instance on the socket transport thread.
+ *
+ * @param aEvent
+ * Event that will receive the notification when a new socket can
+ * be attached
+ *
+ * NOTE: this function may only be called from an event dispatch on the
+ * socket thread.
+ */
+ [noscript] void notifyWhenCanAttachSocket(in nsIRunnable aEvent);
+};
+
+[scriptable, uuid(c5204623-5b58-4a16-8b2e-67c34dd02e3f)]
+interface nsIRoutedSocketTransportService : nsISocketTransportService
+{
+ // use this instead of createTransport when you have a transport
+ // that distinguishes between origin and route (aka connection)
+ nsISocketTransport createRoutedTransport([array, size_is(aTypeCount)]
+ in string aSocketTypes,
+ in unsigned long aTypeCount,
+ in AUTF8String aHost, // origin
+ in long aPort, // origin
+ in AUTF8String aHostRoute,
+ in long aPortRoute,
+ in nsIProxyInfo aProxyInfo);
+};
diff --git a/netwerk/base/nsISpeculativeConnect.idl b/netwerk/base/nsISpeculativeConnect.idl
new file mode 100644
index 0000000000..067edd3f08
--- /dev/null
+++ b/netwerk/base/nsISpeculativeConnect.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+[scriptable, uuid(d74a17ac-5b8a-4824-a309-b1f04a3c4aed)]
+interface nsISpeculativeConnect : nsISupports
+{
+ /**
+ * Called as a hint to indicate a new transaction for the URI is likely coming
+ * soon. The implementer may use this information to start a TCP
+ * and/or SSL level handshake for that resource immediately so that it is
+ * ready and/or progressed when the transaction is actually submitted.
+ *
+ * No obligation is taken on by the implementer, nor is the submitter obligated
+ * to actually open the new channel.
+ *
+ * @param aURI the URI of the hinted transaction
+ * @param aPrincipal the principal that will be used for opening the
+ * channel of the hinted transaction.
+ * @param aCallbacks any security callbacks for use with SSL for interfaces
+ * such as nsIBadCertListener. May be null.
+ *
+ */
+ void speculativeConnect(in nsIURI aURI,
+ in nsIInterfaceRequestor aCallbacks);
+
+ void speculativeConnect2(in nsIURI aURI,
+ in nsIPrincipal aPrincipal,
+ in nsIInterfaceRequestor aCallbacks);
+
+ void speculativeAnonymousConnect(in nsIURI aURI,
+ in nsIInterfaceRequestor aCallbacks);
+
+ void speculativeAnonymousConnect2(in nsIURI aURI,
+ in nsIPrincipal aPrincipal,
+ in nsIInterfaceRequestor aCallbacks);
+};
+
+/**
+ * This is used to override the default values for various values (documented
+ * inline) to determine whether or not to actually make a speculative
+ * connection.
+ */
+[builtinclass, uuid(1040ebe3-6ed1-45a6-8587-995e082518d7)]
+interface nsISpeculativeConnectionOverrider : nsISupports
+{
+ /**
+ * Used to determine the maximum number of unused speculative connections
+ * we will have open for a host at any one time
+ */
+ [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit;
+
+ /**
+ * Used to determine if we will ignore the existence of any currently idle
+ * connections when we decide whether or not to make a speculative
+ * connection.
+ */
+ [infallible] readonly attribute boolean ignoreIdle;
+
+ /*
+ * Used by the Predictor to gather telemetry data on speculative connection
+ * usage.
+ */
+ [infallible] readonly attribute boolean isFromPredictor;
+
+ /**
+ * by default speculative connections are not made to RFC 1918 addresses
+ */
+ [infallible] readonly attribute boolean allow1918;
+};
diff --git a/netwerk/base/nsIStandardURL.idl b/netwerk/base/nsIStandardURL.idl
new file mode 100644
index 0000000000..020555991d
--- /dev/null
+++ b/netwerk/base/nsIStandardURL.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMutable.idl"
+
+interface nsIURI;
+
+/**
+ * nsIStandardURL defines the interface to an URL with the standard
+ * file path format common to protocols like http, ftp, and file.
+ * It supports initialization from a relative path and provides
+ * some customization on how URLs are normalized.
+ */
+[scriptable, uuid(babd6cca-ebe7-4329-967c-d6b9e33caa81)]
+interface nsIStandardURL : nsIMutable
+{
+ /**
+ * blah:foo/bar => blah://foo/bar
+ * blah:/foo/bar => blah:///foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah:///foo/bar
+ */
+ const unsigned long URLTYPE_STANDARD = 1;
+
+ /**
+ * blah:foo/bar => blah://foo/bar
+ * blah:/foo/bar => blah://foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah://foo/bar
+ */
+ const unsigned long URLTYPE_AUTHORITY = 2;
+
+ /**
+ * blah:foo/bar => blah:///foo/bar
+ * blah:/foo/bar => blah:///foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah:///foo/bar
+ */
+ const unsigned long URLTYPE_NO_AUTHORITY = 3;
+
+ /**
+ * Initialize a standard URL.
+ *
+ * @param aUrlType - one of the URLTYPE_ flags listed above.
+ * @param aDefaultPort - if the port parsed from the URL string matches
+ * this port, then the port will be removed from the
+ * canonical form of the URL.
+ * @param aSpec - URL string.
+ * @param aOriginCharset - the charset from which this URI string
+ * originated. this corresponds to the charset
+ * that should be used when communicating this
+ * URI to an origin server, for example. if
+ * null, then provide aBaseURI implements this
+ * interface, the origin charset of aBaseURI will
+ * be assumed, otherwise defaulting to UTF-8 (i.e.,
+ * no charset transformation from aSpec).
+ * @param aBaseURI - if null, aSpec must specify an absolute URI.
+ * otherwise, aSpec will be resolved relative
+ * to aBaseURI.
+ */
+ void init(in unsigned long aUrlType,
+ in long aDefaultPort,
+ in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI);
+
+ /**
+ * Set the default port.
+ *
+ * Note: If this object is already using its default port (i.e. if it has
+ * mPort == -1), then it will now implicitly be using the new default port.
+ *
+ * @param aNewDefaultPort - if the URI has (or is later given) a port that
+ * matches this default, then we won't include a
+ * port number in the canonical form of the URL.
+ */
+ void setDefaultPort(in long aNewDefaultPort);
+};
diff --git a/netwerk/base/nsIStreamListener.idl b/netwerk/base/nsIStreamListener.idl
new file mode 100644
index 0000000000..09ee408a19
--- /dev/null
+++ b/netwerk/base/nsIStreamListener.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequestObserver.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIStreamListener
+ */
+[scriptable, uuid(3b4c8a77-76ba-4610-b316-678c73a3b88c)]
+interface nsIStreamListener : nsIRequestObserver
+{
+ /**
+ * Called when the next chunk of data (corresponding to the request) may
+ * be read without blocking the calling thread. The onDataAvailable impl
+ * must read exactly |aCount| bytes of data before returning.
+ *
+ * @param aRequest request corresponding to the source of the data
+ * @param aContext user defined context
+ * @param aInputStream input stream containing the data chunk
+ * @param aOffset
+ * Number of bytes that were sent in previous onDataAvailable calls
+ * for this request. In other words, the sum of all previous count
+ * parameters.
+ * @param aCount number of bytes available in the stream
+ *
+ * NOTE: The aInputStream parameter must implement readSegments.
+ *
+ * An exception thrown from onDataAvailable has the side-effect of
+ * causing the request to be canceled.
+ */
+ void onDataAvailable(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsIInputStream aInputStream,
+ in unsigned long long aOffset,
+ in unsigned long aCount);
+};
diff --git a/netwerk/base/nsIStreamListenerTee.idl b/netwerk/base/nsIStreamListenerTee.idl
new file mode 100644
index 0000000000..b7c3ae676d
--- /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..cd39f9cba3
--- /dev/null
+++ b/netwerk/base/nsIStreamTransportService.idl
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITransport;
+interface nsIInputStream;
+interface nsIOutputStream;
+
+/**
+ * This service read/writes a stream on a background thread.
+ *
+ * Use this service to transform any blocking stream (e.g., file stream)
+ * into a fully asynchronous stream that can be read/written without
+ * blocking the main thread.
+ */
+[scriptable, uuid(5e0adf7d-9785-45c3-a193-04f25a75da8f)]
+interface nsIStreamTransportService : nsISupports
+{
+ /**
+ * CreateInputTransport
+ *
+ * @param aStream
+ * The input stream that will be read on a background thread.
+ * This stream must implement "blocking" stream semantics.
+ * @param aStartOffset
+ * The input stream will be read starting from this offset. Pass
+ * -1 to read from the current stream offset. NOTE: this parameter
+ * is ignored if the stream does not support nsISeekableStream.
+ * @param aReadLimit
+ * This parameter limits the number of bytes that will be read from
+ * the input stream. Pass -1 to read everything.
+ * @param aCloseWhenDone
+ * Specify this flag to have the input stream closed once its
+ * contents have been completely read.
+ *
+ * @return nsITransport instance.
+ */
+ nsITransport createInputTransport(in nsIInputStream aStream,
+ in long long aStartOffset,
+ in long long aReadLimit,
+ in boolean aCloseWhenDone);
+
+ /**
+ * CreateOutputTransport
+ *
+ * @param aStream
+ * The output stream that will be written to on a background thread.
+ * This stream must implement "blocking" stream semantics.
+ * @param aStartOffset
+ * The output stream will be written starting at this offset. Pass
+ * -1 to write to the current stream offset. NOTE: this parameter
+ * is ignored if the stream does not support nsISeekableStream.
+ * @param aWriteLimit
+ * This parameter limits the number of bytes that will be written to
+ * the output stream. Pass -1 for unlimited writing.
+ * @param aCloseWhenDone
+ * Specify this flag to have the output stream closed once its
+ * contents have been completely written.
+ *
+ * @return nsITransport instance.
+ */
+ nsITransport createOutputTransport(in nsIOutputStream aStream,
+ in long long aStartOffset,
+ in long long aWriteLimit,
+ in boolean aCloseWhenDone);
+};
diff --git a/netwerk/base/nsIStreamingProtocolController.idl b/netwerk/base/nsIStreamingProtocolController.idl
new file mode 100644
index 0000000000..249e8e9830
--- /dev/null
+++ b/netwerk/base/nsIStreamingProtocolController.idl
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+interface nsIURI;
+
+#include "nsISupports.idl"
+
+%{C++
+#define MEDIASTREAM_FRAMETYPE_NORMAL 0x00000001
+#define MEDIASTREAM_FRAMETYPE_DISCONTINUITY 0x00000002
+#define MEDIASTREAM_FRAMETYPE_END_OF_STREAM 0x00000004
+%}
+
+/**
+ * Metadata of the media stream.
+ */
+[uuid(294adb30-856c-11e2-9e96-0800200c9a66)]
+interface nsIStreamingProtocolMetaData : nsISupports
+{
+ /**
+ * Frame type.
+ */
+ attribute uint32_t frameType;
+
+ /**
+ * The total tracks for the given media stream session.
+ */
+ attribute uint32_t totalTracks;
+
+ /**
+ * The mime type of the track.
+ */
+ attribute ACString mimeType;
+
+ /**
+ * The width of the resolution.
+ */
+ attribute unsigned long width;
+
+ /**
+ * The height of the resolution.
+ */
+ attribute unsigned long height;
+
+ /**
+ * The duration of the media stream in units of microseconds.
+ */
+ attribute unsigned long long duration;
+
+ /**
+ * The sample rate of the media stream.
+ */
+ attribute unsigned long sampleRate;
+
+ /**
+ * The timestamp indicates the stream absolute position
+ * relative to the beginning of the presentation.
+ */
+ attribute unsigned long long timeStamp;
+
+ /**
+ * The total number of audio channels in the media stream.
+ */
+ attribute unsigned long channelCount;
+
+ /**
+ * The AAC audio codec specific data.
+ */
+ attribute ACString esdsData;
+
+ /**
+ * The AVCC format extradata of H.264 stream.
+ */
+ attribute ACString avccData;
+};
+
+/**
+ * nsIStreamingProtocolListener
+ */
+[scriptable, uuid(c4f6b660-892e-11e2-9e96-0800200c9a66)]
+interface nsIStreamingProtocolListener : nsISupports
+{
+ /**
+ * Called when the data may be read without blocking the calling thread.
+ * @param index The track number of the media stream.
+ * @param data Raw data of the media stream on given track number.
+ * @param length The length of the raw data.
+ * @param offset The offset in the data stream from the start of the media
+ * presentation in bytes.
+ * @param meta The meta data of the frame.
+ */
+ void onMediaDataAvailable(in uint8_t index,
+ in ACString data,
+ in uint32_t length,
+ in uint32_t offset,
+ in nsIStreamingProtocolMetaData meta);
+
+ /**
+ * Called when the meta data for a given session is available.
+ * @param index The track number of the media stream.
+ * @param meta The meta data of the media stream.
+ */
+ void onConnected(in uint8_t index, in nsIStreamingProtocolMetaData meta);
+
+ /**
+ * Called when the Rtsp session is closed.
+ * @param index Track number of the media stream.
+ * @param reason The reason of disconnection.
+ */
+ void onDisconnected(in uint8_t index, in nsresult reason);
+};
+
+/**
+ * Media stream controller API: control and retrieve meta data from media stream.
+ */
+[uuid(4ce040f0-c50d-461f-94e2-af5a77fe13a5)]
+interface nsIStreamingProtocolController : nsISupports
+{
+ /**
+ * Preprare the URI before we can start the connection.
+ * @param aUri The URI of the media stream.
+ */
+ void init(in nsIURI aUri);
+
+ /**
+ * Asynchronously open this controller. Data is fed to the specified
+ * media stream listener as it becomes available. If asyncOpen returns
+ * successfully, the controller is responsible for keeping itself alive
+ * until it has called onStopRequest on aListener.
+ *
+ * @param aListener The nsIStreamingProtocolListener implementation
+ */
+ void asyncOpen(in nsIStreamingProtocolListener aListener);
+
+ /*
+ * Get the metadata of a track.
+ * @param index Index of a track.
+ * @return A nsIStreamingProtocolMetaData.
+ */
+ nsIStreamingProtocolMetaData getTrackMetaData(in octet index);
+
+ /*
+ * Tell the streaming server to start sending media data.
+ */
+ void play();
+
+ /*
+ * Tell the streaming server to pause sending media data.
+ */
+ void pause();
+
+ /*
+ * Tell the streaming server to resume the suspended media stream.
+ */
+ void resume();
+
+ /*
+ * Tell the streaming server to suspend the media stream.
+ */
+ void suspend();
+
+ /*
+ * Tell the streaming server to send media data in specific time.
+ * @param seekTimeUs Start time of the media stream in microseconds.
+ */
+ void seek(in unsigned long long seekTimeUs);
+
+ /*
+ * Tell the streaming server to stop the
+ * media stream and close the connection.
+ */
+ void stop();
+
+ /*
+ * Notify the streaming controller that the playback has ended.
+ * The controller might have to perform certain internal state transition.
+ */
+ void playbackEnded();
+
+ /**
+ * Total number of audio/video tracks.
+ */
+ readonly attribute octet totalTracks;
+};
diff --git a/netwerk/base/nsIStreamingProtocolService.idl b/netwerk/base/nsIStreamingProtocolService.idl
new file mode 100644
index 0000000000..a0fae164e5
--- /dev/null
+++ b/netwerk/base/nsIStreamingProtocolService.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsIStreamingProtocolController;
+interface nsIChannel;
+
+#include "nsISupports.idl"
+
+%{C++
+#define NS_MEDIASTREAMCONTROLLERSERVICE_CID \
+ { 0x94838530, 0x8627, 0x11e2, \
+ { \
+ 0x9e, 0x96, 0x08, 0x00, \
+ 0x20, 0x0c, 0x9a, 0x66 \
+ } \
+ }
+#define MEDIASTREAMCONTROLLERSERVICE_CONTRACTID \
+ "@mozilla.org/mediastream/mediastreamcontrollerservice;1"
+%}
+
+/**
+ * Media stream controller Service API.
+ */
+[uuid(94838530-8627-11e2-9e96-0800200c9a66)]
+interface nsIStreamingProtocolControllerService : nsISupports
+{
+ /*
+ * Create a new media stream controller from the given channel.
+ * @param channel nsIChannel for the given URI.
+ */
+ nsIStreamingProtocolController create(in nsIChannel channel);
+};
diff --git a/netwerk/base/nsISyncStreamListener.idl b/netwerk/base/nsISyncStreamListener.idl
new file mode 100644
index 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..55be7f3130
--- /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..9a03c2ead7
--- /dev/null
+++ b/netwerk/base/nsITLSServerSocket.idl
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIServerSocket.idl"
+
+interface nsIX509Cert;
+interface nsITLSServerSecurityObserver;
+interface nsISocketTransport;
+
+[scriptable, uuid(cc2c30f9-cfaa-4b8a-bd44-c24881981b74)]
+interface nsITLSServerSocket : nsIServerSocket
+{
+ /**
+ * serverCert
+ *
+ * The server's certificate that is presented to the client during the TLS
+ * handshake. This is required to be set before calling |asyncListen|.
+ */
+ attribute nsIX509Cert serverCert;
+
+ /**
+ * setSessionCache
+ *
+ * Whether the server should use a session cache. Defaults to true. This
+ * should be set before calling |asyncListen| if you wish to change the
+ * default.
+ */
+ void setSessionCache(in boolean aSessionCache);
+
+ /**
+ * setSessionTickets
+ *
+ * Whether the server should support session tickets. Defaults to true. This
+ * should be set before calling |asyncListen| if you wish to change the
+ * default.
+ */
+ void setSessionTickets(in boolean aSessionTickets);
+
+ /**
+ * Values for setRequestClientCertificate
+ */
+ // Never request
+ const unsigned long REQUEST_NEVER = 0;
+ // Request (but do not require) during the first handshake only
+ const unsigned long REQUEST_FIRST_HANDSHAKE = 1;
+ // Request (but do not require) during each handshake
+ const unsigned long REQUEST_ALWAYS = 2;
+ // Require during the first handshake only
+ const unsigned long REQUIRE_FIRST_HANDSHAKE = 3;
+ // Require during each handshake
+ const unsigned long REQUIRE_ALWAYS = 4;
+
+ /**
+ * setRequestClientCertificate
+ *
+ * Whether the server should request and/or require a client auth certificate
+ * from the client. Defaults to REQUEST_NEVER. See the possible options
+ * above. This should be set before calling |asyncListen| if you wish to
+ * change the default.
+ */
+ void setRequestClientCertificate(in unsigned long aRequestClientCert);
+
+ /**
+ * setCipherSuites
+ *
+ * The server's cipher suites that is used by the TLS handshake.
+ * This is required to be set before calling |asyncListen|.
+ */
+ void setCipherSuites([array, size_is(aLength)] in unsigned short aCipherSuites,
+ in unsigned long aLength);
+
+ /**
+ * setVersionRange
+ *
+ * The server's TLS versions that is used by the TLS handshake.
+ * This is required to be set before calling |asyncListen|.
+ *
+ * aMinVersion and aMaxVersion is a TLS version value from
+ * |nsITLSClientStatus| constants.
+ */
+ void setVersionRange(in unsigned short aMinVersion,
+ in unsigned short aMaxVersion);
+};
+
+/**
+ * Security summary for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server.
+ *
+ * This is accessible through the security info object on the transport, which
+ * will be an instance of |nsITLSServerConnectionInfo| (see below).
+ *
+ * The values of these attributes are available once the |onHandshakeDone|
+ * method of the security observer has been called (see
+ * |nsITLSServerSecurityObserver| below).
+ */
+[scriptable, uuid(19668ea4-e5ad-4182-9698-7e890d48f327)]
+interface nsITLSClientStatus : nsISupports
+{
+ /**
+ * peerCert
+ *
+ * The client's certificate, if one was requested via |requestCertificate|
+ * above and supplied by the client.
+ */
+ readonly attribute nsIX509Cert peerCert;
+
+ /**
+ * Values for tlsVersionUsed, as defined by TLS
+ */
+ const short SSL_VERSION_3 = 0x0300;
+ const short TLS_VERSION_1 = 0x0301;
+ const short TLS_VERSION_1_1 = 0x0302;
+ const short TLS_VERSION_1_2 = 0x0303;
+ const short TLS_VERSION_1_3 = 0x0304;
+ const short TLS_VERSION_UNKNOWN = -1;
+
+ /**
+ * tlsVersionUsed
+ *
+ * The version of TLS used by the connection. See values above.
+ */
+ readonly attribute short tlsVersionUsed;
+
+ /**
+ * cipherName
+ *
+ * Name of the cipher suite used, such as
+ * "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".
+ * See security/nss/lib/ssl/sslinfo.c for the possible values.
+ */
+ readonly attribute ACString cipherName;
+
+ /**
+ * keyLength
+ *
+ * The "effective" key size of the symmetric key in bits.
+ */
+ readonly attribute unsigned long keyLength;
+
+ /**
+ * macLength
+ *
+ * The size of the MAC in bits.
+ */
+ readonly attribute unsigned long macLength;
+};
+
+/**
+ * Connection info for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server. This object is thread-safe.
+ *
+ * This is exposed as the security info object on the transport, so it can be
+ * accessed via |transport.securityInfo|.
+ *
+ * This interface is available by the time the |onSocketAttached| is called,
+ * which is the first time the TLS server consumer is notified of a new client.
+ */
+[scriptable, uuid(8a93f5d5-eddd-4c62-a4bd-bfd297653184)]
+interface nsITLSServerConnectionInfo : nsISupports
+{
+ /**
+ * setSecurityObserver
+ *
+ * Set the security observer to be notified when the TLS handshake has
+ * completed.
+ */
+ void setSecurityObserver(in nsITLSServerSecurityObserver observer);
+
+ /**
+ * serverSocket
+ *
+ * The nsITLSServerSocket instance that accepted this client connection.
+ */
+ readonly attribute nsITLSServerSocket serverSocket;
+
+ /**
+ * status
+ *
+ * Security summary for this TLS client connection. Note that the values of
+ * this interface are not available until the TLS handshake has completed.
+ * See |nsITLSClientStatus| above for more details.
+ */
+ readonly attribute nsITLSClientStatus status;
+};
+
+[scriptable, uuid(1f62e1ae-e546-4a38-8917-d428472ed736)]
+interface nsITLSServerSecurityObserver : nsISupports
+{
+ /**
+ * onHandsakeDone
+ *
+ * This method is called once the TLS handshake is completed. This takes
+ * place after |onSocketAccepted| has been called, which typically opens the
+ * streams to keep things moving along. It's important to be aware that the
+ * handshake has not completed at the point that |onSocketAccepted| is called,
+ * so no security verification can be done until this method is called.
+ */
+ void onHandshakeDone(in nsITLSServerSocket aServer,
+ in nsITLSClientStatus aStatus);
+};
diff --git a/netwerk/base/nsIThreadRetargetableRequest.idl b/netwerk/base/nsIThreadRetargetableRequest.idl
new file mode 100644
index 0000000000..9a93b70c65
--- /dev/null
+++ b/netwerk/base/nsIThreadRetargetableRequest.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIEventTarget;
+
+/**
+ * nsIThreadRetargetableRequest
+ *
+ * Should be implemented by requests that support retargeting delivery of
+ * data off the main thread.
+ */
+[uuid(27b84c48-5a73-4ba4-a8a4-8b5e649a145e)]
+interface nsIThreadRetargetableRequest : nsISupports
+{
+ /**
+ * Called to retarget delivery of OnDataAvailable to another thread. Should
+ * only be called before AsyncOpen for nsIWebsocketChannels, or during
+ * OnStartRequest for nsIChannels.
+ * Note: For nsIChannels, OnStartRequest and OnStopRequest will still be
+ * delivered on the main thread.
+ *
+ * @param aNewTarget New event target, e.g. thread or threadpool.
+ *
+ * Note: no return value is given. If the retargeting cannot be handled,
+ * normal delivery to the main thread will continue. As such, listeners
+ * should be ready to deal with OnDataAvailable on either the main thread or
+ * the new target thread.
+ */
+ void retargetDeliveryTo(in nsIEventTarget aNewTarget);
+};
diff --git a/netwerk/base/nsIThreadRetargetableStreamListener.idl b/netwerk/base/nsIThreadRetargetableStreamListener.idl
new file mode 100644
index 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..76b8cc2a5b
--- /dev/null
+++ b/netwerk/base/nsIThrottledInputChannel.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIAsyncInputStream;
+
+/**
+ * An instance of this interface can be used to throttle the uploads
+ * of a group of associated channels.
+ */
+[scriptable, uuid(6b4b96fe-3c67-4587-af7b-58b6b17da411)]
+interface nsIInputChannelThrottleQueue : nsISupports
+{
+ /**
+ * Initialize this object with the mean and maximum bytes per
+ * second that will be allowed. Neither value may be zero, and
+ * the maximum must not be less than the mean.
+ *
+ * @param aMeanBytesPerSecond
+ * Mean number of bytes per second.
+ * @param aMaxBytesPerSecond
+ * Maximum number of bytes per second.
+ */
+ void init(in unsigned long aMeanBytesPerSecond, in unsigned long aMaxBytesPerSecond);
+
+ /**
+ * Return the number of bytes that are available to the caller in
+ * this time slice.
+ *
+ * @param aRemaining
+ * The number of bytes available to be processed
+ * @return the number of bytes allowed to be processed during this
+ * time slice; this will never be greater than aRemaining.
+ */
+ unsigned long available(in unsigned long aRemaining);
+
+ /**
+ * Record a successful read.
+ *
+ * @param aBytesRead
+ * The number of bytes actually read.
+ */
+ void recordRead(in unsigned long aBytesRead);
+
+ /**
+ * Return the number of bytes allowed through this queue. This is
+ * the sum of all the values passed to recordRead. This method is
+ * primarily useful for testing.
+ */
+ unsigned long long bytesProcessed();
+
+ /**
+ * Wrap the given input stream in a new input stream which
+ * throttles the incoming data.
+ *
+ * @param aInputStream the input stream to wrap
+ * @return a new input stream that throttles the data.
+ */
+ nsIAsyncInputStream wrapStream(in nsIInputStream aInputStream);
+};
+
+/**
+ * A throttled input channel can be managed by an
+ * nsIInputChannelThrottleQueue to limit how much data is sent during
+ * a given time slice.
+ */
+[scriptable, uuid(0a32a100-c031-45b6-9e8b-0444c7d4a143)]
+interface nsIThrottledInputChannel : nsISupports
+{
+ /**
+ * The queue that manages this channel. Multiple channels can
+ * share a single queue. A null value means that no throttling
+ * will be done.
+ */
+ attribute nsIInputChannelThrottleQueue throttleQueue;
+};
diff --git a/netwerk/base/nsITimedChannel.idl b/netwerk/base/nsITimedChannel.idl
new file mode 100644
index 0000000000..6ec2d1ff80
--- /dev/null
+++ b/netwerk/base/nsITimedChannel.idl
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIPrincipal;
+%{C++
+namespace mozilla {
+class TimeStamp;
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+// All properties return zero if the value is not available
+[scriptable, uuid(ca63784d-959c-4c3a-9a59-234a2a520de0)]
+interface nsITimedChannel : nsISupports {
+ // Set this attribute to true to enable collection of timing data.
+ // channelCreationTime will be available even with this attribute set to
+ // false.
+ attribute boolean timingEnabled;
+
+ // The number of redirects
+ attribute uint16_t redirectCount;
+
+ [noscript] readonly attribute TimeStamp channelCreation;
+ [noscript] readonly attribute TimeStamp asyncOpen;
+
+ // The following are only set when the document is not (only) read from the
+ // cache
+ [noscript] readonly attribute TimeStamp domainLookupStart;
+ [noscript] readonly attribute TimeStamp domainLookupEnd;
+ [noscript] readonly attribute TimeStamp connectStart;
+ [noscript] readonly attribute TimeStamp connectEnd;
+ [noscript] readonly attribute TimeStamp requestStart;
+ [noscript] readonly attribute TimeStamp responseStart;
+ [noscript] readonly attribute TimeStamp responseEnd;
+
+ // The redirect attributes timings must be writeble, se we can transfer
+ // the data from one channel to the redirected channel.
+ [noscript] attribute TimeStamp redirectStart;
+ [noscript] attribute TimeStamp redirectEnd;
+
+ // The initiator type
+ [noscript] attribute AString initiatorType;
+
+ // This flag should be set to false only if a cross-domain redirect occurred
+ [noscript] attribute boolean allRedirectsSameOrigin;
+ // This flag is set to false if the timing allow check fails
+ [noscript] attribute boolean allRedirectsPassTimingAllowCheck;
+ // Implements the timing-allow-check to determine if we should report
+ // timing info for the resourceTiming object.
+ [noscript] boolean timingAllowCheck(in nsIPrincipal origin);
+ %{C++
+ inline bool TimingAllowCheck(nsIPrincipal* aOrigin) {
+ bool allowed = false;
+ return NS_SUCCEEDED(TimingAllowCheck(aOrigin, &allowed)) && allowed;
+ }
+ %}
+
+ // The following are only set if the document is (partially) read from the
+ // cache
+ [noscript] readonly attribute TimeStamp cacheReadStart;
+ [noscript] readonly attribute TimeStamp cacheReadEnd;
+
+ // All following are PRTime versions of the above.
+ readonly attribute PRTime channelCreationTime;
+ readonly attribute PRTime asyncOpenTime;
+ readonly attribute PRTime domainLookupStartTime;
+ readonly attribute PRTime domainLookupEndTime;
+ readonly attribute PRTime connectStartTime;
+ readonly attribute PRTime connectEndTime;
+ readonly attribute PRTime requestStartTime;
+ readonly attribute PRTime responseStartTime;
+ readonly attribute PRTime responseEndTime;
+ readonly attribute PRTime cacheReadStartTime;
+ readonly attribute PRTime cacheReadEndTime;
+ readonly attribute PRTime redirectStartTime;
+ readonly attribute PRTime redirectEndTime;
+};
diff --git a/netwerk/base/nsITraceableChannel.idl b/netwerk/base/nsITraceableChannel.idl
new file mode 100644
index 0000000000..d639d3310c
--- /dev/null
+++ b/netwerk/base/nsITraceableChannel.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+/**
+ * A channel implementing this interface allows one to intercept its data by
+ * inserting intermediate stream listeners.
+ */
+[scriptable, uuid(68167b0b-ef34-4d79-a09a-8045f7c5140e)]
+interface nsITraceableChannel : nsISupports
+{
+ /*
+ * Replace the channel's listener with a new one, and return the listener
+ * the channel used to have. The new listener intercepts OnStartRequest,
+ * OnDataAvailable and OnStopRequest calls and must pass them to
+ * the original listener after examination. If multiple callers replace
+ * the channel's listener, a chain of listeners is created.
+ * The caller of setNewListener has no way to control at which place
+ * in the chain its listener is placed.
+ *
+ * Note: The caller of setNewListener must not delay passing
+ * OnStartRequest to the original listener.
+ *
+ * Note2: A channel may restrict when the listener can be replaced.
+ * It is not recommended to allow listener replacement after OnStartRequest
+ * has been called.
+ */
+ nsIStreamListener setNewListener(in nsIStreamListener aListener);
+};
diff --git a/netwerk/base/nsITransport.idl b/netwerk/base/nsITransport.idl
new file mode 100644
index 0000000000..2730c3a29a
--- /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..9a92bba2b9
--- /dev/null
+++ b/netwerk/base/nsIUDPSocket.idl
@@ -0,0 +1,353 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsINetAddr;
+interface nsIUDPSocketListener;
+interface nsIUDPMessage;
+interface nsISocketTransport;
+interface nsIOutputStream;
+interface nsIInputStream;
+interface nsIPrincipal;
+
+%{ C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+[ref] native Uint8TArrayRef(FallibleTArray<uint8_t>);
+
+/**
+ * nsIUDPSocket
+ *
+ * An interface to a UDP socket that can accept incoming connections.
+ */
+[scriptable, uuid(d423bf4e-4499-40cf-bc03-153e2bf206d1)]
+interface nsIUDPSocket : nsISupports
+{
+ /**
+ * init
+ *
+ * This method initializes a UDP socket.
+ *
+ * @param aPort
+ * The port of the UDP socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the UDP socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [optional_argc] void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ [optional_argc] void init2(in AUTF8String aAddr,
+ in long aPort,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a UDP socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this UDP socket should be bound.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [noscript, optional_argc] void initWithAddress([const] in NetAddrPtr aAddr,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * close
+ *
+ * This method closes a UDP socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this UDP socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the UDP socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIUDPSocketListener aListener);
+
+ /**
+ * connect
+ *
+ * This method connects the UDP socket to a remote UDP address.
+ *
+ * @param aRemoteAddr
+ * The remote address to connect to
+ */
+ void connect([const] in NetAddrPtr aAddr);
+
+ /**
+ * Returns the local address of this UDP socket
+ */
+ readonly attribute nsINetAddr localAddr;
+
+ /**
+ * Returns the port of this UDP socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this UDP socket is bound. Since a
+ * UDP socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a UDP socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] NetAddr getAddress();
+
+ /**
+ * send
+ *
+ * Send out the datagram to specified remote host and port.
+ * DNS lookup will be triggered.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param data The buffer containing the data to be written.
+ * @param dataLength The maximum number of bytes to be written.
+ * @return number of bytes written. (0 or dataLength)
+ */
+ unsigned long send(in AUTF8String host, in unsigned short port,
+ [const, array, size_is(dataLength)]in uint8_t data,
+ in unsigned long dataLength);
+
+ /**
+ * sendWithAddr
+ *
+ * Send out the datagram to specified remote host and port.
+ *
+ * @param addr The remote host address.
+ * @param data The buffer containing the data to be written.
+ * @param dataLength The maximum number of bytes to be written.
+ * @return number of bytes written. (0 or dataLength)
+ */
+ unsigned long sendWithAddr(in nsINetAddr addr,
+ [const, array, size_is(dataLength)]in uint8_t data,
+ in unsigned long dataLength);
+
+ /**
+ * sendWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param data The buffer containing the data to be written.
+ * @param dataLength The maximum number of bytes to be written.
+ * @return number of bytes written. (0 or dataLength)
+ */
+ [noscript] unsigned long sendWithAddress([const] in NetAddrPtr addr,
+ [const, array, size_is(dataLength)]in uint8_t data,
+ in unsigned long dataLength);
+
+ /**
+ * sendBinaryStream
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStream(in AUTF8String host, in unsigned short port,
+ in nsIInputStream stream);
+
+ /**
+ * sendBinaryStreamWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStreamWithAddress([const] in NetAddrPtr addr,
+ in nsIInputStream stream);
+
+ /**
+ * joinMulticast
+ *
+ * Join the multicast group specified by |addr|. You are then able to
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to join the group. If
+ * this is not specified, the OS may join the group on all interfaces
+ * or only the primary interface.
+ */
+ void joinMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void joinMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * leaveMulticast
+ *
+ * Leave the multicast group specified by |addr|. You will no longer
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to leave the group.
+ * If this is not specified, the OS may leave the group on all
+ * interfaces or only the primary interface.
+ */
+ void leaveMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void leaveMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * multicastLoopback
+ *
+ * Whether multicast datagrams sent via this socket should be looped back to
+ * this host (assuming this host has joined the relevant group). Defaults
+ * to true.
+ * Note: This is currently write-only.
+ */
+ attribute boolean multicastLoopback;
+
+ /**
+ * multicastInterface
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ attribute AUTF8String multicastInterface;
+
+ /**
+ * multicastInterfaceAddr
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ [noscript] attribute NetAddr multicastInterfaceAddr;
+
+ /**
+ * recvBufferSize
+ *
+ * The size of the receive buffer. Default depends on the OS.
+ */
+ [noscript] attribute long recvBufferSize;
+
+ /**
+ * sendBufferSize
+ *
+ * The size of the send buffer. Default depends on the OS.
+ */
+ [noscript] attribute long sendBufferSize;
+};
+
+/**
+ * nsIUDPSocketListener
+ *
+ * This interface is notified whenever a UDP socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(2E4B5DD3-7358-4281-B81F-10C62EF39CB5)]
+interface nsIUDPSocketListener : nsISupports
+{
+ /**
+ * onPacketReceived
+ *
+ * This method is called when a client sends an UDP packet.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aMessage
+ * The message.
+ */
+ void onPacketReceived(in nsIUDPSocket aSocket,
+ in nsIUDPMessage aMessage);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The UDP socket is effectively dead after this notification.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aStatus
+ * The reason why the UDP socket stopped listening. If the
+ * UDP socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus);
+};
+
+/**
+ * nsIUDPMessage
+ *
+ * This interface is used to encapsulate an incomming UDP message
+ */
+[scriptable, uuid(afdc743f-9cc0-40d8-b442-695dc54bbb74)]
+interface nsIUDPMessage : nsISupports
+{
+ /**
+ * Address of the source of the message
+ */
+ readonly attribute nsINetAddr fromAddr;
+
+ /**
+ * Data of the message
+ */
+ readonly attribute ACString data;
+
+ /**
+ * Stream to send a response
+ */
+ readonly attribute nsIOutputStream outputStream;
+
+ /**
+ * Raw Data of the message
+ */
+ [implicit_jscontext] readonly attribute jsval rawData;
+ [noscript, notxpcom, nostdcall] Uint8TArrayRef getDataAsTArray();
+};
diff --git a/netwerk/base/nsIURI.idl b/netwerk/base/nsIURI.idl
new file mode 100644
index 0000000000..2384c5fd99
--- /dev/null
+++ b/netwerk/base/nsIURI.idl
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * URIs are essentially structured names for things -- anything. This interface
+ * provides accessors to set and query the most basic components of an URI.
+ * Subclasses, including nsIURL, impose greater structure on the URI.
+ *
+ * This interface follows Tim Berners-Lee's URI spec (RFC2396) [1], where the
+ * basic URI components are defined as such:
+ * <pre>
+ * ftp://username:password@hostname:portnumber/pathname#ref
+ * \ / \ / \ / \ /\ \ /
+ * - --------------- ------ -------- | -
+ * | | | | | |
+ * | | | | | Ref
+ * | | | Port \ /
+ * | | Host / --------
+ * | UserPass / |
+ * Scheme / Path
+ * \ /
+ * --------------------------------
+ * |
+ * PrePath
+ * </pre>
+ * The definition of the URI components has been extended to allow for
+ * internationalized domain names [2] and the more generic IRI structure [3].
+ *
+ * Note also that the RFC defines #-separated fragment identifiers as being
+ * "not part of the URI". Despite this, we bundle them as part of the URI, for
+ * convenience.
+ *
+ * [1] http://www.ietf.org/rfc/rfc2396.txt
+ * [2] http://www.ietf.org/internet-drafts/draft-ietf-idn-idna-06.txt
+ * [3] http://www.ietf.org/internet-drafts/draft-masinter-url-i18n-08.txt
+ */
+
+%{C++
+#include "nsStringGlue.h"
+
+#undef GetPort // XXX Windows!
+#undef SetPort // XXX Windows!
+%}
+
+/**
+ * nsIURI - interface for an uniform resource identifier w/ i18n support.
+ *
+ * AUTF8String attributes may contain unescaped UTF-8 characters.
+ * Consumers should be careful to escape the UTF-8 strings as necessary, but
+ * should always try to "display" the UTF-8 version as provided by this
+ * interface.
+ *
+ * AUTF8String attributes may also contain escaped characters.
+ *
+ * Unescaping URI segments is unadvised unless there is intimate
+ * knowledge of the underlying charset or there is no plan to display (or
+ * otherwise enforce a charset on) the resulting URI substring.
+ *
+ * The correct way to create an nsIURI from a string is via
+ * nsIIOService.newURI.
+ *
+ * NOTE: nsBinaryInputStream::ReadObject contains a hackaround to intercept the
+ * old (pre-gecko6) nsIURI IID and swap in the current IID instead, in order
+ * for sessionstore to work after an upgrade. If this IID is revved further,
+ * we will need to add additional checks there for all intermediate IIDs, until
+ * nsPrincipal is fixed to serialize its URIs as nsISupports (bug 662693).
+ */
+[scriptable, uuid(92073a54-6d78-4f30-913a-b871813208c6)]
+interface nsIURI : nsISupports
+{
+ /************************************************************************
+ * The URI is broken down into the following principal components:
+ */
+
+ /**
+ * Returns a string representation of the URI. Setting the spec causes
+ * the new spec to be parsed per the rules for the scheme the URI
+ * currently has. In particular, setting the spec to a URI string with a
+ * different scheme will generally produce incorrect results; no one
+ * outside of a protocol handler implementation should be doing that. If
+ * the URI stores information from the nsIIOService.newURI call used to
+ * create it other than just the parsed string, then behavior of this
+ * information on setting the spec attribute is undefined.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String spec;
+
+%{ C++
+ // An infallible wrapper for GetSpec() that returns a failure indication
+ // string if GetSpec() fails. It is most useful for creating
+ // logging/warning/error messages produced for human consumption, and when
+ // matching a URI spec against a fixed spec such as about:blank.
+ nsCString GetSpecOrDefault()
+ {
+ nsCString spec;
+ nsresult rv = GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ spec.Assign("[nsIURI::GetSpec failed]");
+ }
+ return spec;
+ }
+%}
+
+ /**
+ * The prePath (eg. scheme://user:password@host:port) returns the string
+ * before the path. This is useful for authentication or managing sessions.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String prePath;
+
+ /**
+ * The Scheme is the protocol to which this URI refers. The scheme is
+ * restricted to the US-ASCII charset per RFC2396. Setting this is
+ * highly discouraged outside of a protocol handler implementation, since
+ * that will generally lead to incorrect results.
+ */
+ attribute ACString scheme;
+
+ /**
+ * The username:password (or username only if value doesn't contain a ':')
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String userPass;
+
+ /**
+ * The optional username and password, assuming the preHost consists of
+ * username:password.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String username;
+ attribute AUTF8String password;
+
+ /**
+ * The host:port (or simply the host, if port == -1).
+ *
+ * If this attribute is set to a value that only has a host part, the port
+ * will not be reset. To reset the port as well use setHostAndPort.
+ *
+ * Characters are NOT escaped.
+ */
+ attribute AUTF8String hostPort;
+
+ /**
+ * This function will always set a host and a port. If the port part is
+ * empty, the value of the port will be set to the default value.
+ */
+ void setHostAndPort(in AUTF8String hostport);
+
+ /**
+ * The host is the internet domain name to which this URI refers. It could
+ * be an IPv4 (or IPv6) address literal. If supported, it could be a
+ * non-ASCII internationalized domain name.
+ *
+ * Characters are NOT escaped.
+ */
+ attribute AUTF8String host;
+
+ /**
+ * A port value of -1 corresponds to the protocol's default port (eg. -1
+ * implies port 80 for http URIs).
+ */
+ attribute long port;
+
+ /**
+ * The path, typically including at least a leading '/' (but may also be
+ * empty, depending on the protocol).
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String path;
+
+
+ /************************************************************************
+ * An URI supports the following methods:
+ */
+
+ /**
+ * URI equivalence test (not a strict string comparison).
+ *
+ * eg. http://foo.com:80/ == http://foo.com/
+ */
+ boolean equals(in nsIURI other);
+
+ /**
+ * An optimization to do scheme checks without requiring the users of nsIURI
+ * to GetScheme, thereby saving extra allocating and freeing. Returns true if
+ * the schemes match (case ignored).
+ */
+ boolean schemeIs(in string scheme);
+
+ /**
+ * Clones the current URI.
+ */
+ nsIURI clone();
+
+ /**
+ * This method resolves a relative string into an absolute URI string,
+ * using this URI as the base.
+ *
+ * NOTE: some implementations may have no concept of a relative URI.
+ */
+ AUTF8String resolve(in AUTF8String relativePath);
+
+
+ /************************************************************************
+ * Additional attributes:
+ */
+
+ /**
+ * The URI spec with an ASCII compatible encoding. Host portion follows
+ * the IDNA draft spec. Other parts are URL-escaped per the rules of
+ * RFC2396. The result is strictly ASCII.
+ */
+ readonly attribute ACString asciiSpec;
+
+ /**
+ * The host:port (or simply the host, if port == -1), with an ASCII compatible
+ * encoding. Host portion follows the IDNA draft spec. The result is strictly
+ * ASCII.
+ */
+ readonly attribute ACString asciiHostPort;
+
+ /**
+ * The URI host with an ASCII compatible encoding. Follows the IDNA
+ * draft spec for converting internationalized domain names (UTF-8) to
+ * ASCII for compatibility with existing internet infrasture.
+ */
+ readonly attribute ACString asciiHost;
+
+ /**
+ * The charset of the document from which this URI originated. An empty
+ * value implies UTF-8.
+ *
+ * If this value is something other than UTF-8 then the URI components
+ * (e.g., spec, prePath, username, etc.) will all be fully URL-escaped.
+ * Otherwise, the URI components may contain unescaped multibyte UTF-8
+ * characters.
+ */
+ readonly attribute ACString originCharset;
+
+ /************************************************************************
+ * Additional attribute & methods added for .ref support:
+ */
+
+ /**
+ * Returns the reference portion (the part after the "#") of the URI.
+ * If there isn't one, an empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String ref;
+
+ /**
+ * URI equivalence test (not a strict string comparison), ignoring
+ * the value of the .ref member.
+ *
+ * eg. http://foo.com/# == http://foo.com/
+ * http://foo.com/#aaa == http://foo.com/#bbb
+ */
+ boolean equalsExceptRef(in nsIURI other);
+
+ /**
+ * Clones the current URI, clearing the 'ref' attribute in the clone.
+ */
+ nsIURI cloneIgnoringRef();
+
+ /**
+ * Clones the current URI, replacing the 'ref' attribute in the clone with
+ * the ref supplied.
+ */
+ nsIURI cloneWithNewRef(in AUTF8String newRef);
+
+ /**
+ * returns a string for the current URI with the ref element cleared.
+ */
+ readonly attribute AUTF8String specIgnoringRef;
+
+ /**
+ * Returns if there is a reference portion (the part after the "#") of the URI.
+ */
+ readonly attribute boolean hasRef;
+};
diff --git a/netwerk/base/nsIURIClassifier.idl b/netwerk/base/nsIURIClassifier.idl
new file mode 100644
index 0000000000..a8f6098a78
--- /dev/null
+++ b/netwerk/base/nsIURIClassifier.idl
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * Callback function for nsIURIClassifier lookups.
+ */
+[scriptable, function, uuid(8face46e-0c96-470f-af40-0037dcd797bd)]
+interface nsIURIClassifierCallback : nsISupports
+{
+ /**
+ * Called by the URI classifier service when it is done checking a URI.
+ *
+ * Clients are responsible for associating callback objects with classify()
+ * calls.
+ *
+ * @param aErrorCode
+ * The error code with which the channel should be cancelled, or
+ * NS_OK if the load should continue normally.
+ */
+ void onClassifyComplete(in nsresult aErrorCode);
+};
+
+/**
+ * The URI classifier service checks a URI against lists of phishing
+ * and malware sites.
+ */
+[scriptable, uuid(596620cc-76e3-4133-9d90-360e59a794cf)]
+interface nsIURIClassifier : nsISupports
+{
+ /**
+ * Classify a Principal using its URI.
+ *
+ * @param aPrincipal
+ * The principal that should be checked by the URI classifier.
+ * @param aTrackingProtectionEnabled
+ * Whether or not to classify the given URI against tracking
+ * protection lists
+ *
+ * @param aCallback
+ * The URI classifier will call this callback when the URI has been
+ * classified.
+ *
+ * @return <code>false</code> if classification is not necessary. The
+ * callback will not be called.
+ * <code>true</code> if classification will be performed. The
+ * callback will be called.
+ */
+ boolean classify(in nsIPrincipal aPrincipal,
+ in boolean aTrackingProtectionEnabled,
+ in nsIURIClassifierCallback aCallback);
+
+ /**
+ * Synchronously classify a URI with a comma-separated string
+ * containing the given tables. This does not make network requests.
+ * The result is a comma-separated string of tables that match.
+ */
+ ACString classifyLocalWithTables(in nsIURI aURI, in ACString aTables);
+};
diff --git a/netwerk/base/nsIURIWithBlobImpl.idl b/netwerk/base/nsIURIWithBlobImpl.idl
new file mode 100644
index 0000000000..d055746cce
--- /dev/null
+++ b/netwerk/base/nsIURIWithBlobImpl.idl
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsIURIWithBlobImpl is implemented by URIs which are associated with a
+ * specific BlobImpl.
+ */
+[builtinclass, uuid(331b41d3-3506-4ab5-bef9-aab41e3202a3)]
+interface nsIURIWithBlobImpl : nsISupports
+{
+ /**
+ * The BlobImpl associated with the resource returned when loading this uri.
+ */
+ readonly attribute nsISupports blobImpl;
+};
diff --git a/netwerk/base/nsIURIWithPrincipal.idl b/netwerk/base/nsIURIWithPrincipal.idl
new file mode 100644
index 0000000000..6c63aaad9b
--- /dev/null
+++ b/netwerk/base/nsIURIWithPrincipal.idl
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * nsIURIWithPrincipal is implemented by URIs which are associated with a
+ * specific principal.
+ */
+[scriptable, uuid(626a5c0c-bfd8-4531-8b47-a8451b0daa33)]
+interface nsIURIWithPrincipal : nsISupports
+{
+ /**
+ * The principal associated with the resource returned when loading this
+ * uri.
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * The uri for the principal.
+ */
+ readonly attribute nsIURI principalUri;
+};
diff --git a/netwerk/base/nsIURIWithQuery.idl b/netwerk/base/nsIURIWithQuery.idl
new file mode 100644
index 0000000000..749b2773d8
--- /dev/null
+++ b/netwerk/base/nsIURIWithQuery.idl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.idl"
+
+/**
+ * nsIURIWithQuery is implemented by URIs which have a query parameter.
+ * This is useful for the URL API.
+ */
+[scriptable, uuid(367510ee-8556-435a-8f99-b5fd357e08cc)]
+interface nsIURIWithQuery : nsIURI
+{
+ /**
+ * Returns a path including the directory and file portions of a
+ * URL. For example, the filePath of "http://host/foo/bar.html#baz"
+ * is "/foo/bar.html".
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String filePath;
+
+ /**
+ * Returns the query portion (the part after the "?") of the URL.
+ * If there isn't one, an empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String query;
+};
diff --git a/netwerk/base/nsIURL.idl b/netwerk/base/nsIURL.idl
new file mode 100644
index 0000000000..aeaa3f6949
--- /dev/null
+++ b/netwerk/base/nsIURL.idl
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURIWithQuery.idl"
+
+/**
+ * The nsIURL interface provides convenience methods that further
+ * break down the path portion of nsIURI:
+ *
+ * http://host/directory/fileBaseName.fileExtension?query
+ * http://host/directory/fileBaseName.fileExtension#ref
+ * \ \ /
+ * \ -----------------------
+ * \ | /
+ * \ fileName /
+ * ----------------------------
+ * |
+ * filePath
+ */
+[scriptable, uuid(86adcd89-0b70-47a2-b0fe-5bb2c5f37e31)]
+interface nsIURL : nsIURIWithQuery
+{
+ /*************************************************************************
+ * The URL path is broken down into the following principal components:
+ *
+ * attribute AUTF8String filePath;
+ * attribute AUTF8String query;
+ *
+ * These are inherited from nsIURIWithQuery.
+ */
+
+ /*************************************************************************
+ * The URL filepath is broken down into the following sub-components:
+ */
+
+ /**
+ * Returns the directory portion of a URL. If the URL denotes a path to a
+ * directory and not a file, e.g. http://host/foo/bar/, then the Directory
+ * attribute accesses the complete /foo/bar/ portion, and the FileName is
+ * the empty string. If the trailing slash is omitted, then the Directory
+ * is /foo/ and the file is bar (i.e. this is a syntactic, not a semantic
+ * breakdown of the Path). And hence don't rely on this for something to
+ * be a definitely be a file. But you can get just the leading directory
+ * portion for sure.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String directory;
+
+ /**
+ * Returns the file name portion of a URL. If the URL denotes a path to a
+ * directory and not a file, e.g. http://host/foo/bar/, then the Directory
+ * attribute accesses the complete /foo/bar/ portion, and the FileName is
+ * the empty string. Note that this is purely based on searching for the
+ * last trailing slash. And hence don't rely on this to be a definite file.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String fileName;
+
+
+ /*************************************************************************
+ * The URL filename is broken down even further:
+ */
+
+ /**
+ * Returns the file basename portion of a filename in a url.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String fileBaseName;
+
+ /**
+ * Returns the file extension portion of a filename in a url. If a file
+ * extension does not exist, the empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String fileExtension;
+
+ /**
+ * This method takes a uri and compares the two. The common uri portion
+ * is returned as a string. The minimum common uri portion is the
+ * protocol, and any of these if present: login, password, host and port
+ * If no commonality is found, "" is returned. If they are identical, the
+ * whole path with file/ref/etc. is returned. For file uris, it is
+ * expected that the common spec would be at least "file:///" since '/' is
+ * a shared common root.
+ *
+ * Examples:
+ * this.spec aURIToCompare.spec result
+ * 1) http://mozilla.org/ http://www.mozilla.org/ ""
+ * 2) http://foo.com/bar/ ftp://foo.com/bar/ ""
+ * 3) http://foo.com:8080/ http://foo.com/bar/ ""
+ * 4) ftp://user@foo.com/ ftp://user:pw@foo.com/ ""
+ * 5) ftp://foo.com/bar/ ftp://foo.com/bar ftp://foo.com/
+ * 6) ftp://foo.com/bar/ ftp://foo.com/bar/b.html ftp://foo.com/bar/
+ * 7) http://foo.com/a.htm#i http://foo.com/b.htm http://foo.com/
+ * 8) ftp://foo.com/c.htm#i ftp://foo.com/c.htm ftp://foo.com/c.htm
+ * 9) file:///a/b/c.html file:///d/e/c.html file:///
+ */
+ AUTF8String getCommonBaseSpec(in nsIURI aURIToCompare);
+
+ /**
+ * This method tries to create a string which specifies the location of the
+ * argument relative to |this|. If the argument and |this| are equal, the
+ * method returns "". If any of the URIs' scheme, host, userpass, or port
+ * don't match, the method returns the full spec of the argument.
+ *
+ * Examples:
+ * this.spec aURIToCompare.spec result
+ * 1) http://mozilla.org/ http://www.mozilla.org/ http://www.mozilla.org/
+ * 2) http://mozilla.org/ http://www.mozilla.org http://www.mozilla.org/
+ * 3) http://foo.com/bar/ http://foo.com:80/bar/ ""
+ * 4) http://foo.com/ http://foo.com/a.htm#b a.html#b
+ * 5) http://foo.com/a/b/ http://foo.com/c ../../c
+ */
+ AUTF8String getRelativeSpec(in nsIURI aURIToCompare);
+
+};
diff --git a/netwerk/base/nsIURLParser.idl b/netwerk/base/nsIURLParser.idl
new file mode 100644
index 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/nsIUnicharStreamLoader.idl b/netwerk/base/nsIUnicharStreamLoader.idl
new file mode 100644
index 0000000000..d4cdc3fb34
--- /dev/null
+++ b/netwerk/base/nsIUnicharStreamLoader.idl
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIUnicharInputStream;
+interface nsIUnicharStreamLoader;
+interface nsIChannel;
+
+[scriptable, uuid(c2982b39-2e48-429e-92b7-99348a1633c5)]
+interface nsIUnicharStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called as soon as at least 512 octets of data have arrived.
+ * If the stream receives fewer than 512 octets of data in total,
+ * called upon stream completion but before calling OnStreamComplete.
+ * Will not be called if the stream receives no data at all.
+ *
+ * @param aLoader the unichar stream loader
+ * @param aContext the context parameter of the underlying channel
+ * @param aSegment up to 512 octets of raw data from the stream
+ *
+ * @return the name of the character set to be used to decode this stream
+ */
+ ACString onDetermineCharset(in nsIUnicharStreamLoader aLoader,
+ in nsISupports aContext,
+ in ACString aSegment);
+
+ /**
+ * Called when the entire stream has been loaded and decoded.
+ *
+ * @param aLoader the unichar stream loader
+ * @param aContext the context parameter of the underlying channel
+ * @param aStatus the status of the underlying channel
+ * @param aBuffer the contents of the stream, decoded to UTF-16.
+ *
+ * This method will always be called asynchronously by the
+ * nsUnicharIStreamLoader involved, on the thread that called the
+ * loader's init() method. If onDetermineCharset fails,
+ * onStreamComplete will still be called, but aStatus will be an
+ * error code.
+ */
+ void onStreamComplete(in nsIUnicharStreamLoader aLoader,
+ in nsISupports aContext,
+ in nsresult aStatus,
+ in AString aBuffer);
+};
+
+/**
+ * Asynchronously load a channel, converting the data to UTF-16.
+ *
+ * To use this interface, first call init() with a
+ * nsIUnicharStreamLoaderObserver that will be notified when the data has been
+ * loaded. Then call asyncOpen() on the channel with the nsIUnicharStreamLoader
+ * as the listener. The context argument in the asyncOpen() call will be
+ * passed to the onStreamComplete() callback.
+ */
+[scriptable, uuid(afb62060-37c7-4713-8a84-4a0c1199ba5c)]
+interface nsIUnicharStreamLoader : nsIStreamListener
+{
+ /**
+ * Initializes the unichar stream loader
+ *
+ * @param aObserver the observer to notify when a charset is needed and when
+ * the load is complete
+ */
+ void init(in nsIUnicharStreamLoaderObserver aObserver);
+
+ /**
+ * The channel attribute is only valid inside the onDetermineCharset
+ * and onStreamComplete callbacks. Otherwise it will be null.
+ */
+ readonly attribute nsIChannel channel;
+
+ /**
+ * The charset that onDetermineCharset returned, if that's been
+ * called.
+ */
+ readonly attribute ACString charset;
+
+ /**
+ * Get the raw bytes as seen on the wire prior to character converstion.
+ * Used by Subresource Integrity checker to generate the correct hash.
+ */
+ readonly attribute ACString rawBuffer;
+};
diff --git a/netwerk/base/nsIUploadChannel.idl b/netwerk/base/nsIUploadChannel.idl
new file mode 100644
index 0000000000..3ec81444a1
--- /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..2ea74ac358
--- /dev/null
+++ b/netwerk/base/nsIUploadChannel2.idl
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIRunnable;
+
+[scriptable, uuid(2f712b52-19c5-4e0c-9e8f-b5c7c3b67049)]
+interface nsIUploadChannel2 : nsISupports
+{
+ /**
+ * Sets a stream to be uploaded by this channel with the specified
+ * Content-Type and Content-Length header values.
+ *
+ * Most implementations of this interface require that the stream:
+ * (1) implement threadsafe addRef and release
+ * (2) implement nsIInputStream::readSegments
+ * (3) implement nsISeekableStream::seek
+ *
+ * @param aStream
+ * The stream to be uploaded by this channel.
+ * @param aContentType
+ * This value will replace any existing Content-Type
+ * header on the HTTP request, regardless of whether
+ * or not its empty.
+ * @param aContentLength
+ * A value of -1 indicates that the length of the stream should be
+ * determined by calling the stream's |available| method.
+ * @param aMethod
+ * The HTTP request method to set on the stream.
+ * @param aStreamHasHeaders
+ * True if the stream already contains headers for the HTTP request.
+ */
+ void explicitSetUploadStream(in nsIInputStream aStream,
+ in ACString aContentType,
+ in long long aContentLength,
+ in ACString aMethod,
+ in boolean aStreamHasHeaders);
+
+ /**
+ * Value of aStreamHasHeaders from the last successful call to
+ * explicitSetUploadStream. TRUE indicates the attached upload stream
+ * contians request headers.
+ */
+ readonly attribute boolean uploadStreamHasHeaders;
+
+ /**
+ * Ensure the upload stream, if any, is cloneable. This may involve
+ * async copying, so a callback runnable must be provided. It will
+ * invoked on the current thread when the upload stream is ready
+ * for cloning. If the stream is already cloneable, then the callback
+ * will be invoked synchronously.
+ */
+ [noscript]
+ void ensureUploadStreamIsCloneable(in nsIRunnable aCallback);
+
+ /**
+ * Clones the upload stream. May return failure if the upload stream
+ * is not cloneable. If this is not acceptable, use the
+ * ensureUploadStreamIsCloneable() method first.
+ */
+ [noscript]
+ nsIInputStream cloneUploadStream();
+};
diff --git a/netwerk/base/nsIncrementalDownload.cpp b/netwerk/base/nsIncrementalDownload.cpp
new file mode 100644
index 0000000000..42cd6faa5e
--- /dev/null
+++ b/netwerk/base/nsIncrementalDownload.cpp
@@ -0,0 +1,936 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsIIncrementalDownload.h"
+#include "nsIRequestObserver.h"
+#include "nsIProgressEventSink.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsWeakReference.h"
+#include "prio.h"
+#include "prprf.h"
+#include <algorithm>
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "mozilla/UniquePtr.h"
+
+// Default values used to initialize a nsIncrementalDownload object.
+#define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
+#define DEFAULT_INTERVAL 60 // seconds
+
+#define UPDATE_PROGRESS_INTERVAL PRTime(500 * PR_USEC_PER_MSEC) // 500ms
+
+// Number of times to retry a failed byte-range request.
+#define MAX_RETRY_COUNT 20
+
+using namespace mozilla;
+
+//-----------------------------------------------------------------------------
+
+static nsresult
+WriteToFile(nsIFile *lf, const char *data, uint32_t len, int32_t flags)
+{
+ PRFileDesc *fd;
+ int32_t mode = 0600;
+ nsresult rv;
+#if defined(MOZ_WIDGET_GONK)
+ // The sdcard on a B2G phone looks like:
+ // d---rwx--- system sdcard_rw 1970-01-01 01:00:00 sdcard
+ // On the emulator, xpcshell fails when using 0600 mode to open the file,
+ // and 0660 works.
+ nsCOMPtr<nsIFile> parent;
+ rv = lf->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ uint32_t parentPerm;
+ rv = parent->GetPermissions(&parentPerm);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if ((parentPerm & 0700) == 0) {
+ // Parent directory has no owner-write, so try to use group permissions
+ // instead of owner permissions.
+ mode = 0660;
+ }
+#endif
+ rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (len)
+ rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
+
+ PR_Close(fd);
+ return rv;
+}
+
+static nsresult
+AppendToFile(nsIFile *lf, const char *data, uint32_t len)
+{
+ int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
+ return WriteToFile(lf, data, len, flags);
+}
+
+// maxSize may be -1 if unknown
+static void
+MakeRangeSpec(const int64_t &size, const int64_t &maxSize, int32_t chunkSize,
+ bool fetchRemaining, nsCString &rangeSpec)
+{
+ rangeSpec.AssignLiteral("bytes=");
+ rangeSpec.AppendInt(int64_t(size));
+ rangeSpec.Append('-');
+
+ if (fetchRemaining)
+ return;
+
+ int64_t end = size + int64_t(chunkSize);
+ if (maxSize != int64_t(-1) && end > maxSize)
+ end = maxSize;
+ end -= 1;
+
+ rangeSpec.AppendInt(int64_t(end));
+}
+
+//-----------------------------------------------------------------------------
+
+class nsIncrementalDownload final
+ : public nsIIncrementalDownload
+ , public nsIStreamListener
+ , public nsIObserver
+ , public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+ , public nsSupportsWeakReference
+ , public nsIAsyncVerifyRedirectCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINCREMENTALDOWNLOAD
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+ nsIncrementalDownload();
+
+private:
+ ~nsIncrementalDownload() {}
+ nsresult FlushChunk();
+ void UpdateProgress();
+ nsresult CallOnStartRequest();
+ void CallOnStopRequest();
+ nsresult StartTimer(int32_t interval);
+ nsresult ProcessTimeout();
+ nsresult ReadCurrentSize();
+ nsresult ClearRequestHeader(nsIHttpChannel *channel);
+
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsISupports> mObserverContext;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mFinalURI;
+ nsCOMPtr<nsIFile> mDest;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsITimer> mTimer;
+ mozilla::UniquePtr<char[]> mChunk;
+ int32_t mChunkLen;
+ int32_t mChunkSize;
+ int32_t mInterval;
+ int64_t mTotalSize;
+ int64_t mCurrentSize;
+ uint32_t mLoadFlags;
+ int32_t mNonPartialCount;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mDidOnStartRequest;
+ PRTime mLastProgressUpdate;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIChannel> mNewRedirectChannel;
+ nsCString mPartialValidator;
+ bool mCacheBust;
+};
+
+nsIncrementalDownload::nsIncrementalDownload()
+ : mChunkLen(0)
+ , mChunkSize(DEFAULT_CHUNK_SIZE)
+ , mInterval(DEFAULT_INTERVAL)
+ , mTotalSize(-1)
+ , mCurrentSize(-1)
+ , mLoadFlags(LOAD_NORMAL)
+ , mNonPartialCount(0)
+ , mStatus(NS_OK)
+ , mIsPending(false)
+ , mDidOnStartRequest(false)
+ , mLastProgressUpdate(0)
+ , mRedirectCallback(nullptr)
+ , mNewRedirectChannel(nullptr)
+ , mCacheBust(false)
+{
+}
+
+nsresult
+nsIncrementalDownload::FlushChunk()
+{
+ NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
+
+ if (mChunkLen == 0)
+ return NS_OK;
+
+ nsresult rv = AppendToFile(mDest, mChunk.get(), mChunkLen);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mCurrentSize += int64_t(mChunkLen);
+ mChunkLen = 0;
+
+ return NS_OK;
+}
+
+void
+nsIncrementalDownload::UpdateProgress()
+{
+ mLastProgressUpdate = PR_Now();
+
+ if (mProgressSink)
+ mProgressSink->OnProgress(this, mObserverContext,
+ mCurrentSize + mChunkLen,
+ mTotalSize);
+}
+
+nsresult
+nsIncrementalDownload::CallOnStartRequest()
+{
+ if (!mObserver || mDidOnStartRequest)
+ return NS_OK;
+
+ mDidOnStartRequest = true;
+ return mObserver->OnStartRequest(this, mObserverContext);
+}
+
+void
+nsIncrementalDownload::CallOnStopRequest()
+{
+ if (!mObserver)
+ return;
+
+ // Ensure that OnStartRequest is always called once before OnStopRequest.
+ nsresult rv = CallOnStartRequest();
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = rv;
+
+ mIsPending = false;
+
+ mObserver->OnStopRequest(this, mObserverContext, mStatus);
+ mObserver = nullptr;
+ mObserverContext = nullptr;
+}
+
+nsresult
+nsIncrementalDownload::StartTimer(int32_t interval)
+{
+ nsresult rv;
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
+}
+
+nsresult
+nsIncrementalDownload::ProcessTimeout()
+{
+ NS_ASSERTION(!mChannel, "how can we have a channel?");
+
+ // Handle existing error conditions
+ if (NS_FAILED(mStatus)) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ // Fetch next chunk
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+ mFinalURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // loadGroup
+ this, // aCallbacks
+ mLoadFlags);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ASSERTION(mCurrentSize != int64_t(-1),
+ "we should know the current file size by now");
+
+ rv = ClearRequestHeader(http);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Don't bother making a range request if we are just going to fetch the
+ // entire document.
+ if (mInterval || mCurrentSize != int64_t(0)) {
+ nsAutoCString range;
+ MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
+
+ rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!mPartialValidator.IsEmpty())
+ http->SetRequestHeader(NS_LITERAL_CSTRING("If-Range"),
+ mPartialValidator, false);
+
+ if (mCacheBust) {
+ http->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
+ NS_LITERAL_CSTRING("no-cache"), false);
+ http->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
+ NS_LITERAL_CSTRING("no-cache"), false);
+ }
+ }
+
+ rv = channel->AsyncOpen2(this);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Wait to assign mChannel when we know we are going to succeed. This is
+ // important because we don't want to introduce a reference cycle between
+ // mChannel and this until we know for a fact that AsyncOpen has succeeded,
+ // thus ensuring that our stream listener methods will be invoked.
+ mChannel = channel;
+ return NS_OK;
+}
+
+// Reads the current file size and validates it.
+nsresult
+nsIncrementalDownload::ReadCurrentSize()
+{
+ int64_t size;
+ nsresult rv = mDest->GetFileSize((int64_t *) &size);
+ if (rv == NS_ERROR_FILE_NOT_FOUND ||
+ rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ mCurrentSize = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ mCurrentSize = size;
+ return NS_OK;
+}
+
+// nsISupports
+
+NS_IMPL_ISUPPORTS(nsIncrementalDownload,
+ nsIIncrementalDownload,
+ nsIRequest,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIObserver,
+ nsIInterfaceRequestor,
+ nsIChannelEventSink,
+ nsISupportsWeakReference,
+ nsIAsyncVerifyRedirectCallback)
+
+// nsIRequest
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetName(nsACString &name)
+{
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+
+ return mURI->GetSpec(name);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::IsPending(bool *isPending)
+{
+ *isPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetStatus(nsresult *status)
+{
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Cancel(nsresult status)
+{
+ NS_ENSURE_ARG(NS_FAILED(status));
+
+ // Ignore this cancelation if we're already canceled.
+ if (NS_FAILED(mStatus))
+ return NS_OK;
+
+ mStatus = status;
+
+ // Nothing more to do if callbacks aren't pending.
+ if (!mIsPending)
+ return NS_OK;
+
+ if (mChannel) {
+ mChannel->Cancel(mStatus);
+ NS_ASSERTION(!mTimer, "what is this timer object doing here?");
+ }
+ else {
+ // dispatch a timer callback event to drive invoking our listener's
+ // OnStopRequest.
+ if (mTimer)
+ mTimer->Cancel();
+ StartTimer(0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Suspend()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Resume()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
+{
+ *loadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
+{
+ mLoadFlags = loadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIIncrementalDownload
+
+NS_IMETHODIMP
+nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
+ int32_t chunkSize, int32_t interval)
+{
+ // Keep it simple: only allow initialization once
+ NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
+
+ mDest = do_QueryInterface(dest);
+ NS_ENSURE_ARG(mDest);
+
+ mURI = uri;
+ mFinalURI = uri;
+
+ if (chunkSize > 0)
+ mChunkSize = chunkSize;
+ if (interval >= 0)
+ mInterval = interval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetURI(nsIURI **result)
+{
+ NS_IF_ADDREF(*result = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetFinalURI(nsIURI **result)
+{
+ NS_IF_ADDREF(*result = mFinalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetDestination(nsIFile **result)
+{
+ if (!mDest) {
+ *result = nullptr;
+ return NS_OK;
+ }
+ // Return a clone of mDest so that callers may modify the resulting nsIFile
+ // without corrupting our internal object. This also works around the fact
+ // that some nsIFile impls may cache the result of stat'ing the filesystem.
+ return mDest->Clone(result);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetTotalSize(int64_t *result)
+{
+ *result = mTotalSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetCurrentSize(int64_t *result)
+{
+ *result = mCurrentSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Start(nsIRequestObserver *observer,
+ nsISupports *context)
+{
+ NS_ENSURE_ARG(observer);
+ NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
+
+ // Observe system shutdown so we can be sure to release any reference held
+ // between ourselves and the timer. We have the observer service hold a weak
+ // reference to us, so that we don't have to worry about calling
+ // RemoveObserver. XXX(darin): The timer code should do this for us.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs)
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+
+ nsresult rv = ReadCurrentSize();
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = StartTimer(0);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mObserver = observer;
+ mObserverContext = context;
+ mProgressSink = do_QueryInterface(observer); // ok if null
+
+ mIsPending = true;
+ return NS_OK;
+}
+
+// nsIRequestObserver
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnStartRequest(nsIRequest *request,
+ nsISupports *context)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Ensure that we are receiving a 206 response.
+ uint32_t code;
+ rv = http->GetResponseStatus(&code);
+ if (NS_FAILED(rv))
+ return rv;
+ if (code != 206) {
+ // We may already have the entire file downloaded, in which case
+ // our request for a range beyond the end of the file would have
+ // been met with an error response code.
+ if (code == 416 && mTotalSize == int64_t(-1)) {
+ mTotalSize = mCurrentSize;
+ // Return an error code here to suppress OnDataAvailable.
+ return NS_ERROR_DOWNLOAD_COMPLETE;
+ }
+ // The server may have decided to give us all of the data in one chunk. If
+ // we requested a partial range, then we don't want to download all of the
+ // data at once. So, we'll just try again, but if this keeps happening then
+ // we'll eventually give up.
+ if (code == 200) {
+ if (mInterval) {
+ mChannel = nullptr;
+ if (++mNonPartialCount > MAX_RETRY_COUNT) {
+ NS_WARNING("unable to fetch a byte range; giving up");
+ return NS_ERROR_FAILURE;
+ }
+ // Increase delay with each failure.
+ StartTimer(mInterval * mNonPartialCount);
+ return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
+ }
+ // Since we have been asked to download the rest of the file, we can deal
+ // with a 200 response. This may result in downloading the beginning of
+ // the file again, but that can't really be helped.
+ } else {
+ NS_WARNING("server response was unexpected");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // We got a partial response, so clear this counter in case the next chunk
+ // results in a 200 response.
+ mNonPartialCount = 0;
+
+ // confirm that the content-range response header is consistent with
+ // expectations on each 206. If it is not then drop this response and
+ // retry with no-cache set.
+ if (!mCacheBust) {
+ nsAutoCString buf;
+ int64_t startByte = 0;
+ bool confirmedOK = false;
+
+ rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
+ if (NS_FAILED(rv))
+ return rv; // it isn't a useful 206 without a CONTENT-RANGE of some sort
+
+ // Content-Range: bytes 0-299999/25604694
+ int32_t p = buf.Find("bytes ");
+
+ // first look for the starting point of the content-range
+ // to make sure it is what we expect
+ if (p != -1) {
+ char *endptr = nullptr;
+ const char *s = buf.get() + p + 6;
+ while (*s && *s == ' ')
+ s++;
+ startByte = strtol(s, &endptr, 10);
+
+ if (*s && endptr && (endptr != s) &&
+ (mCurrentSize == startByte)) {
+
+ // ok the starting point is confirmed. We still need to check the
+ // total size of the range for consistency if this isn't
+ // the first chunk
+ if (mTotalSize == int64_t(-1)) {
+ // first chunk
+ confirmedOK = true;
+ } else {
+ int32_t slash = buf.FindChar('/');
+ int64_t rangeSize = 0;
+ if (slash != kNotFound &&
+ (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &rangeSize) == 1) &&
+ rangeSize == mTotalSize) {
+ confirmedOK = true;
+ }
+ }
+ }
+ }
+
+ if (!confirmedOK) {
+ NS_WARNING("unexpected content-range");
+ mCacheBust = true;
+ mChannel = nullptr;
+ if (++mNonPartialCount > MAX_RETRY_COUNT) {
+ NS_WARNING("unable to fetch a byte range; giving up");
+ return NS_ERROR_FAILURE;
+ }
+ // Increase delay with each failure.
+ StartTimer(mInterval * mNonPartialCount);
+ return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
+ }
+ }
+ }
+
+ // Do special processing after the first response.
+ if (mTotalSize == int64_t(-1)) {
+ // Update knowledge of mFinalURI
+ rv = http->GetURI(getter_AddRefs(mFinalURI));
+ if (NS_FAILED(rv))
+ return rv;
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Etag"), mPartialValidator);
+ if (StringBeginsWith(mPartialValidator, NS_LITERAL_CSTRING("W/")))
+ mPartialValidator.Truncate(); // don't use weak validators
+ if (mPartialValidator.IsEmpty())
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), mPartialValidator);
+
+ if (code == 206) {
+ // OK, read the Content-Range header to determine the total size of this
+ // download file.
+ nsAutoCString buf;
+ rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
+ if (NS_FAILED(rv))
+ return rv;
+ int32_t slash = buf.FindChar('/');
+ if (slash == kNotFound) {
+ NS_WARNING("server returned invalid Content-Range header!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &mTotalSize) != 1)
+ return NS_ERROR_UNEXPECTED;
+ } else {
+ rv = http->GetContentLength(&mTotalSize);
+ if (NS_FAILED(rv))
+ return rv;
+ // We need to know the total size of the thing we're trying to download.
+ if (mTotalSize == int64_t(-1)) {
+ NS_WARNING("server returned no content-length header!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Need to truncate (or create, if it doesn't exist) the file since we
+ // are downloading the whole thing.
+ WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
+ mCurrentSize = 0;
+ }
+
+ // Notify observer that we are starting...
+ rv = CallOnStartRequest();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
+ int64_t diff = mTotalSize - mCurrentSize;
+ if (diff <= int64_t(0)) {
+ NS_WARNING("about to set a bogus chunk size; giving up");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (diff < int64_t(mChunkSize))
+ mChunkSize = uint32_t(diff);
+
+ mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize);
+ if (!mChunk)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnStopRequest(nsIRequest *request,
+ nsISupports *context,
+ nsresult status)
+{
+ // Not a real error; just a trick to kill off the channel without our
+ // listener having to care.
+ if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
+ return NS_OK;
+
+ // Not a real error; just a trick used to suppress OnDataAvailable calls.
+ if (status == NS_ERROR_DOWNLOAD_COMPLETE)
+ status = NS_OK;
+
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = status;
+
+ if (mChunk) {
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = FlushChunk();
+
+ mChunk = nullptr; // deletes memory
+ mChunkLen = 0;
+ UpdateProgress();
+ }
+
+ mChannel = nullptr;
+
+ // Notify listener if we hit an error or finished
+ if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ return StartTimer(mInterval); // Do next chunk
+}
+
+// nsIStreamListener
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
+ nsISupports *context,
+ nsIInputStream *input,
+ uint64_t offset,
+ uint32_t count)
+{
+ while (count) {
+ uint32_t space = mChunkSize - mChunkLen;
+ uint32_t n, len = std::min(space, count);
+
+ nsresult rv = input->Read(&mChunk[mChunkLen], len, &n);
+ if (NS_FAILED(rv))
+ return rv;
+ if (n != len)
+ return NS_ERROR_UNEXPECTED;
+
+ count -= n;
+ mChunkLen += n;
+
+ if (mChunkLen == mChunkSize) {
+ rv = FlushChunk();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL)
+ UpdateProgress();
+
+ return NS_OK;
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data)
+{
+ if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ Cancel(NS_ERROR_ABORT);
+
+ // Since the app is shutting down, we need to go ahead and notify our
+ // observer here. Otherwise, we would notify them after XPCOM has been
+ // shutdown or not at all.
+ CallOnStopRequest();
+ }
+ else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
+ mTimer = nullptr;
+ nsresult rv = ProcessTimeout();
+ if (NS_FAILED(rv))
+ Cancel(rv);
+ }
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
+{
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink *>(this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
+ if (ir)
+ return ir->GetInterface(iid, result);
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+nsresult
+nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel)
+{
+ NS_ENSURE_ARG(channel);
+
+ // We don't support encodings -- they make the Content-Length not equal
+ // to the actual size of the data.
+ return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
+ NS_LITERAL_CSTRING(""), false);
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+nsIncrementalDownload::AsyncOnChannelRedirect(nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback *cb)
+{
+ // In response to a redirect, we need to propagate the Range header. See bug
+ // 311595. Any failure code returned from this function aborts the redirect.
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
+ NS_ENSURE_STATE(http);
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
+ NS_ENSURE_STATE(newHttpChannel);
+
+ NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
+
+ nsresult rv = ClearRequestHeader(newHttpChannel);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // If we didn't have a Range header, then we must be doing a full download.
+ nsAutoCString rangeVal;
+ http->GetRequestHeader(rangeHdr, rangeVal);
+ if (!rangeVal.IsEmpty()) {
+ rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // A redirection changes the validator
+ mPartialValidator.Truncate();
+
+ if (mCacheBust) {
+ newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
+ NS_LITERAL_CSTRING("no-cache"), false);
+ newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
+ NS_LITERAL_CSTRING("no-cache"), false);
+ }
+
+ // Prepare to receive callback
+ mRedirectCallback = cb;
+ mNewRedirectChannel = newChannel;
+
+ // Give the observer a chance to see this redirect notification.
+ nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
+ if (sink) {
+ rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
+ if (NS_FAILED(rv)) {
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ }
+ return rv;
+ }
+ (void) OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result)
+{
+ NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
+ NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
+
+ // Update mChannel, so we can Cancel the new channel.
+ if (NS_SUCCEEDED(result))
+ mChannel = mNewRedirectChannel;
+
+ mRedirectCallback->OnRedirectVerifyCallback(result);
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ return NS_OK;
+}
+
+extern nsresult
+net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
+{
+ if (outer)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsIncrementalDownload *d = new nsIncrementalDownload();
+ if (!d)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(d);
+ nsresult rv = d->QueryInterface(iid, result);
+ NS_RELEASE(d);
+ return rv;
+}
diff --git a/netwerk/base/nsIncrementalStreamLoader.cpp b/netwerk/base/nsIncrementalStreamLoader.cpp
new file mode 100644
index 0000000000..a7298be3f0
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIncrementalStreamLoader.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsError.h"
+#include "GeckoProfiler.h"
+
+#include <limits>
+
+nsIncrementalStreamLoader::nsIncrementalStreamLoader()
+ : mData(), mBytesConsumed(0)
+{
+}
+
+nsIncrementalStreamLoader::~nsIncrementalStreamLoader()
+{
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::Init(nsIIncrementalStreamLoaderObserver* observer)
+{
+ NS_ENSURE_ARG_POINTER(observer);
+ mObserver = observer;
+ return NS_OK;
+}
+
+nsresult
+nsIncrementalStreamLoader::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ nsIncrementalStreamLoader* it = new nsIncrementalStreamLoader();
+ if (it == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsIncrementalStreamLoader, nsIIncrementalStreamLoader,
+ nsIRequestObserver, nsIStreamListener,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::GetNumBytesRead(uint32_t* aNumBytes)
+{
+ *aNumBytes = mBytesConsumed + mData.length();
+ return NS_OK;
+}
+
+/* readonly attribute nsIRequest request; */
+NS_IMETHODIMP
+nsIncrementalStreamLoader::GetRequest(nsIRequest **aRequest)
+{
+ NS_IF_ADDREF(*aRequest = mRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
+{
+ nsCOMPtr<nsIChannel> chan( do_QueryInterface(request) );
+ if (chan) {
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+ if (contentLength >= 0) {
+ if (uint64_t(contentLength) > std::numeric_limits<size_t>::max()) {
+ // Too big to fit into size_t, so let's bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // preallocate buffer
+ if (!mData.initCapacity(contentLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ mContext = ctxt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnStopRequest(nsIRequest* request, nsISupports *ctxt,
+ nsresult aStatus)
+{
+ PROFILER_LABEL("nsIncrementalStreamLoader", "OnStopRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ if (mObserver) {
+ // provide nsIIncrementalStreamLoader::request during call to OnStreamComplete
+ mRequest = request;
+ size_t length = mData.length();
+ uint8_t* elems = mData.extractOrCopyRawBuffer();
+ nsresult rv = mObserver->OnStreamComplete(this, mContext, aStatus,
+ length, elems);
+ if (rv != NS_SUCCESS_ADOPTED_DATA) {
+ // The observer didn't take ownership of the extracted data buffer, so
+ // put it back into mData.
+ mData.replaceRawBuffer(elems, length);
+ }
+ // done.. cleanup
+ ReleaseData();
+ mRequest = nullptr;
+ mObserver = nullptr;
+ mContext = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsIncrementalStreamLoader::WriteSegmentFun(nsIInputStream *inStr,
+ void *closure,
+ const char *fromSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ nsIncrementalStreamLoader *self = (nsIncrementalStreamLoader *) closure;
+
+ const uint8_t *data = reinterpret_cast<const uint8_t *>(fromSegment);
+ uint32_t consumedCount = 0;
+ nsresult rv;
+ if (self->mData.empty()) {
+ // Shortcut when observer wants to keep the listener's buffer empty.
+ rv = self->mObserver->OnIncrementalData(self, self->mContext,
+ count, data, &consumedCount);
+
+ if (rv != NS_OK) {
+ return rv;
+ }
+
+ if (consumedCount > count) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (consumedCount < count) {
+ if (!self->mData.append(fromSegment + consumedCount,
+ count - consumedCount)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ } else {
+ // We have some non-consumed data from previous OnIncrementalData call,
+ // appending new data and reporting combined data.
+ if (!self->mData.append(fromSegment, count)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ size_t length = self->mData.length();
+ uint32_t reportCount = length > UINT32_MAX ? UINT32_MAX : (uint32_t)length;
+ uint8_t* elems = self->mData.extractOrCopyRawBuffer();
+
+ rv = self->mObserver->OnIncrementalData(self, self->mContext,
+ reportCount, elems, &consumedCount);
+
+ // We still own elems, freeing its memory when exiting scope.
+ if (rv != NS_OK) {
+ free(elems);
+ return rv;
+ }
+
+ if (consumedCount > reportCount) {
+ free(elems);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (consumedCount == length) {
+ free(elems); // good case -- fully consumed data
+ } else {
+ // Adopting elems back (at least its portion).
+ self->mData.replaceRawBuffer(elems, length);
+ if (consumedCount > 0) {
+ self->mData.erase(self->mData.begin() + consumedCount);
+ }
+ }
+ }
+
+ self->mBytesConsumed += consumedCount;
+ *writeCount = count;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnDataAvailable(nsIRequest* request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ if (mObserver) {
+ // provide nsIIncrementalStreamLoader::request during call to OnStreamComplete
+ mRequest = request;
+ }
+ uint32_t countRead;
+ nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead);
+ mRequest = nullptr;
+ return rv;
+}
+
+void
+nsIncrementalStreamLoader::ReleaseData()
+{
+ mData.clearAndFree();
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::CheckListenerChain()
+{
+ return NS_OK;
+}
diff --git a/netwerk/base/nsIncrementalStreamLoader.h b/netwerk/base/nsIncrementalStreamLoader.h
new file mode 100644
index 0000000000..f04d4a9583
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIncrementalStreamLoader_h__
+#define nsIncrementalStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+class nsIncrementalStreamLoader final : public nsIIncrementalStreamLoader
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsIncrementalStreamLoader();
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ ~nsIncrementalStreamLoader();
+
+ static nsresult WriteSegmentFun(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ // Utility method to free mData, if present, and update other state to
+ // reflect that no data has been allocated.
+ void ReleaseData();
+
+ nsCOMPtr<nsIIncrementalStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsISupports> mContext; // the observer's context
+ nsCOMPtr<nsIRequest> mRequest;
+
+ // Buffer to accumulate incoming data. We preallocate if contentSize is
+ // available.
+ mozilla::Vector<uint8_t, 0> mData;
+
+ // Number of consumed bytes from the mData.
+ size_t mBytesConsumed;
+};
+
+#endif // nsIncrementalStreamLoader_h__
diff --git a/netwerk/base/nsInputStreamChannel.cpp b/netwerk/base/nsInputStreamChannel.cpp
new file mode 100644
index 0000000000..99877eebf9
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsInputStreamChannel.h"
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel
+
+namespace mozilla {
+namespace net {
+
+nsresult
+nsInputStreamChannel::OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel)
+{
+ NS_ENSURE_TRUE(mContentStream, NS_ERROR_NOT_INITIALIZED);
+
+ // If content length is unknown, then we must guess. In this case, we assume
+ // the stream can tell us. If the stream is a pipe, then this will not work.
+
+ if (mContentLength < 0) {
+ uint64_t avail;
+ nsresult rv = mContentStream->Available(&avail);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ // This just means there's nothing in the stream
+ avail = 0;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mContentLength = avail;
+ }
+
+ EnableSynthesizedProgressEvents(true);
+
+ NS_ADDREF(*result = mContentStream);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamChannel,
+ nsBaseChannel,
+ nsIInputStreamChannel)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel::nsIInputStreamChannel
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetURI(nsIURI *uri)
+{
+ NS_ENSURE_TRUE(!URI(), NS_ERROR_ALREADY_INITIALIZED);
+ nsBaseChannel::SetURI(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetContentStream(nsIInputStream **stream)
+{
+ NS_IF_ADDREF(*stream = mContentStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetContentStream(nsIInputStream *stream)
+{
+ NS_ENSURE_TRUE(!mContentStream, NS_ERROR_ALREADY_INITIALIZED);
+ mContentStream = stream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetSrcdocData(nsAString& aSrcdocData)
+{
+ aSrcdocData = mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetSrcdocData(const nsAString& aSrcdocData)
+{
+ mSrcdocData = aSrcdocData;
+ mIsSrcdocChannel = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetIsSrcdocChannel(bool *aIsSrcdocChannel)
+{
+ *aIsSrcdocChannel = mIsSrcdocChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetBaseURI(nsIURI** aBaseURI)
+{
+ *aBaseURI = mBaseURI;
+ NS_IF_ADDREF(*aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetBaseURI(nsIURI* aBaseURI)
+{
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsInputStreamChannel.h b/netwerk/base/nsInputStreamChannel.h
new file mode 100644
index 0000000000..3c1c6e83b8
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInputStreamChannel_h__
+#define nsInputStreamChannel_h__
+
+#include "nsBaseChannel.h"
+#include "nsIInputStreamChannel.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsInputStreamChannel : public nsBaseChannel
+ , public nsIInputStreamChannel
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCHANNEL
+
+ nsInputStreamChannel() :
+ mIsSrcdocChannel(false) {}
+
+protected:
+ virtual ~nsInputStreamChannel() {}
+
+ virtual nsresult OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel) override;
+
+ virtual void OnChannelDone() override {
+ mContentStream = nullptr;
+ }
+
+private:
+ nsCOMPtr<nsIInputStream> mContentStream;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsString mSrcdocData;
+ bool mIsSrcdocChannel;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsInputStreamChannel_h__
diff --git a/netwerk/base/nsInputStreamPump.cpp b/netwerk/base/nsInputStreamPump.cpp
new file mode 100644
index 0000000000..19c2a790ab
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.cpp
@@ -0,0 +1,766 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sts=4 sw=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsInputStreamPump.h"
+#include "nsIStreamTransportService.h"
+#include "nsISeekableStream.h"
+#include "nsITransport.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Logging.h"
+#include "GeckoProfiler.h"
+#include "nsIStreamListener.h"
+#include "nsILoadGroup.h"
+#include "nsNetCID.h"
+#include <algorithm>
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+//
+// MOZ_LOG=nsStreamPump:5
+//
+static mozilla::LazyLogModule gStreamPumpLog("nsStreamPump");
+#undef LOG
+#define LOG(args) MOZ_LOG(gStreamPumpLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump methods
+//-----------------------------------------------------------------------------
+
+nsInputStreamPump::nsInputStreamPump()
+ : mState(STATE_IDLE)
+ , mStreamOffset(0)
+ , mStreamLength(UINT64_MAX)
+ , mStatus(NS_OK)
+ , mSuspendCount(0)
+ , mLoadFlags(LOAD_NORMAL)
+ , mProcessingCallbacks(false)
+ , mWaitingForInputStreamReady(false)
+ , mCloseWhenDone(false)
+ , mRetargeting(false)
+ , mMonitor("nsInputStreamPump")
+{
+}
+
+nsInputStreamPump::~nsInputStreamPump()
+{
+}
+
+nsresult
+nsInputStreamPump::Create(nsInputStreamPump **result,
+ nsIInputStream *stream,
+ int64_t streamPos,
+ int64_t streamLen,
+ uint32_t segsize,
+ uint32_t segcount,
+ bool closeWhenDone)
+{
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ RefPtr<nsInputStreamPump> pump = new nsInputStreamPump();
+ if (pump) {
+ rv = pump->Init(stream, streamPos, streamLen,
+ segsize, segcount, closeWhenDone);
+ if (NS_SUCCEEDED(rv)) {
+ pump.forget(result);
+ }
+ }
+ return rv;
+}
+
+struct PeekData {
+ PeekData(nsInputStreamPump::PeekSegmentFun fun, void* closure)
+ : mFunc(fun), mClosure(closure) {}
+
+ nsInputStreamPump::PeekSegmentFun mFunc;
+ void* mClosure;
+};
+
+static nsresult
+CallPeekFunc(nsIInputStream *aInStream, void *aClosure,
+ const char *aFromSegment, uint32_t aToOffset, uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ NS_ASSERTION(aToOffset == 0, "Called more than once?");
+ NS_ASSERTION(aCount > 0, "Called without data?");
+
+ PeekData* data = static_cast<PeekData*>(aClosure);
+ data->mFunc(data->mClosure,
+ reinterpret_cast<const uint8_t*>(aFromSegment), aCount);
+ return NS_BINDING_ABORTED;
+}
+
+nsresult
+nsInputStreamPump::PeekStream(PeekSegmentFun callback, void* closure)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ NS_ASSERTION(mAsyncStream, "PeekStream called without stream");
+
+ // See if the pipe is closed by checking the return of Available.
+ uint64_t dummy64;
+ nsresult rv = mAsyncStream->Available(&dummy64);
+ if (NS_FAILED(rv))
+ return rv;
+ uint32_t dummy = (uint32_t)std::min(dummy64, (uint64_t)UINT32_MAX);
+
+ PeekData data(callback, closure);
+ return mAsyncStream->ReadSegments(CallPeekFunc,
+ &data,
+ nsIOService::gDefaultSegmentSize,
+ &dummy);
+}
+
+nsresult
+nsInputStreamPump::EnsureWaiting()
+{
+ mMonitor.AssertCurrentThreadIn();
+
+ // no need to worry about multiple threads... an input stream pump lives
+ // on only one thread at a time.
+ MOZ_ASSERT(mAsyncStream);
+ if (!mWaitingForInputStreamReady && !mProcessingCallbacks) {
+ // Ensure OnStateStop is called on the main thread.
+ if (mState == STATE_STOP) {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ if (mTargetThread != mainThread) {
+ mTargetThread = do_QueryInterface(mainThread);
+ }
+ }
+ MOZ_ASSERT(mTargetThread);
+ nsresult rv = mAsyncStream->AsyncWait(this, 0, 0, mTargetThread);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("AsyncWait failed");
+ return rv;
+ }
+ // Any retargeting during STATE_START or START_TRANSFER is complete
+ // after the call to AsyncWait; next callback wil be on mTargetThread.
+ mRetargeting = false;
+ mWaitingForInputStreamReady = true;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsISupports
+//-----------------------------------------------------------------------------
+
+// although this class can only be accessed from one thread at a time, we do
+// allow its ownership to move from thread to thread, assuming the consumer
+// understands the limitations of this.
+NS_IMPL_ISUPPORTS(nsInputStreamPump,
+ nsIRequest,
+ nsIThreadRetargetableRequest,
+ nsIInputStreamCallback,
+ nsIInputStreamPump)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::GetName(nsACString &result)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::IsPending(bool *result)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ *result = (mState != STATE_IDLE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetStatus(nsresult *status)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Cancel(nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ LOG(("nsInputStreamPump::Cancel [this=%p status=%x]\n",
+ this, status));
+
+ if (NS_FAILED(mStatus)) {
+ LOG((" already canceled\n"));
+ return NS_OK;
+ }
+
+ NS_ASSERTION(NS_FAILED(status), "cancel with non-failure status code");
+ mStatus = status;
+
+ // close input stream
+ if (mAsyncStream) {
+ mAsyncStream->CloseWithStatus(status);
+ if (mSuspendCount == 0)
+ EnsureWaiting();
+ // Otherwise, EnsureWaiting will be called by Resume().
+ // Note that while suspended, OnInputStreamReady will
+ // not do anything, and also note that calling asyncWait
+ // on a closed stream works and will dispatch an event immediately.
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Suspend()
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ LOG(("nsInputStreamPump::Suspend [this=%p]\n", this));
+ NS_ENSURE_TRUE(mState != STATE_IDLE, NS_ERROR_UNEXPECTED);
+ ++mSuspendCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Resume()
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ LOG(("nsInputStreamPump::Resume [this=%p]\n", this));
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(mState != STATE_IDLE, NS_ERROR_UNEXPECTED);
+
+ if (--mSuspendCount == 0)
+ EnsureWaiting();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIInputStreamPump implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::Init(nsIInputStream *stream,
+ int64_t streamPos, int64_t streamLen,
+ uint32_t segsize, uint32_t segcount,
+ bool closeWhenDone)
+{
+ NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
+
+ mStreamOffset = uint64_t(streamPos);
+ if (int64_t(streamLen) >= int64_t(0))
+ mStreamLength = uint64_t(streamLen);
+ mStream = stream;
+ mSegSize = segsize;
+ mSegCount = segcount;
+ mCloseWhenDone = closeWhenDone;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::AsyncRead(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_ARG_POINTER(listener);
+ MOZ_ASSERT(NS_IsMainThread(), "nsInputStreamPump should be read from the "
+ "main thread only.");
+
+ //
+ // OK, we need to use the stream transport service if
+ //
+ // (1) the stream is blocking
+ // (2) the stream does not support nsIAsyncInputStream
+ //
+
+ bool nonBlocking;
+ nsresult rv = mStream->IsNonBlocking(&nonBlocking);
+ if (NS_FAILED(rv)) return rv;
+
+ if (nonBlocking) {
+ mAsyncStream = do_QueryInterface(mStream);
+ //
+ // if the stream supports nsIAsyncInputStream, and if we need to seek
+ // to a starting offset, then we must do so here. in the non-async
+ // stream case, the stream transport service will take care of seeking
+ // for us.
+ //
+ if (mAsyncStream && (mStreamOffset != UINT64_MAX)) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, mStreamOffset);
+ }
+ }
+
+ if (!mAsyncStream) {
+ // ok, let's use the stream transport service to read this stream.
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransport> transport;
+ rv = sts->CreateInputTransport(mStream, mStreamOffset, mStreamLength,
+ mCloseWhenDone, getter_AddRefs(transport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> wrapper;
+ rv = transport->OpenInputStream(0, mSegSize, mSegCount, getter_AddRefs(wrapper));
+ if (NS_FAILED(rv)) return rv;
+
+ mAsyncStream = do_QueryInterface(wrapper, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // release our reference to the original stream. from this point forward,
+ // we only reference the "stream" via mAsyncStream.
+ mStream = nullptr;
+
+ // mStreamOffset now holds the number of bytes currently read. we use this
+ // to enforce the mStreamLength restriction.
+ mStreamOffset = 0;
+
+ // grab event queue (we must do this here by contract, since all notifications
+ // must go to the thread which called AsyncRead)
+ mTargetThread = do_GetCurrentThread();
+ NS_ENSURE_STATE(mTargetThread);
+
+ rv = EnsureWaiting();
+ if (NS_FAILED(rv)) return rv;
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ mState = STATE_START;
+ mListener = listener;
+ mListenerContext = ctxt;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIInputStreamCallback implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream *stream)
+{
+ LOG(("nsInputStreamPump::OnInputStreamReady [this=%p]\n", this));
+
+ PROFILER_LABEL("nsInputStreamPump", "OnInputStreamReady",
+ js::ProfileEntry::Category::NETWORK);
+
+ // this function has been called from a PLEvent, so we can safely call
+ // any listener or progress sink methods directly from here.
+
+ for (;;) {
+ // There should only be one iteration of this loop happening at a time.
+ // To prevent AsyncWait() (called during callbacks or on other threads)
+ // from creating a parallel OnInputStreamReady(), we use:
+ // -- a monitor; and
+ // -- a boolean mProcessingCallbacks to detect parallel loops
+ // when exiting the monitor for callbacks.
+ ReentrantMonitorAutoEnter lock(mMonitor);
+
+ // Prevent parallel execution during callbacks, while out of monitor.
+ if (mProcessingCallbacks) {
+ MOZ_ASSERT(!mProcessingCallbacks);
+ break;
+ }
+ mProcessingCallbacks = true;
+ if (mSuspendCount || mState == STATE_IDLE) {
+ mWaitingForInputStreamReady = false;
+ mProcessingCallbacks = false;
+ break;
+ }
+
+ uint32_t nextState;
+ switch (mState) {
+ case STATE_START:
+ nextState = OnStateStart();
+ break;
+ case STATE_TRANSFER:
+ nextState = OnStateTransfer();
+ break;
+ case STATE_STOP:
+ mRetargeting = false;
+ nextState = OnStateStop();
+ break;
+ default:
+ nextState = 0;
+ NS_NOTREACHED("Unknown enum value.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool stillTransferring = (mState == STATE_TRANSFER &&
+ nextState == STATE_TRANSFER);
+ if (stillTransferring) {
+ NS_ASSERTION(NS_SUCCEEDED(mStatus),
+ "Should not have failed status for ongoing transfer");
+ } else {
+ NS_ASSERTION(mState != nextState,
+ "Only OnStateTransfer can be called more than once.");
+ }
+ if (mRetargeting) {
+ NS_ASSERTION(mState != STATE_STOP,
+ "Retargeting should not happen during OnStateStop.");
+ }
+
+ // Set mRetargeting so EnsureWaiting will be called. It ensures that
+ // OnStateStop is called on the main thread.
+ if (nextState == STATE_STOP && !NS_IsMainThread()) {
+ mRetargeting = true;
+ }
+
+ // Unset mProcessingCallbacks here (while we have lock) so our own call to
+ // EnsureWaiting isn't blocked by it.
+ mProcessingCallbacks = false;
+
+ // We must break the loop when we're switching event delivery to another
+ // thread and the input stream pump is suspended, otherwise
+ // OnStateStop() might be called off the main thread. See bug 1026951
+ // comment #107 for the exact scenario.
+ if (mSuspendCount && mRetargeting) {
+ mState = nextState;
+ mWaitingForInputStreamReady = false;
+ break;
+ }
+
+ // Wait asynchronously if there is still data to transfer, or we're
+ // switching event delivery to another thread.
+ if (!mSuspendCount && (stillTransferring || mRetargeting)) {
+ mState = nextState;
+ mWaitingForInputStreamReady = false;
+ nsresult rv = EnsureWaiting();
+ if (NS_SUCCEEDED(rv))
+ break;
+
+ // Failure to start asynchronous wait: stop transfer.
+ // Do not set mStatus if it was previously set to report a failure.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+ nextState = STATE_STOP;
+ }
+
+ mState = nextState;
+ }
+ return NS_OK;
+}
+
+uint32_t
+nsInputStreamPump::OnStateStart()
+{
+ mMonitor.AssertCurrentThreadIn();
+
+ PROFILER_LABEL("nsInputStreamPump", "OnStateStart",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG((" OnStateStart [this=%p]\n", this));
+
+ nsresult rv;
+
+ // need to check the reason why the stream is ready. this is required
+ // so our listener can check our status from OnStartRequest.
+ // XXX async streams should have a GetStatus method!
+ if (NS_SUCCEEDED(mStatus)) {
+ uint64_t avail;
+ rv = mAsyncStream->Available(&avail);
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_CLOSED)
+ mStatus = rv;
+ }
+
+ {
+ // Note: Must exit monitor for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ mMonitor.Exit();
+ rv = mListener->OnStartRequest(this, mListenerContext);
+ mMonitor.Enter();
+ }
+
+ // an error returned from OnStartRequest should cause us to abort; however,
+ // we must not stomp on mStatus if already canceled.
+ if (NS_FAILED(rv) && NS_SUCCEEDED(mStatus))
+ mStatus = rv;
+
+ return NS_SUCCEEDED(mStatus) ? STATE_TRANSFER : STATE_STOP;
+}
+
+uint32_t
+nsInputStreamPump::OnStateTransfer()
+{
+ mMonitor.AssertCurrentThreadIn();
+
+ PROFILER_LABEL("nsInputStreamPump", "OnStateTransfer",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG((" OnStateTransfer [this=%p]\n", this));
+
+ // if canceled, go directly to STATE_STOP...
+ if (NS_FAILED(mStatus))
+ return STATE_STOP;
+
+ nsresult rv;
+
+ uint64_t avail;
+ rv = mAsyncStream->Available(&avail);
+ LOG((" Available returned [stream=%x rv=%x avail=%llu]\n", mAsyncStream.get(), rv, avail));
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ rv = NS_OK;
+ avail = 0;
+ }
+ else if (NS_SUCCEEDED(rv) && avail) {
+ // figure out how much data to report (XXX detect overflow??)
+ if (avail > mStreamLength - mStreamOffset)
+ avail = mStreamLength - mStreamOffset;
+
+ if (avail) {
+ // we used to limit avail to 16K - we were afraid some ODA handlers
+ // might assume they wouldn't get more than 16K at once
+ // we're removing that limit since it speeds up local file access.
+ // Now there's an implicit 64K limit of 4 16K segments
+ // NOTE: ok, so the story is as follows. OnDataAvailable impls
+ // are by contract supposed to consume exactly |avail| bytes.
+ // however, many do not... mailnews... stream converters...
+ // cough, cough. the input stream pump is fairly tolerant
+ // in this regard; however, if an ODA does not consume any
+ // data from the stream, then we could potentially end up in
+ // an infinite loop. we do our best here to try to catch
+ // such an error. (see bug 189672)
+
+ // in most cases this QI will succeed (mAsyncStream is almost always
+ // a nsPipeInputStream, which implements nsISeekableStream::Tell).
+ int64_t offsetBefore;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mAsyncStream);
+ if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
+ NS_NOTREACHED("Tell failed on readable stream");
+ offsetBefore = 0;
+ }
+
+ uint32_t odaAvail =
+ avail > UINT32_MAX ?
+ UINT32_MAX : uint32_t(avail);
+
+ LOG((" calling OnDataAvailable [offset=%llu count=%llu(%u)]\n",
+ mStreamOffset, avail, odaAvail));
+
+ {
+ // Note: Must exit monitor for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ mMonitor.Exit();
+ rv = mListener->OnDataAvailable(this, mListenerContext,
+ mAsyncStream, mStreamOffset,
+ odaAvail);
+ mMonitor.Enter();
+ }
+
+ // don't enter this code if ODA failed or called Cancel
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(mStatus)) {
+ // test to see if this ODA failed to consume data
+ if (seekable) {
+ // NOTE: if Tell fails, which can happen if the stream is
+ // now closed, then we assume that everything was read.
+ int64_t offsetAfter;
+ if (NS_FAILED(seekable->Tell(&offsetAfter)))
+ offsetAfter = offsetBefore + odaAvail;
+ if (offsetAfter > offsetBefore)
+ mStreamOffset += (offsetAfter - offsetBefore);
+ else if (mSuspendCount == 0) {
+ //
+ // possible infinite loop if we continue pumping data!
+ //
+ // NOTE: although not allowed by nsIStreamListener, we
+ // will allow the ODA impl to Suspend the pump. IMAP
+ // does this :-(
+ //
+ NS_ERROR("OnDataAvailable implementation consumed no data");
+ mStatus = NS_ERROR_UNEXPECTED;
+ }
+ }
+ else
+ mStreamOffset += odaAvail; // assume ODA behaved well
+ }
+ }
+ }
+
+ // an error returned from Available or OnDataAvailable should cause us to
+ // abort; however, we must not stomp on mStatus if already canceled.
+
+ if (NS_SUCCEEDED(mStatus)) {
+ if (NS_FAILED(rv))
+ mStatus = rv;
+ else if (avail) {
+ // if stream is now closed, advance to STATE_STOP right away.
+ // Available may return 0 bytes available at the moment; that
+ // would not mean that we are done.
+ // XXX async streams should have a GetStatus method!
+ rv = mAsyncStream->Available(&avail);
+ if (NS_SUCCEEDED(rv))
+ return STATE_TRANSFER;
+ if (rv != NS_BASE_STREAM_CLOSED)
+ mStatus = rv;
+ }
+ }
+ return STATE_STOP;
+}
+
+nsresult
+nsInputStreamPump::CallOnStateStop()
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "CallOnStateStop should only be called on the main thread.");
+
+ mState = OnStateStop();
+ return NS_OK;
+}
+
+uint32_t
+nsInputStreamPump::OnStateStop()
+{
+ mMonitor.AssertCurrentThreadIn();
+
+ if (!NS_IsMainThread()) {
+ // Hopefully temporary hack: OnStateStop should only run on the main
+ // thread, but we're seeing some rare off-main-thread calls. For now
+ // just redispatch to the main thread in release builds, and crash in
+ // debug builds.
+ MOZ_ASSERT(NS_IsMainThread(),
+ "OnStateStop should only be called on the main thread.");
+ nsresult rv = NS_DispatchToMainThread(
+ NewRunnableMethod(this, &nsInputStreamPump::CallOnStateStop));
+ NS_ENSURE_SUCCESS(rv, STATE_IDLE);
+ return STATE_IDLE;
+ }
+
+ PROFILER_LABEL("nsInputStreamPump", "OnStateStop",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG((" OnStateStop [this=%p status=%x]\n", this, mStatus));
+
+ // if an error occurred, we must be sure to pass the error onto the async
+ // stream. in some cases, this is redundant, but since close is idempotent,
+ // this is OK. otherwise, be sure to honor the "close-when-done" option.
+
+ if (!mAsyncStream || !mListener) {
+ MOZ_ASSERT(mAsyncStream, "null mAsyncStream: OnStateStop called twice?");
+ MOZ_ASSERT(mListener, "null mListener: OnStateStop called twice?");
+ return STATE_IDLE;
+ }
+
+ if (NS_FAILED(mStatus))
+ mAsyncStream->CloseWithStatus(mStatus);
+ else if (mCloseWhenDone)
+ mAsyncStream->Close();
+
+ mAsyncStream = nullptr;
+ mTargetThread = nullptr;
+ mIsPending = false;
+ {
+ // Note: Must exit monitor for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ mMonitor.Exit();
+ mListener->OnStopRequest(this, mListenerContext, mStatus);
+ mMonitor.Enter();
+ }
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return STATE_IDLE;
+}
+
+//-----------------------------------------------------------------------------
+// nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::RetargetDeliveryTo(nsIEventTarget* aNewTarget)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ NS_ENSURE_ARG(aNewTarget);
+ NS_ENSURE_TRUE(mState == STATE_START || mState == STATE_TRANSFER,
+ NS_ERROR_UNEXPECTED);
+
+ // If canceled, do not retarget. Return with canceled status.
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (aNewTarget == mTargetThread) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+
+ // Ensure that |mListener| and any subsequent listeners can be retargeted
+ // to another thread.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (NS_SUCCEEDED(rv) && retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_SUCCEEDED(rv)) {
+ mTargetThread = aNewTarget;
+ mRetargeting = true;
+ }
+ }
+ LOG(("nsInputStreamPump::RetargetDeliveryTo [this=%x aNewTarget=%p] "
+ "%s listener [%p] rv[%x]",
+ this, aNewTarget, (mTargetThread == aNewTarget ? "success" : "failure"),
+ (nsIStreamListener*)mListener, rv));
+ return rv;
+}
diff --git a/netwerk/base/nsInputStreamPump.h b/netwerk/base/nsInputStreamPump.h
new file mode 100644
index 0000000000..c9b7bd64cc
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInputStreamPump_h__
+#define nsInputStreamPump_h__
+
+#include "nsIInputStreamPump.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ReentrantMonitor.h"
+
+class nsIInputStream;
+class nsILoadGroup;
+class nsIStreamListener;
+
+class nsInputStreamPump final : public nsIInputStreamPump
+ , public nsIInputStreamCallback
+ , public nsIThreadRetargetableRequest
+{
+ ~nsInputStreamPump();
+
+public:
+ typedef mozilla::ReentrantMonitorAutoEnter ReentrantMonitorAutoEnter;
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINPUTSTREAMPUMP
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+
+ nsInputStreamPump();
+
+ static nsresult
+ Create(nsInputStreamPump **result,
+ nsIInputStream *stream,
+ int64_t streamPos = -1,
+ int64_t streamLen = -1,
+ uint32_t segsize = 0,
+ uint32_t segcount = 0,
+ bool closeWhenDone = false);
+
+ typedef void (*PeekSegmentFun)(void *closure, const uint8_t *buf,
+ uint32_t bufLen);
+ /**
+ * Peek into the first chunk of data that's in the stream. Note that this
+ * method will not call the callback when there is no data in the stream.
+ * The callback will be called at most once.
+ *
+ * The data from the stream will not be consumed, i.e. the pump's listener
+ * can still read all the data.
+ *
+ * Do not call before asyncRead. Do not call after onStopRequest.
+ */
+ nsresult PeekStream(PeekSegmentFun callback, void *closure);
+
+ /**
+ * Dispatched (to the main thread) by OnStateStop if it's called off main
+ * thread. Updates mState based on return value of OnStateStop.
+ */
+ nsresult CallOnStateStop();
+
+protected:
+
+ enum {
+ STATE_IDLE,
+ STATE_START,
+ STATE_TRANSFER,
+ STATE_STOP
+ };
+
+ nsresult EnsureWaiting();
+ uint32_t OnStateStart();
+ uint32_t OnStateTransfer();
+ uint32_t OnStateStop();
+
+ uint32_t mState;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+ nsCOMPtr<nsIInputStream> mStream;
+ nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
+ uint64_t mStreamOffset;
+ uint64_t mStreamLength;
+ uint32_t mSegSize;
+ uint32_t mSegCount;
+ nsresult mStatus;
+ uint32_t mSuspendCount;
+ uint32_t mLoadFlags;
+ bool mIsPending;
+ // True while in OnInputStreamReady, calling OnStateStart, OnStateTransfer
+ // and OnStateStop. Used to prevent calls to AsyncWait during callbacks.
+ bool mProcessingCallbacks;
+ // True if waiting on the "input stream ready" callback.
+ bool mWaitingForInputStreamReady;
+ bool mCloseWhenDone;
+ bool mRetargeting;
+ // Protects state/member var accesses across multiple threads.
+ mozilla::ReentrantMonitor mMonitor;
+};
+
+#endif // !nsInputStreamChannel_h__
diff --git a/netwerk/base/nsLoadGroup.cpp b/netwerk/base/nsLoadGroup.cpp
new file mode 100644
index 0000000000..3b8cf44342
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.cpp
@@ -0,0 +1,1087 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsLoadGroup.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/Telemetry.h"
+#include "nsITimedChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIRequestObserver.h"
+#include "nsIRequestContext.h"
+#include "CacheObserver.h"
+#include "MainThreadUtils.h"
+
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+
+//
+// Log module for nsILoadGroup logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=LoadGroup:5
+// set MOZ_LOG_FILE=network.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file network.log.
+//
+static LazyLogModule gLoadGroupLog("LoadGroup");
+#undef LOG
+#define LOG(args) MOZ_LOG(gLoadGroupLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class RequestMapEntry : public PLDHashEntryHdr
+{
+public:
+ explicit RequestMapEntry(nsIRequest *aRequest) :
+ mKey(aRequest)
+ {
+ }
+
+ nsCOMPtr<nsIRequest> mKey;
+};
+
+static bool
+RequestHashMatchEntry(const PLDHashEntryHdr *entry, const void *key)
+{
+ const RequestMapEntry *e =
+ static_cast<const RequestMapEntry *>(entry);
+ const nsIRequest *request = static_cast<const nsIRequest *>(key);
+
+ return e->mKey == request;
+}
+
+static void
+RequestHashClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry)
+{
+ RequestMapEntry *e = static_cast<RequestMapEntry *>(entry);
+
+ // An entry is being cleared, let the entry do its own cleanup.
+ e->~RequestMapEntry();
+}
+
+static void
+RequestHashInitEntry(PLDHashEntryHdr *entry, const void *key)
+{
+ const nsIRequest *const_request = static_cast<const nsIRequest *>(key);
+ nsIRequest *request = const_cast<nsIRequest *>(const_request);
+
+ // Initialize the entry with placement new
+ new (entry) RequestMapEntry(request);
+}
+
+static const PLDHashTableOps sRequestHashOps =
+{
+ PLDHashTable::HashVoidPtrKeyStub,
+ RequestHashMatchEntry,
+ PLDHashTable::MoveEntryStub,
+ RequestHashClearEntry,
+ RequestHashInitEntry
+};
+
+static void
+RescheduleRequest(nsIRequest *aRequest, int32_t delta)
+{
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aRequest);
+ if (p)
+ p->AdjustPriority(delta);
+}
+
+nsLoadGroup::nsLoadGroup(nsISupports* outer)
+ : mForegroundCount(0)
+ , mLoadFlags(LOAD_NORMAL)
+ , mDefaultLoadFlags(0)
+ , mRequests(&sRequestHashOps, sizeof(RequestMapEntry))
+ , mStatus(NS_OK)
+ , mPriority(PRIORITY_NORMAL)
+ , mIsCanceling(false)
+ , mDefaultLoadIsTimed(false)
+ , mTimedRequests(0)
+ , mCachedRequests(0)
+ , mTimedNonCachedRequestsUntilOnEndPageLoad(0)
+{
+ NS_INIT_AGGREGATED(outer);
+ LOG(("LOADGROUP [%x]: Created.\n", this));
+}
+
+nsLoadGroup::~nsLoadGroup()
+{
+ DebugOnly<nsresult> rv = Cancel(NS_BINDING_ABORTED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed");
+
+ mDefaultLoadRequest = nullptr;
+
+ if (mRequestContext) {
+ nsID rcid;
+ mRequestContext->GetID(&rcid);
+
+ if (IsNeckoChild() && gNeckoChild) {
+ char rcid_str[NSID_LENGTH];
+ rcid.ToProvidedString(rcid_str);
+
+ nsCString rcid_nscs;
+ rcid_nscs.AssignASCII(rcid_str);
+
+ gNeckoChild->SendRemoveRequestContext(rcid_nscs);
+ } else {
+ mRequestContextService->RemoveRequestContext(rcid);
+ }
+ }
+
+ LOG(("LOADGROUP [%x]: Destroyed.\n", this));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports methods:
+
+NS_IMPL_AGGREGATED(nsLoadGroup)
+NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsLoadGroup)
+ NS_INTERFACE_MAP_ENTRY(nsILoadGroup)
+ NS_INTERFACE_MAP_ENTRY(nsPILoadGroupInternal)
+ NS_INTERFACE_MAP_ENTRY(nsILoadGroupChild)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetName(nsACString &result)
+{
+ // XXX is this the right "name" for a load group?
+
+ if (!mDefaultLoadRequest) {
+ result.Truncate();
+ return NS_OK;
+ }
+
+ return mDefaultLoadRequest->GetName(result);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::IsPending(bool *aResult)
+{
+ *aResult = (mForegroundCount > 0) ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetStatus(nsresult *status)
+{
+ if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest)
+ return mDefaultLoadRequest->GetStatus(status);
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+static bool
+AppendRequestsToArray(PLDHashTable* aTable, nsTArray<nsIRequest*> *aArray)
+{
+ for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+ auto e = static_cast<RequestMapEntry*>(iter.Get());
+ nsIRequest *request = e->mKey;
+ NS_ASSERTION(request, "What? Null key in PLDHashTable entry?");
+
+ bool ok = !!aArray->AppendElement(request);
+ if (!ok) {
+ break;
+ }
+ NS_ADDREF(request);
+ }
+
+ if (aArray->Length() != aTable->EntryCount()) {
+ for (uint32_t i = 0, len = aArray->Length(); i < len; ++i) {
+ NS_RELEASE((*aArray)[i]);
+ }
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Cancel(nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
+ nsresult rv;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // set the load group status to our cancel status while we cancel
+ // all our requests...once the cancel is done, we'll reset it...
+ //
+ mStatus = status;
+
+ // Set the flag indicating that the loadgroup is being canceled... This
+ // prevents any new channels from being added during the operation.
+ //
+ mIsCanceling = true;
+
+ nsresult firstError = NS_OK;
+
+ while (count > 0) {
+ nsIRequest* request = requests.ElementAt(--count);
+
+ NS_ASSERTION(request, "NULL request found in list.");
+
+ if (!mRequests.Search(request)) {
+ // |request| was removed already
+ NS_RELEASE(request);
+ continue;
+ }
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Canceling request %x %s.\n",
+ this, request, nameStr.get()));
+ }
+
+ //
+ // Remove the request from the load group... This may cause
+ // the OnStopRequest notification to fire...
+ //
+ // XXX: What should the context be?
+ //
+ (void)RemoveRequest(request, nullptr, status);
+
+ // Cancel the request...
+ rv = request->Cancel(status);
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError))
+ firstError = rv;
+
+ NS_RELEASE(request);
+ }
+
+#if defined(DEBUG)
+ NS_ASSERTION(mRequests.EntryCount() == 0, "Request list is not empty.");
+ NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active.");
+#endif
+
+ mStatus = NS_OK;
+ mIsCanceling = false;
+
+ return firstError;
+}
+
+
+NS_IMETHODIMP
+nsLoadGroup::Suspend()
+{
+ nsresult rv, firstError;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ firstError = NS_OK;
+ //
+ // Operate the elements from back to front so that if items get
+ // get removed from the list it won't affect our iteration
+ //
+ while (count > 0) {
+ nsIRequest* request = requests.ElementAt(--count);
+
+ NS_ASSERTION(request, "NULL request found in list.");
+ if (!request)
+ continue;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Suspending request %x %s.\n",
+ this, request, nameStr.get()));
+ }
+
+ // Suspend the request...
+ rv = request->Suspend();
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError))
+ firstError = rv;
+
+ NS_RELEASE(request);
+ }
+
+ return firstError;
+}
+
+
+NS_IMETHODIMP
+nsLoadGroup::Resume()
+{
+ nsresult rv, firstError;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ firstError = NS_OK;
+ //
+ // Operate the elements from back to front so that if items get
+ // get removed from the list it won't affect our iteration
+ //
+ while (count > 0) {
+ nsIRequest* request = requests.ElementAt(--count);
+
+ NS_ASSERTION(request, "NULL request found in list.");
+ if (!request)
+ continue;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Resuming request %x %s.\n",
+ this, request, nameStr.get()));
+ }
+
+ // Resume the request...
+ rv = request->Resume();
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError))
+ firstError = rv;
+
+ NS_RELEASE(request);
+ }
+
+ return firstError;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetLoadFlags(uint32_t *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetLoadFlags(uint32_t aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetLoadGroup(nsILoadGroup **loadGroup)
+{
+ *loadGroup = mLoadGroup;
+ NS_IF_ADDREF(*loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetLoadGroup(nsILoadGroup *loadGroup)
+{
+ mLoadGroup = loadGroup;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsILoadGroup methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetDefaultLoadRequest(nsIRequest * *aRequest)
+{
+ *aRequest = mDefaultLoadRequest;
+ NS_IF_ADDREF(*aRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetDefaultLoadRequest(nsIRequest *aRequest)
+{
+ mDefaultLoadRequest = aRequest;
+ // Inherit the group load flags from the default load request
+ if (mDefaultLoadRequest) {
+ mDefaultLoadRequest->GetLoadFlags(&mLoadFlags);
+ //
+ // Mask off any bits that are not part of the nsIRequest flags.
+ // in particular, nsIChannel::LOAD_DOCUMENT_URI...
+ //
+ mLoadFlags &= nsIRequest::LOAD_REQUESTMASK;
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(aRequest);
+ mDefaultLoadIsTimed = timedChannel != nullptr;
+ if (mDefaultLoadIsTimed) {
+ timedChannel->GetChannelCreation(&mDefaultRequestCreationTime);
+ timedChannel->SetTimingEnabled(true);
+ }
+ }
+ // Else, do not change the group's load flags (see bug 95981)
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::AddRequest(nsIRequest *request, nsISupports* ctxt)
+{
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Adding request %x %s (count=%d).\n",
+ this, request, nameStr.get(), mRequests.EntryCount()));
+ }
+
+ NS_ASSERTION(!mRequests.Search(request),
+ "Entry added to loadgroup twice, don't do that");
+
+ //
+ // Do not add the channel, if the loadgroup is being canceled...
+ //
+ if (mIsCanceling) {
+ LOG(("LOADGROUP [%x]: AddChannel() ABORTED because LoadGroup is"
+ " being canceled!!\n", this));
+
+ return NS_BINDING_ABORTED;
+ }
+
+ nsLoadFlags flags;
+ // if the request is the default load request or if the default load
+ // request is null, then the load group should inherit its load flags from
+ // the request, but also we need to enforce defaultLoadFlags.
+ if (mDefaultLoadRequest == request || !mDefaultLoadRequest) {
+ rv = MergeDefaultLoadFlags(request, flags);
+ } else {
+ rv = MergeLoadFlags(request, flags);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ //
+ // Add the request to the list of active requests...
+ //
+
+ auto entry =
+ static_cast<RequestMapEntry*>(mRequests.Add(request, fallible));
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mPriority != 0)
+ RescheduleRequest(request, mPriority);
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
+ if (timedChannel)
+ timedChannel->SetTimingEnabled(true);
+
+ if (!(flags & nsIRequest::LOAD_BACKGROUND)) {
+ // Update the count of foreground URIs..
+ mForegroundCount += 1;
+
+ //
+ // Fire the OnStartRequest notification out to the observer...
+ //
+ // If the notification fails then DO NOT add the request to
+ // the load group.
+ //
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(("LOADGROUP [%x]: Firing OnStartRequest for request %x."
+ "(foreground count=%d).\n", this, request, mForegroundCount));
+
+ rv = observer->OnStartRequest(request, ctxt);
+ if (NS_FAILED(rv)) {
+ LOG(("LOADGROUP [%x]: OnStartRequest for request %x FAILED.\n",
+ this, request));
+ //
+ // The URI load has been canceled by the observer. Clean up
+ // the damage...
+ //
+
+ mRequests.Remove(request);
+
+ rv = NS_OK;
+
+ mForegroundCount -= 1;
+ }
+ }
+
+ // Ensure that we're part of our loadgroup while pending
+ if (mForegroundCount == 1 && mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::RemoveRequest(nsIRequest *request, nsISupports* ctxt,
+ nsresult aStatus)
+{
+ NS_ENSURE_ARG_POINTER(request);
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Removing request %x %s status %x (count=%d).\n",
+ this, request, nameStr.get(), aStatus, mRequests.EntryCount() - 1));
+ }
+
+ // Make sure we have a owning reference to the request we're about
+ // to remove.
+
+ nsCOMPtr<nsIRequest> kungFuDeathGrip(request);
+
+ //
+ // Remove the request from the group. If this fails, it means that
+ // the request was *not* in the group so do not update the foreground
+ // count or it will get messed up...
+ //
+ auto entry = static_cast<RequestMapEntry*>(mRequests.Search(request));
+
+ if (!entry) {
+ LOG(("LOADGROUP [%x]: Unable to remove request %x. Not in group!\n",
+ this, request));
+
+ return NS_ERROR_FAILURE;
+ }
+
+ mRequests.RemoveEntry(entry);
+
+ // Collect telemetry stats only when default request is a timed channel.
+ // Don't include failed requests in the timing statistics.
+ if (mDefaultLoadIsTimed && NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
+ if (timedChannel) {
+ // Figure out if this request was served from the cache
+ ++mTimedRequests;
+ TimeStamp timeStamp;
+ rv = timedChannel->GetCacheReadStart(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ ++mCachedRequests;
+ }
+ else {
+ mTimedNonCachedRequestsUntilOnEndPageLoad++;
+ }
+
+ rv = timedChannel->GetAsyncOpen(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP_SUBITEM_OPEN_LATENCY_TIME,
+ mDefaultRequestCreationTime, timeStamp);
+ }
+
+ rv = timedChannel->GetResponseStart(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME,
+ mDefaultRequestCreationTime, timeStamp);
+ }
+
+ TelemetryReportChannel(timedChannel, false);
+ }
+ }
+
+ if (mRequests.EntryCount() == 0) {
+ TelemetryReport();
+ }
+
+ // Undo any group priority delta...
+ if (mPriority != 0)
+ RescheduleRequest(request, -mPriority);
+
+ nsLoadFlags flags;
+ rv = request->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!(flags & nsIRequest::LOAD_BACKGROUND)) {
+ NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up");
+ mForegroundCount -= 1;
+
+ // Fire the OnStopRequest out to the observer...
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(("LOADGROUP [%x]: Firing OnStopRequest for request %x."
+ "(foreground count=%d).\n", this, request, mForegroundCount));
+
+ rv = observer->OnStopRequest(request, ctxt, aStatus);
+
+ if (NS_FAILED(rv)) {
+ LOG(("LOADGROUP [%x]: OnStopRequest for request %x FAILED.\n",
+ this, request));
+ }
+ }
+
+ // If that was the last request -> remove ourselves from loadgroup
+ if (mForegroundCount == 0 && mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRequests(nsISimpleEnumerator * *aRequests)
+{
+ nsCOMArray<nsIRequest> requests;
+ requests.SetCapacity(mRequests.EntryCount());
+
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ auto e = static_cast<RequestMapEntry*>(iter.Get());
+ requests.AppendObject(e->mKey);
+ }
+
+ return NS_NewArrayEnumerator(aRequests, requests);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver)
+{
+ mObserver = do_GetWeakReference(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetGroupObserver(nsIRequestObserver* *aResult)
+{
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ *aResult = observer;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetActiveCount(uint32_t* aResult)
+{
+ *aResult = mForegroundCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
+{
+ NS_ENSURE_ARG_POINTER(aCallbacks);
+ *aCallbacks = mCallbacks;
+ NS_IF_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRequestContextID(nsID *aRCID)
+{
+ if (!mRequestContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mRequestContext->GetID(aRCID);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsILoadGroupChild methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetParentLoadGroup(nsILoadGroup * *aParentLoadGroup)
+{
+ *aParentLoadGroup = nullptr;
+ nsCOMPtr<nsILoadGroup> parent = do_QueryReferent(mParentLoadGroup);
+ if (!parent)
+ return NS_OK;
+ parent.forget(aParentLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetParentLoadGroup(nsILoadGroup *aParentLoadGroup)
+{
+ mParentLoadGroup = do_GetWeakReference(aParentLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetChildLoadGroup(nsILoadGroup * *aChildLoadGroup)
+{
+ NS_ADDREF(*aChildLoadGroup = this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRootLoadGroup(nsILoadGroup * *aRootLoadGroup)
+{
+ // first recursively try the root load group of our parent
+ nsCOMPtr<nsILoadGroupChild> ancestor = do_QueryReferent(mParentLoadGroup);
+ if (ancestor)
+ return ancestor->GetRootLoadGroup(aRootLoadGroup);
+
+ // next recursively try the root load group of our own load grop
+ ancestor = do_QueryInterface(mLoadGroup);
+ if (ancestor)
+ return ancestor->GetRootLoadGroup(aRootLoadGroup);
+
+ // finally just return this
+ NS_ADDREF(*aRootLoadGroup = this);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsPILoadGroupInternal methods:
+
+NS_IMETHODIMP
+nsLoadGroup::OnEndPageLoad(nsIChannel *aDefaultChannel)
+{
+ // for the moment, nothing to do here.
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupportsPriority methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetPriority(int32_t *aValue)
+{
+ *aValue = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetPriority(int32_t aValue)
+{
+ return AdjustPriority(aValue - mPriority);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::AdjustPriority(int32_t aDelta)
+{
+ // Update the priority for each request that supports nsISupportsPriority
+ if (aDelta != 0) {
+ mPriority += aDelta;
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ auto e = static_cast<RequestMapEntry*>(iter.Get());
+ RescheduleRequest(e->mKey, aDelta);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetDefaultLoadFlags(uint32_t *aFlags)
+{
+ *aFlags = mDefaultLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags)
+{
+ mDefaultLoadFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetUserAgentOverrideCache(nsACString & aUserAgentOverrideCache)
+{
+ aUserAgentOverrideCache = mUserAgentOverrideCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetUserAgentOverrideCache(const nsACString & aUserAgentOverrideCache)
+{
+ mUserAgentOverrideCache = aUserAgentOverrideCache;
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+void
+nsLoadGroup::TelemetryReport()
+{
+ if (mDefaultLoadIsTimed) {
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE, mTimedRequests);
+ if (mTimedRequests) {
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE_FROM_CACHE,
+ mCachedRequests * 100 / mTimedRequests);
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChannel =
+ do_QueryInterface(mDefaultLoadRequest);
+ if (timedChannel)
+ TelemetryReportChannel(timedChannel, true);
+ }
+
+ mTimedRequests = 0;
+ mCachedRequests = 0;
+ mDefaultLoadIsTimed = false;
+}
+
+void
+nsLoadGroup::TelemetryReportChannel(nsITimedChannel *aTimedChannel,
+ bool aDefaultRequest)
+{
+ nsresult rv;
+ bool timingEnabled;
+ rv = aTimedChannel->GetTimingEnabled(&timingEnabled);
+ if (NS_FAILED(rv) || !timingEnabled)
+ return;
+
+ TimeStamp asyncOpen;
+ rv = aTimedChannel->GetAsyncOpen(&asyncOpen);
+ // We do not check !asyncOpen.IsNull() bellow, prevent ASSERTIONs this way
+ if (NS_FAILED(rv) || asyncOpen.IsNull())
+ return;
+
+ TimeStamp cacheReadStart;
+ rv = aTimedChannel->GetCacheReadStart(&cacheReadStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp cacheReadEnd;
+ rv = aTimedChannel->GetCacheReadEnd(&cacheReadEnd);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp domainLookupStart;
+ rv = aTimedChannel->GetDomainLookupStart(&domainLookupStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp domainLookupEnd;
+ rv = aTimedChannel->GetDomainLookupEnd(&domainLookupEnd);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp connectStart;
+ rv = aTimedChannel->GetConnectStart(&connectStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp connectEnd;
+ rv = aTimedChannel->GetConnectEnd(&connectEnd);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp requestStart;
+ rv = aTimedChannel->GetRequestStart(&requestStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp responseStart;
+ rv = aTimedChannel->GetResponseStart(&responseStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp responseEnd;
+ rv = aTimedChannel->GetResponseEnd(&responseEnd);
+ if (NS_FAILED(rv))
+ return;
+
+#define HTTP_REQUEST_HISTOGRAMS(prefix) \
+ if (!domainLookupStart.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_DNS_ISSUE_TIME, \
+ asyncOpen, domainLookupStart); \
+ } \
+ \
+ if (!domainLookupStart.IsNull() && !domainLookupEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_DNS_LOOKUP_TIME, \
+ domainLookupStart, domainLookupEnd); \
+ } \
+ \
+ if (!connectStart.IsNull() && !connectEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_TCP_CONNECTION, \
+ connectStart, connectEnd); \
+ } \
+ \
+ \
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_SENT, \
+ asyncOpen, requestStart); \
+ \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_FIRST_SENT_TO_LAST_RECEIVED, \
+ requestStart, responseEnd); \
+ \
+ if (cacheReadStart.IsNull() && !responseStart.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_RECEIVED, \
+ asyncOpen, responseStart); \
+ } \
+ } \
+ \
+ if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) { \
+ if (!CacheObserver::UseNewCache()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE, \
+ asyncOpen, cacheReadStart); \
+ } else { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE_V2, \
+ asyncOpen, cacheReadStart); \
+ } \
+ \
+ if (!CacheObserver::UseNewCache()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_CACHE_READ_TIME, \
+ cacheReadStart, cacheReadEnd); \
+ } else { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_CACHE_READ_TIME_V2, \
+ cacheReadStart, cacheReadEnd); \
+ } \
+ \
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_REVALIDATION, \
+ requestStart, responseEnd); \
+ } \
+ } \
+ \
+ if (!cacheReadEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD, \
+ asyncOpen, cacheReadEnd); \
+ \
+ if (!CacheObserver::UseNewCache()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED, \
+ asyncOpen, cacheReadEnd); \
+ } else { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2, \
+ asyncOpen, cacheReadEnd); \
+ } \
+ } \
+ else if (!responseEnd.IsNull()) { \
+ if (!CacheObserver::UseNewCache()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD, \
+ asyncOpen, responseEnd); \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET, \
+ asyncOpen, responseEnd); \
+ } else { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, \
+ asyncOpen, responseEnd); \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET_V2, \
+ asyncOpen, responseEnd); \
+ } \
+ }
+
+ if (aDefaultRequest) {
+ HTTP_REQUEST_HISTOGRAMS(PAGE)
+ } else {
+ HTTP_REQUEST_HISTOGRAMS(SUB)
+ }
+#undef HTTP_REQUEST_HISTOGRAMS
+}
+
+nsresult nsLoadGroup::MergeLoadFlags(nsIRequest *aRequest,
+ nsLoadFlags& outFlags)
+{
+ nsresult rv;
+ nsLoadFlags flags, oldFlags;
+
+ rv = aRequest->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ oldFlags = flags;
+
+ // Inherit the following bits...
+ flags |= (mLoadFlags & (LOAD_BACKGROUND |
+ LOAD_BYPASS_CACHE |
+ LOAD_FROM_CACHE |
+ VALIDATE_ALWAYS |
+ VALIDATE_ONCE_PER_SESSION |
+ VALIDATE_NEVER));
+
+ // ... and force the default flags.
+ flags |= mDefaultLoadFlags;
+
+ if (flags != oldFlags) {
+ rv = aRequest->SetLoadFlags(flags);
+ }
+
+ outFlags = flags;
+ return rv;
+}
+
+nsresult nsLoadGroup::MergeDefaultLoadFlags(nsIRequest *aRequest,
+ nsLoadFlags& outFlags)
+{
+ nsresult rv;
+ nsLoadFlags flags, oldFlags;
+
+ rv = aRequest->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ oldFlags = flags;
+ // ... and force the default flags.
+ flags |= mDefaultLoadFlags;
+
+ if (flags != oldFlags) {
+ rv = aRequest->SetLoadFlags(flags);
+ }
+ outFlags = flags;
+ return rv;
+}
+
+nsresult nsLoadGroup::Init()
+{
+ mRequestContextService = do_GetService("@mozilla.org/network/request-context-service;1");
+ if (mRequestContextService) {
+ nsID requestContextID;
+ if (NS_SUCCEEDED(mRequestContextService->NewRequestContextID(&requestContextID))) {
+ mRequestContextService->GetRequestContext(requestContextID,
+ getter_AddRefs(mRequestContext));
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/base/nsLoadGroup.h b/netwerk/base/nsLoadGroup.h
new file mode 100644
index 0000000000..da89ca1b39
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLoadGroup_h__
+#define nsLoadGroup_h__
+
+#include "nsILoadGroup.h"
+#include "nsILoadGroupChild.h"
+#include "nsPILoadGroupInternal.h"
+#include "nsAgg.h"
+#include "nsCOMPtr.h"
+#include "nsWeakPtr.h"
+#include "nsWeakReference.h"
+#include "nsISupportsPriority.h"
+#include "PLDHashTable.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIRequestContext;
+class nsIRequestContextService;
+class nsITimedChannel;
+
+namespace mozilla {
+namespace net {
+
+class nsLoadGroup : public nsILoadGroup,
+ public nsILoadGroupChild,
+ public nsISupportsPriority,
+ public nsSupportsWeakReference,
+ public nsPILoadGroupInternal
+{
+public:
+ NS_DECL_AGGREGATED
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsIRequest methods:
+ NS_DECL_NSIREQUEST
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsILoadGroup methods:
+ NS_DECL_NSILOADGROUP
+ NS_DECL_NSPILOADGROUPINTERNAL
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsILoadGroupChild methods:
+ NS_DECL_NSILOADGROUPCHILD
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsISupportsPriority methods:
+ NS_DECL_NSISUPPORTSPRIORITY
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsLoadGroup methods:
+
+ explicit nsLoadGroup(nsISupports* outer);
+ virtual ~nsLoadGroup();
+
+ nsresult Init();
+
+protected:
+ nsresult MergeLoadFlags(nsIRequest *aRequest, nsLoadFlags& flags);
+ nsresult MergeDefaultLoadFlags(nsIRequest *aRequest, nsLoadFlags& flags);
+
+private:
+ void TelemetryReport();
+ void TelemetryReportChannel(nsITimedChannel *timedChannel,
+ bool defaultRequest);
+
+protected:
+ uint32_t mForegroundCount;
+ uint32_t mLoadFlags;
+ uint32_t mDefaultLoadFlags;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup; // load groups can contain load groups
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+ nsCOMPtr<nsIRequestContextService> mRequestContextService;
+
+ nsCOMPtr<nsIRequest> mDefaultLoadRequest;
+ PLDHashTable mRequests;
+
+ nsWeakPtr mObserver;
+ nsWeakPtr mParentLoadGroup;
+
+ nsresult mStatus;
+ int32_t mPriority;
+ bool mIsCanceling;
+
+ /* Telemetry */
+ mozilla::TimeStamp mDefaultRequestCreationTime;
+ bool mDefaultLoadIsTimed;
+ uint32_t mTimedRequests;
+ uint32_t mCachedRequests;
+
+ /* For nsPILoadGroupInternal */
+ uint32_t mTimedNonCachedRequestsUntilOnEndPageLoad;
+
+ nsCString mUserAgentOverrideCache;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsLoadGroup_h__
diff --git a/netwerk/base/nsMIMEInputStream.cpp b/netwerk/base/nsMIMEInputStream.cpp
new file mode 100644
index 0000000000..ce1188ea08
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.cpp
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIMIMEInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIStringStream.h"
+#include "nsString.h"
+#include "nsMIMEInputStream.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+
+using namespace mozilla::ipc;
+using mozilla::Maybe;
+
+class nsMIMEInputStream : public nsIMIMEInputStream,
+ public nsISeekableStream,
+ public nsIIPCSerializableInputStream
+{
+ virtual ~nsMIMEInputStream();
+
+public:
+ nsMIMEInputStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIMIMEINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ nsresult Init();
+
+private:
+
+ void InitStreams();
+
+ struct MOZ_STACK_CLASS ReadSegmentsState {
+ nsCOMPtr<nsIInputStream> mThisStream;
+ nsWriteSegmentFun mWriter;
+ void* mClosure;
+ };
+ static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t *aWriteCount);
+
+ nsCString mHeaders;
+ nsCOMPtr<nsIStringInputStream> mHeaderStream;
+
+ nsCString mContentLength;
+ nsCOMPtr<nsIStringInputStream> mCLStream;
+
+ nsCOMPtr<nsIInputStream> mData;
+ nsCOMPtr<nsIMultiplexInputStream> mStream;
+ bool mAddContentLength;
+ bool mStartedReading;
+};
+
+NS_IMPL_ADDREF(nsMIMEInputStream)
+NS_IMPL_RELEASE(nsMIMEInputStream)
+
+NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_MIMEINPUTSTREAM_CID)
+
+NS_IMPL_QUERY_INTERFACE_CI(nsMIMEInputStream,
+ nsIMIMEInputStream,
+ nsIInputStream,
+ nsISeekableStream,
+ nsIIPCSerializableInputStream)
+NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream,
+ nsIMIMEInputStream,
+ nsIInputStream,
+ nsISeekableStream)
+
+nsMIMEInputStream::nsMIMEInputStream() : mAddContentLength(false),
+ mStartedReading(false)
+{
+}
+
+nsMIMEInputStream::~nsMIMEInputStream()
+{
+}
+
+nsresult nsMIMEInputStream::Init()
+{
+ nsresult rv = NS_OK;
+ mStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1",
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHeaderStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1",
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCLStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStream->AppendStream(mHeaderStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStream->AppendStream(mCLStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetAddContentLength(bool *aAddContentLength)
+{
+ *aAddContentLength = mAddContentLength;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsMIMEInputStream::SetAddContentLength(bool aAddContentLength)
+{
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+ mAddContentLength = aAddContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::AddHeader(const char *aName, const char *aValue)
+{
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+ mHeaders.Append(aName);
+ mHeaders.AppendLiteral(": ");
+ mHeaders.Append(aValue);
+ mHeaders.AppendLiteral("\r\n");
+
+ // Just in case someone somehow uses our stream, lets at least
+ // let the stream have a valid pointer. The stream will be properly
+ // initialized in nsMIMEInputStream::InitStreams
+ mHeaderStream->ShareData(mHeaders.get(), 0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::SetData(nsIInputStream *aStream)
+{
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+ // Remove the old stream if there is one
+ if (mData)
+ mStream->RemoveStream(2);
+
+ mData = aStream;
+ if (aStream)
+ mStream->AppendStream(mData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetData(nsIInputStream **aStream)
+{
+ NS_ENSURE_ARG_POINTER(aStream);
+ *aStream = mData;
+ NS_IF_ADDREF(*aStream);
+ return NS_OK;
+}
+
+// set up the internal streams
+void nsMIMEInputStream::InitStreams()
+{
+ NS_ASSERTION(!mStartedReading,
+ "Don't call initStreams twice without rewinding");
+
+ mStartedReading = true;
+
+ // We'll use the content-length stream to add the final \r\n
+ if (mAddContentLength) {
+ uint64_t cl = 0;
+ if (mData) {
+ mData->Available(&cl);
+ }
+ mContentLength.AssignLiteral("Content-Length: ");
+ mContentLength.AppendInt(cl);
+ mContentLength.AppendLiteral("\r\n\r\n");
+ }
+ else {
+ mContentLength.AssignLiteral("\r\n");
+ }
+ mCLStream->ShareData(mContentLength.get(), -1);
+ mHeaderStream->ShareData(mHeaders.get(), -1);
+}
+
+
+
+#define INITSTREAMS \
+if (!mStartedReading) { \
+ InitStreams(); \
+}
+
+// Reset mStartedReading when Seek-ing to start
+NS_IMETHODIMP
+nsMIMEInputStream::Seek(int32_t whence, int64_t offset)
+{
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+ if (whence == NS_SEEK_SET && offset == 0) {
+ rv = stream->Seek(whence, offset);
+ if (NS_SUCCEEDED(rv))
+ mStartedReading = false;
+ }
+ else {
+ INITSTREAMS;
+ rv = stream->Seek(whence, offset);
+ }
+
+ return rv;
+}
+
+// Proxy ReadSegments since we need to be a good little nsIInputStream
+NS_IMETHODIMP nsMIMEInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void *aClosure, uint32_t aCount,
+ uint32_t *_retval)
+{
+ INITSTREAMS;
+ ReadSegmentsState state;
+ state.mThisStream = this;
+ state.mWriter = aWriter;
+ state.mClosure = aClosure;
+ return mStream->ReadSegments(ReadSegCb, &state, aCount, _retval);
+}
+
+nsresult
+nsMIMEInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ ReadSegmentsState* state = (ReadSegmentsState*)aClosure;
+ return (state->mWriter)(state->mThisStream,
+ state->mClosure,
+ aFromRawSegment,
+ aToOffset,
+ aCount,
+ aWriteCount);
+}
+
+/**
+ * Forward everything else to the mStream after calling InitStreams()
+ */
+
+// nsIInputStream
+NS_IMETHODIMP nsMIMEInputStream::Close(void) { INITSTREAMS; return mStream->Close(); }
+NS_IMETHODIMP nsMIMEInputStream::Available(uint64_t *_retval) { INITSTREAMS; return mStream->Available(_retval); }
+NS_IMETHODIMP nsMIMEInputStream::Read(char * buf, uint32_t count, uint32_t *_retval) { INITSTREAMS; return mStream->Read(buf, count, _retval); }
+NS_IMETHODIMP nsMIMEInputStream::IsNonBlocking(bool *aNonBlocking) { INITSTREAMS; return mStream->IsNonBlocking(aNonBlocking); }
+
+// nsISeekableStream
+NS_IMETHODIMP nsMIMEInputStream::Tell(int64_t *_retval)
+{
+ INITSTREAMS;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+ return stream->Tell(_retval);
+}
+NS_IMETHODIMP nsMIMEInputStream::SetEOF(void) {
+ INITSTREAMS;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+ return stream->SetEOF();
+}
+
+
+/**
+ * Factory method used by do_CreateInstance
+ */
+
+nsresult
+nsMIMEInputStreamConstructor(nsISupports *outer, REFNSIID iid, void **result)
+{
+ *result = nullptr;
+
+ if (outer)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsMIMEInputStream *inst = new nsMIMEInputStream();
+ if (!inst)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(inst);
+
+ nsresult rv = inst->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(inst);
+ return rv;
+ }
+
+ rv = inst->QueryInterface(iid, result);
+ NS_RELEASE(inst);
+
+ return rv;
+}
+
+void
+nsMIMEInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ MIMEInputStreamParams params;
+
+ if (mData) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mData);
+ MOZ_ASSERT(stream);
+
+ InputStreamParams wrappedParams;
+ SerializeInputStream(stream, wrappedParams, aFileDescriptors);
+
+ NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None,
+ "Wrapped stream failed to serialize!");
+
+ params.optionalStream() = wrappedParams;
+ }
+ else {
+ params.optionalStream() = mozilla::void_t();
+ }
+
+ params.headers() = mHeaders;
+ params.contentLength() = mContentLength;
+ params.startedReading() = mStartedReading;
+ params.addContentLength() = mAddContentLength;
+
+ aParams = params;
+}
+
+bool
+nsMIMEInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ if (aParams.type() != InputStreamParams::TMIMEInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const MIMEInputStreamParams& params =
+ aParams.get_MIMEInputStreamParams();
+ const OptionalInputStreamParams& wrappedParams = params.optionalStream();
+
+ mHeaders = params.headers();
+ mContentLength = params.contentLength();
+ mStartedReading = params.startedReading();
+
+ // nsMIMEInputStream::Init() already appended mHeaderStream & mCLStream
+ mHeaderStream->ShareData(mHeaders.get(),
+ mStartedReading ? mHeaders.Length() : 0);
+ mCLStream->ShareData(mContentLength.get(),
+ mStartedReading ? mContentLength.Length() : 0);
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) {
+ stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(),
+ aFileDescriptors);
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+
+ mData = stream;
+
+ if (NS_FAILED(mStream->AppendStream(mData))) {
+ NS_WARNING("Failed to append stream!");
+ return false;
+ }
+ }
+ else {
+ NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t,
+ "Unknown type for OptionalInputStreamParams!");
+ }
+
+ mAddContentLength = params.addContentLength();
+
+ return true;
+}
+
+Maybe<uint64_t>
+nsMIMEInputStream::ExpectedSerializedLength()
+{
+ nsCOMPtr<nsIIPCSerializableInputStream> serializable = do_QueryInterface(mStream);
+ return serializable ? serializable->ExpectedSerializedLength() : Nothing();
+}
+
diff --git a/netwerk/base/nsMIMEInputStream.h b/netwerk/base/nsMIMEInputStream.h
new file mode 100644
index 0000000000..38823d7ae4
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+#ifndef _nsMIMEInputStream_h_
+#define _nsMIMEInputStream_h_
+
+#include "nsIMIMEInputStream.h"
+
+#define NS_MIMEINPUTSTREAM_CONTRACTID "@mozilla.org/network/mime-input-stream;1"
+#define NS_MIMEINPUTSTREAM_CID \
+{ /* 58a1c31c-1dd2-11b2-a3f6-d36949d48268 */ \
+ 0x58a1c31c, \
+ 0x1dd2, \
+ 0x11b2, \
+ {0xa3, 0xf6, 0xd3, 0x69, 0x49, 0xd4, 0x82, 0x68} \
+}
+
+extern nsresult nsMIMEInputStreamConstructor(nsISupports *outer,
+ REFNSIID iid,
+ void **result);
+
+#endif // _nsMIMEInputStream_h_
diff --git a/netwerk/base/nsMediaFragmentURIParser.cpp b/netwerk/base/nsMediaFragmentURIParser.cpp
new file mode 100644
index 0000000000..af73fe52cc
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.cpp
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTArray.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsEscape.h"
+#include "nsIURI.h"
+#include <utility>
+
+#include "nsMediaFragmentURIParser.h"
+
+using std::pair;
+using std::make_pair;
+
+namespace mozilla { namespace net {
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsIURI* aURI)
+ : mClipUnit(eClipUnit_Pixel)
+{
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+ Parse(ref);
+}
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsCString& aRef)
+ : mClipUnit(eClipUnit_Pixel)
+{
+ Parse(aRef);
+}
+
+bool nsMediaFragmentURIParser::ParseNPT(nsDependentSubstring aString)
+{
+ nsDependentSubstring original(aString);
+ if (aString.Length() > 4 &&
+ aString[0] == 'n' && aString[1] == 'p' &&
+ aString[2] == 't' && aString[3] == ':') {
+ aString.Rebind(aString, 4);
+ }
+
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ double start = -1.0;
+ double end = -1.0;
+
+ ParseNPTTime(aString, start);
+
+ if (aString.Length() == 0) {
+ mStart.emplace(start);
+ return true;
+ }
+
+ if (aString[0] != ',') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+
+ if (aString.Length() == 0) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ ParseNPTTime(aString, end);
+
+ if (end <= start || aString.Length() != 0) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ mStart.emplace(start);
+ mEnd.emplace(end);
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTTime(nsDependentSubstring& aString, double& aTime)
+{
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ return
+ ParseNPTHHMMSS(aString, aTime) ||
+ ParseNPTMMSS(aString, aTime) ||
+ ParseNPTSec(aString, aTime);
+}
+
+// Return true if the given character is a numeric character
+static bool IsDigit(nsDependentSubstring::char_type aChar)
+{
+ return (aChar >= '0' && aChar <= '9');
+}
+
+// Return the index of the first character in the string that is not
+// a numerical digit, starting from 'aStart'.
+static uint32_t FirstNonDigit(nsDependentSubstring& aString, uint32_t aStart)
+{
+ while (aStart < aString.Length() && IsDigit(aString[aStart])) {
+ ++aStart;
+ }
+ return aStart;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTSec(nsDependentSubstring& aString, double& aSec)
+{
+ nsDependentSubstring original(aString);
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t s = PromiseFlatString(n).ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ double fraction = 0.0;
+ if (!ParseNPTFraction(aString, fraction)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aSec = s + fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTMMSS(nsDependentSubstring& aString, double& aTime)
+{
+ nsDependentSubstring original(aString);
+ uint32_t mm = 0;
+ uint32_t ss = 0;
+ double fraction = 0.0;
+ if (!ParseNPTMM(aString, mm)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ if (aString.Length() < 2 || aString[0] != ':') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+ if (!ParseNPTSS(aString, ss)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ if (!ParseNPTFraction(aString, fraction)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+ aTime = mm * 60 + ss + fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTFraction(nsDependentSubstring& aString, double& aFraction)
+{
+ double fraction = 0.0;
+
+ if (aString.Length() > 0 && aString[0] == '.') {
+ uint32_t index = FirstNonDigit(aString, 1);
+
+ if (index > 1) {
+ nsDependentSubstring number(aString, 0, index);
+ nsresult ec;
+ fraction = PromiseFlatString(number).ToDouble(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+ }
+ aString.Rebind(aString, index);
+ }
+
+ aFraction = fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime)
+{
+ nsDependentSubstring original(aString);
+ uint32_t hh = 0;
+ double seconds = 0.0;
+ if (!ParseNPTHH(aString, hh)) {
+ return false;
+ }
+
+ if (aString.Length() < 2 || aString[0] != ':') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+ if (!ParseNPTMMSS(aString, seconds)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aTime = hh * 3600 + seconds;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTHH(nsDependentSubstring& aString, uint32_t& aHour)
+{
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t u = PromiseFlatString(n).ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ aHour = u;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTMM(nsDependentSubstring& aString, uint32_t& aMinute)
+{
+ return ParseNPTSS(aString, aMinute);
+}
+
+bool nsMediaFragmentURIParser::ParseNPTSS(nsDependentSubstring& aString, uint32_t& aSecond)
+{
+ if (aString.Length() < 2) {
+ return false;
+ }
+
+ if (IsDigit(aString[0]) && IsDigit(aString[1])) {
+ nsDependentSubstring n(aString, 0, 2);
+ nsresult ec;
+ int32_t u = PromiseFlatString(n).ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, 2);
+ if (u >= 60)
+ return false;
+
+ aSecond = u;
+ return true;
+ }
+
+ return false;
+}
+
+static bool ParseInteger(nsDependentSubstring& aString,
+ int32_t& aResult)
+{
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t s = PromiseFlatString(n).ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ aResult = s;
+ return true;
+}
+
+static bool ParseCommaSeparator(nsDependentSubstring& aString)
+{
+ if (aString.Length() > 1 && aString[0] == ',') {
+ aString.Rebind(aString, 1);
+ return true;
+ }
+
+ return false;
+}
+
+bool nsMediaFragmentURIParser::ParseXYWH(nsDependentSubstring aString)
+{
+ int32_t x, y, w, h;
+ ClipUnit clipUnit;
+
+ // Determine units.
+ if (StringBeginsWith(aString, NS_LITERAL_STRING("pixel:"))) {
+ clipUnit = eClipUnit_Pixel;
+ aString.Rebind(aString, 6);
+ } else if (StringBeginsWith(aString, NS_LITERAL_STRING("percent:"))) {
+ clipUnit = eClipUnit_Percent;
+ aString.Rebind(aString, 8);
+ } else {
+ clipUnit = eClipUnit_Pixel;
+ }
+
+ // Read and validate coordinates.
+ if (ParseInteger(aString, x) && x >= 0 &&
+ ParseCommaSeparator(aString) &&
+ ParseInteger(aString, y) && y >= 0 &&
+ ParseCommaSeparator(aString) &&
+ ParseInteger(aString, w) && w > 0 &&
+ ParseCommaSeparator(aString) &&
+ ParseInteger(aString, h) && h > 0 &&
+ aString.Length() == 0) {
+
+ // Reject invalid percentage coordinates.
+ if (clipUnit == eClipUnit_Percent &&
+ (x + w > 100 || y + h > 100)) {
+ return false;
+ }
+
+ mClip.emplace(x, y, w, h);
+ mClipUnit = clipUnit;
+ return true;
+ }
+
+ return false;
+}
+
+bool nsMediaFragmentURIParser::ParseMozSampleSize(nsDependentSubstring aString)
+{
+ int32_t sampleSize;
+
+ // Read and validate coordinates.
+ if (ParseInteger(aString, sampleSize) && sampleSize > 0) {
+ mSampleSize.emplace(sampleSize);
+ return true;
+ }
+
+ return false;
+}
+
+void nsMediaFragmentURIParser::Parse(nsACString& aRef)
+{
+ // Create an array of possibly-invalid media fragments.
+ nsTArray< std::pair<nsCString, nsCString> > fragments;
+ nsCCharSeparatedTokenizer tokenizer(aRef, '&');
+
+ while (tokenizer.hasMoreTokens()) {
+ const nsCSubstring& nv = tokenizer.nextToken();
+ int32_t index = nv.FindChar('=');
+ if (index >= 0) {
+ nsAutoCString name;
+ nsAutoCString value;
+ NS_UnescapeURL(StringHead(nv, index), esc_Ref | esc_AlwaysCopy, name);
+ NS_UnescapeURL(Substring(nv, index + 1, nv.Length()),
+ esc_Ref | esc_AlwaysCopy, value);
+ fragments.AppendElement(make_pair(name, value));
+ }
+ }
+
+ // Parse the media fragment values.
+ bool gotTemporal = false, gotSpatial = false, gotSampleSize = false;
+ for (int i = fragments.Length() - 1 ; i >= 0 ; --i) {
+ if (gotTemporal && gotSpatial && gotSampleSize) {
+ // We've got one of each possible type. No need to look at the rest.
+ break;
+ } else if (!gotTemporal && fragments[i].first.EqualsLiteral("t")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotTemporal = ParseNPT(nsDependentSubstring(value, 0));
+ } else if (!gotSpatial && fragments[i].first.EqualsLiteral("xywh")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotSpatial = ParseXYWH(nsDependentSubstring(value, 0));
+ } else if (!gotSampleSize && fragments[i].first.EqualsLiteral("-moz-samplesize")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotSampleSize = ParseMozSampleSize(nsDependentSubstring(value, 0));
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsMediaFragmentURIParser.h b/netwerk/base/nsMediaFragmentURIParser.h
new file mode 100644
index 0000000000..acfa1d5fea
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(nsMediaFragmentURIParser_h__)
+#define nsMediaFragmentURIParser_h__
+
+#include "mozilla/Maybe.h"
+#include "nsStringFwd.h"
+#include "nsRect.h"
+
+class nsIURI;
+
+// Class to handle parsing of a W3C media fragment URI as per
+// spec at: http://www.w3.org/TR/media-frags/
+// Only the temporaral URI portion of the spec is implemented.
+// To use:
+// a) Construct an instance with the URI containing the fragment
+// b) Check for the validity of the values you are interested in
+// using e.g. HasStartTime().
+// c) If the values are valid, obtain them using e.g. GetStartTime().
+
+namespace mozilla { namespace net {
+
+enum ClipUnit
+{
+ eClipUnit_Pixel,
+ eClipUnit_Percent,
+};
+
+class nsMediaFragmentURIParser
+{
+public:
+ // Create a parser with the provided URI.
+ explicit nsMediaFragmentURIParser(nsIURI* aURI);
+
+ // Create a parser with the provided URI reference portion.
+ explicit nsMediaFragmentURIParser(nsCString& aRef);
+
+ // True if a valid temporal media fragment indicated a start time.
+ bool HasStartTime() const { return mStart.isSome(); }
+
+ // If a valid temporal media fragment indicated a start time, returns
+ // it in units of seconds. If not, defaults to 0.
+ double GetStartTime() const { return *mStart; }
+
+ // True if a valid temporal media fragment indicated an end time.
+ bool HasEndTime() const { return mEnd.isSome(); }
+
+ // If a valid temporal media fragment indicated an end time, returns
+ // it in units of seconds. If not, defaults to -1.
+ double GetEndTime() const { return *mEnd; }
+
+ // True if a valid spatial media fragment indicated a clipping region.
+ bool HasClip() const { return mClip.isSome(); }
+
+ // If a valid spatial media fragment indicated a clipping region,
+ // returns the region. If not, returns an empty region. The unit
+ // used depends on the value returned by GetClipUnit().
+ nsIntRect GetClip() const { return *mClip; }
+
+ // If a valid spatial media fragment indicated a clipping region,
+ // returns the unit used.
+ ClipUnit GetClipUnit() const { return mClipUnit; }
+
+ bool HasSampleSize() const { return mSampleSize.isSome(); }
+
+ int GetSampleSize() const { return *mSampleSize; }
+
+private:
+ // Parse the URI ref provided, looking for media fragments. This is
+ // the top-level parser the invokes the others below.
+ void Parse(nsACString& aRef);
+
+ // The following methods parse the fragment as per the media
+ // fragments specification. 'aString' contains the remaining
+ // fragment data to be parsed. The method returns true
+ // if the parse was successful and leaves the remaining unparsed
+ // data in 'aString'. If the parse fails then false is returned
+ // and 'aString' is left as it was when called.
+ bool ParseNPT(nsDependentSubstring aString);
+ bool ParseNPTTime(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTSec(nsDependentSubstring& aString, double& aSec);
+ bool ParseNPTFraction(nsDependentSubstring& aString, double& aFraction);
+ bool ParseNPTMMSS(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTHH(nsDependentSubstring& aString, uint32_t& aHour);
+ bool ParseNPTMM(nsDependentSubstring& aString, uint32_t& aMinute);
+ bool ParseNPTSS(nsDependentSubstring& aString, uint32_t& aSecond);
+ bool ParseXYWH(nsDependentSubstring aString);
+ bool ParseMozResolution(nsDependentSubstring aString);
+ bool ParseMozSampleSize(nsDependentSubstring aString);
+
+ // Media fragment information.
+ Maybe<double> mStart;
+ Maybe<double> mEnd;
+ Maybe<nsIntRect> mClip;
+ ClipUnit mClipUnit;
+ Maybe<int> mSampleSize;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsNetAddr.cpp b/netwerk/base/nsNetAddr.cpp
new file mode 100644
index 0000000000..8d6f245cf3
--- /dev/null
+++ b/netwerk/base/nsNetAddr.cpp
@@ -0,0 +1,152 @@
+/* vim: et ts=2 sw=2 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNetAddr.h"
+#include "nsString.h"
+#include "mozilla/net/DNS.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(nsNetAddr, nsINetAddr)
+
+/* Makes a copy of |addr| */
+nsNetAddr::nsNetAddr(NetAddr* addr)
+{
+ NS_ASSERTION(addr, "null addr");
+ mAddr = *addr;
+}
+
+NS_IMETHODIMP nsNetAddr::GetFamily(uint16_t *aFamily)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET:
+ *aFamily = nsINetAddr::FAMILY_INET;
+ break;
+ case AF_INET6:
+ *aFamily = nsINetAddr::FAMILY_INET6;
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ *aFamily = nsINetAddr::FAMILY_LOCAL;
+ break;
+#endif
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetAddress(nsACString & aAddress)
+{
+ switch(mAddr.raw.family) {
+ /* PR_NetAddrToString can handle INET and INET6, but not LOCAL. */
+ case AF_INET:
+ aAddress.SetCapacity(kIPv4CStrBufSize);
+ NetAddrToString(&mAddr, aAddress.BeginWriting(), kIPv4CStrBufSize);
+ aAddress.SetLength(strlen(aAddress.BeginReading()));
+ break;
+ case AF_INET6:
+ aAddress.SetCapacity(kIPv6CStrBufSize);
+ NetAddrToString(&mAddr, aAddress.BeginWriting(), kIPv6CStrBufSize);
+ aAddress.SetLength(strlen(aAddress.BeginReading()));
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ aAddress.Assign(mAddr.local.path);
+ break;
+#endif
+ // PR_AF_LOCAL falls through to default when not XP_UNIX
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetPort(uint16_t *aPort)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET:
+ *aPort = ntohs(mAddr.inet.port);
+ break;
+ case AF_INET6:
+ *aPort = ntohs(mAddr.inet6.port);
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ // There is no port number for local / connections.
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetFlow(uint32_t *aFlow)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET6:
+ *aFlow = ntohl(mAddr.inet6.flowinfo);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetScope(uint32_t *aScope)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET6:
+ *aScope = ntohl(mAddr.inet6.scope_id);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetIsV4Mapped(bool *aIsV4Mapped)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET6:
+ *aIsV4Mapped = IPv6ADDR_IS_V4MAPPED(&mAddr.inet6.ip);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetNetAddr(NetAddr *aResult) {
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
diff --git a/netwerk/base/nsNetAddr.h b/netwerk/base/nsNetAddr.h
new file mode 100644
index 0000000000..2717596f65
--- /dev/null
+++ b/netwerk/base/nsNetAddr.h
@@ -0,0 +1,31 @@
+/* vim: et ts=2 sw=2 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetAddr_h__
+#define nsNetAddr_h__
+
+#include "nsINetAddr.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Attributes.h"
+
+class nsNetAddr final : public nsINetAddr
+{
+ ~nsNetAddr() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETADDR
+
+ explicit nsNetAddr(mozilla::net::NetAddr* addr);
+
+private:
+ mozilla::net::NetAddr mAddr;
+
+protected:
+ /* additional members */
+};
+
+#endif // !nsNetAddr_h__
diff --git a/netwerk/base/nsNetSegmentUtils.h b/netwerk/base/nsNetSegmentUtils.h
new file mode 100644
index 0000000000..41808eb50d
--- /dev/null
+++ b/netwerk/base/nsNetSegmentUtils.h
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetSegmentUtils_h__
+#define nsNetSegmentUtils_h__
+
+#include "nsIOService.h"
+
+/**
+ * applies defaults to segment params in a consistent way.
+ */
+static inline void
+net_ResolveSegmentParams(uint32_t &segsize, uint32_t &segcount)
+{
+ if (!segsize)
+ segsize = mozilla::net::nsIOService::gDefaultSegmentSize;
+
+ if (!segcount)
+ segcount = mozilla::net::nsIOService::gDefaultSegmentCount;
+}
+
+#endif // !nsNetSegmentUtils_h__
diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp
new file mode 100644
index 0000000000..8ff3e788f3
--- /dev/null
+++ b/netwerk/base/nsNetUtil.cpp
@@ -0,0 +1,2459 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/LoadContext.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Telemetry.h"
+#include "nsNetUtil.h"
+#include "nsNetUtilInlines.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "nsCategoryCache.h"
+#include "nsContentUtils.h"
+#include "nsHashKeys.h"
+#include "nsHttp.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIAuthPromptAdapterFactory.h"
+#include "nsIBufferedStreams.h"
+#include "nsIChannelEventSink.h"
+#include "nsIContentSniffer.h"
+#include "nsIDocument.h"
+#include "nsIDownloader.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIIDNService.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsIMutable.h"
+#include "nsINode.h"
+#include "nsIOfflineCacheUpdate.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIPropertyBag2.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIRedirectChannelRegistrar.h"
+#include "nsIRequestObserverProxy.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISimpleStreamListener.h"
+#include "nsISocketProvider.h"
+#include "nsISocketProviderService.h"
+#include "nsIStandardURL.h"
+#include "nsIStreamLoader.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsIStreamTransportService.h"
+#include "nsStringStream.h"
+#include "nsISyncStreamListener.h"
+#include "nsITransport.h"
+#include "nsIUnicharStreamLoader.h"
+#include "nsIURIWithPrincipal.h"
+#include "nsIURLParser.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIViewSourceChannel.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "plstr.h"
+#include "nsINestedURI.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "nsIScriptError.h"
+#include "nsISiteSecurityService.h"
+#include "nsHttpHandler.h"
+#include "nsNSSComponent.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkManager.h"
+#include "nsThreadUtils.h" // for NS_IsMainThread
+#endif
+
+#include <limits>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ MOZ_ASSERT(aLoadingNode);
+ NS_ASSERTION(aTriggeringPrincipal, "Can not create channel without a triggering Principal!");
+ return NS_NewChannelInternal(outChannel,
+ aUri,
+ aLoadingNode,
+ aLoadingNode->NodePrincipal(),
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aIoService);
+}
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /*NS_NewChannelWithPrincipalAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ NS_ASSERTION(aLoadingPrincipal, "Can not create channel without a loading Principal!");
+ return NS_NewChannelInternal(outChannel,
+ aUri,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aIoService);
+}
+
+nsresult /* NS_NewChannelNode */
+NS_NewChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ NS_ASSERTION(aLoadingNode, "Can not create channel without a loading Node!");
+ return NS_NewChannelInternal(outChannel,
+ aUri,
+ aLoadingNode,
+ aLoadingNode->NodePrincipal(),
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aIoService);
+}
+
+nsresult
+NS_MakeAbsoluteURI(nsACString &result,
+ const nsACString &spec,
+ nsIURI *baseURI)
+{
+ nsresult rv;
+ if (!baseURI) {
+ NS_WARNING("It doesn't make sense to not supply a base URI");
+ result = spec;
+ rv = NS_OK;
+ }
+ else if (spec.IsEmpty())
+ rv = baseURI->GetSpec(result);
+ else
+ rv = baseURI->Resolve(spec, result);
+ return rv;
+}
+
+nsresult
+NS_MakeAbsoluteURI(char **result,
+ const char *spec,
+ nsIURI *baseURI)
+{
+ nsresult rv;
+ nsAutoCString resultBuf;
+ rv = NS_MakeAbsoluteURI(resultBuf, nsDependentCString(spec), baseURI);
+ if (NS_SUCCEEDED(rv)) {
+ *result = ToNewCString(resultBuf);
+ if (!*result)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return rv;
+}
+
+nsresult
+NS_MakeAbsoluteURI(nsAString &result,
+ const nsAString &spec,
+ nsIURI *baseURI)
+{
+ nsresult rv;
+ if (!baseURI) {
+ NS_WARNING("It doesn't make sense to not supply a base URI");
+ result = spec;
+ rv = NS_OK;
+ }
+ else {
+ nsAutoCString resultBuf;
+ if (spec.IsEmpty())
+ rv = baseURI->GetSpec(resultBuf);
+ else
+ rv = baseURI->Resolve(NS_ConvertUTF16toUTF8(spec), resultBuf);
+ if (NS_SUCCEEDED(rv))
+ CopyUTF8toUTF16(resultBuf, result);
+ }
+ return rv;
+}
+
+int32_t
+NS_GetDefaultPort(const char *scheme,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> grip;
+ net_EnsureIOService(&ioService, grip);
+ if (!ioService)
+ return -1;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return -1;
+ int32_t port;
+ rv = handler->GetDefaultPort(&port);
+ return NS_SUCCEEDED(rv) ? port : -1;
+}
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool
+NS_StringToACE(const nsACString &idn, nsACString &result)
+{
+ nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (!idnSrv)
+ return false;
+ nsresult rv = idnSrv->ConvertUTF8toACE(idn, result);
+ if (NS_FAILED(rv))
+ return false;
+
+ return true;
+}
+
+int32_t
+NS_GetRealPort(nsIURI *aURI)
+{
+ int32_t port;
+ nsresult rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv))
+ return -1;
+
+ if (port != -1)
+ return port; // explicitly specified
+
+ // Otherwise, we have to get the default port from the protocol handler
+
+ // Need the scheme first
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv))
+ return -1;
+
+ return NS_GetDefaultPort(scheme.get());
+}
+
+nsresult /* NS_NewInputStreamChannelWithLoadInfo */
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ const nsACString &aContentType,
+ const nsACString &aContentCharset,
+ nsILoadInfo *aLoadInfo)
+{
+ nsresult rv;
+ nsCOMPtr<nsIInputStreamChannel> isc =
+ do_CreateInstance(NS_INPUTSTREAMCHANNEL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = isc->SetURI(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = isc->SetContentStream(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(isc, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aContentType.IsEmpty()) {
+ rv = channel->SetContentType(aContentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aContentCharset.IsEmpty()) {
+ rv = channel->SetContentCharset(aContentCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel->SetLoadInfo(aLoadInfo);
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (aLoadInfo && aLoadInfo->GetLoadingSandboxed()) {
+ channel->SetOwner(nullptr);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ const nsACString &aContentType,
+ const nsACString &aContentCharset,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new mozilla::LoadInfo(aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aLoadingNode,
+ aSecurityFlags,
+ aContentPolicyType);
+ if (!loadInfo) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_NewInputStreamChannelInternal(outChannel,
+ aUri,
+ aStream,
+ aContentType,
+ aContentCharset,
+ loadInfo);
+}
+
+nsresult /* NS_NewInputStreamChannelPrincipal */
+NS_NewInputStreamChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const nsACString &aContentType /* = EmptyCString() */,
+ const nsACString &aContentCharset /* = EmptyCString() */)
+{
+ return NS_NewInputStreamChannelInternal(outChannel,
+ aUri,
+ aStream,
+ aContentType,
+ aContentCharset,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType);
+}
+
+nsresult
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsILoadInfo *aLoadInfo,
+ bool aIsSrcdocChannel /* = false */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> stream;
+ stream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef MOZILLA_INTERNAL_API
+ uint32_t len;
+ char* utf8Bytes = ToNewUTF8String(aData, &len);
+ rv = stream->AdoptData(utf8Bytes, len);
+#else
+ char* utf8Bytes = ToNewUTF8String(aData);
+ rv = stream->AdoptData(utf8Bytes, strlen(utf8Bytes));
+#endif
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
+ aUri,
+ stream,
+ aContentType,
+ NS_LITERAL_CSTRING("UTF-8"),
+ aLoadInfo);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIsSrcdocChannel) {
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(inStrmChan, NS_ERROR_FAILURE);
+ inStrmChan->SetSrcdocData(aData);
+ }
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel /* = false */)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new mozilla::LoadInfo(aLoadingPrincipal, aTriggeringPrincipal,
+ aLoadingNode, aSecurityFlags, aContentPolicyType);
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, aData, aContentType,
+ loadInfo, aIsSrcdocChannel);
+}
+
+nsresult
+NS_NewInputStreamChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel /* = false */)
+{
+ return NS_NewInputStreamChannelInternal(outChannel,
+ aUri,
+ aData,
+ aContentType,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType,
+ aIsSrcdocChannel);
+}
+
+nsresult
+NS_NewInputStreamPump(nsIInputStreamPump **result,
+ nsIInputStream *stream,
+ int64_t streamPos /* = int64_t(-1) */,
+ int64_t streamLen /* = int64_t(-1) */,
+ uint32_t segsize /* = 0 */,
+ uint32_t segcount /* = 0 */,
+ bool closeWhenDone /* = false */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIInputStreamPump> pump =
+ do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = pump->Init(stream, streamPos, streamLen,
+ segsize, segcount, closeWhenDone);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ pump.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewAsyncStreamCopier(nsIAsyncStreamCopier **result,
+ nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ bool sourceBuffered /* = true */,
+ bool sinkBuffered /* = true */,
+ uint32_t chunkSize /* = 0 */,
+ bool closeSource /* = true */,
+ bool closeSink /* = true */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAsyncStreamCopier> copier =
+ do_CreateInstance(NS_ASYNCSTREAMCOPIER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = copier->Init(source, sink, target, sourceBuffered, sinkBuffered,
+ chunkSize, closeSource, closeSink);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ copier.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewLoadGroup(nsILoadGroup **result,
+ nsIRequestObserver *obs)
+{
+ nsresult rv;
+ nsCOMPtr<nsILoadGroup> group =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = group->SetGroupObserver(obs);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ group.swap(*result);
+ }
+ }
+ return rv;
+}
+
+bool NS_IsReasonableHTTPHeaderValue(const nsACString &aValue)
+{
+ return mozilla::net::nsHttp::IsReasonableHeaderValue(aValue);
+}
+
+bool NS_IsValidHTTPToken(const nsACString &aToken)
+{
+ return mozilla::net::nsHttp::IsValidToken(aToken);
+}
+
+nsresult
+NS_NewLoadGroup(nsILoadGroup **aResult, nsIPrincipal *aPrincipal)
+{
+ using mozilla::LoadContext;
+ nsresult rv;
+
+ nsCOMPtr<nsILoadGroup> group =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<LoadContext> loadContext = new LoadContext(aPrincipal);
+ rv = group->SetNotificationCallbacks(loadContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ group.forget(aResult);
+ return rv;
+}
+
+bool
+NS_LoadGroupMatchesPrincipal(nsILoadGroup *aLoadGroup,
+ nsIPrincipal *aPrincipal)
+{
+ if (!aPrincipal) {
+ return false;
+ }
+
+ // If this is a null principal then the load group doesn't really matter.
+ // The principal will not be allowed to perform any actions that actually
+ // use the load group. Unconditionally treat null principals as a match.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return true;
+ }
+
+ if (!aLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(nullptr, aLoadGroup, NS_GET_IID(nsILoadContext),
+ getter_AddRefs(loadContext));
+ NS_ENSURE_TRUE(loadContext, false);
+
+ // Verify load context appId and browser flag match the principal
+ uint32_t contextAppId;
+ bool contextInIsolatedBrowser;
+ nsresult rv = loadContext->GetAppId(&contextAppId);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = loadContext->GetIsInIsolatedMozBrowserElement(&contextInIsolatedBrowser);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return contextAppId == aPrincipal->GetAppId() &&
+ contextInIsolatedBrowser == aPrincipal->GetIsInIsolatedMozBrowserElement();
+}
+
+nsresult
+NS_NewDownloader(nsIStreamListener **result,
+ nsIDownloadObserver *observer,
+ nsIFile *downloadLocation /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDownloader> downloader =
+ do_CreateInstance(NS_DOWNLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = downloader->Init(observer, downloadLocation);
+ if (NS_SUCCEEDED(rv)) {
+ downloader.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewIncrementalStreamLoader(nsIIncrementalStreamLoader **result,
+ nsIIncrementalStreamLoaderObserver *observer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIIncrementalStreamLoader> loader =
+ do_CreateInstance(NS_INCREMENTALSTREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewStreamLoaderInternal(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIURI *aReferrer /* = nullptr */)
+{
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannelInternal(getter_AddRefs(channel),
+ aUri,
+ aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ httpChannel->SetReferrer(aReferrer);
+ }
+ rv = NS_NewStreamLoader(outStream, aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return channel->AsyncOpen2(*outStream);
+}
+
+
+nsresult /* NS_NewStreamLoaderNode */
+NS_NewStreamLoader(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsINode *aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIURI *aReferrer /* = nullptr */)
+{
+ NS_ASSERTION(aLoadingNode, "Can not create stream loader without a loading Node!");
+ return NS_NewStreamLoaderInternal(outStream,
+ aUri,
+ aObserver,
+ aLoadingNode,
+ aLoadingNode->NodePrincipal(),
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aReferrer);
+}
+
+nsresult /* NS_NewStreamLoaderPrincipal */
+NS_NewStreamLoader(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIURI *aReferrer /* = nullptr */)
+{
+ return NS_NewStreamLoaderInternal(outStream,
+ aUri,
+ aObserver,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aReferrer);
+}
+
+nsresult
+NS_NewUnicharStreamLoader(nsIUnicharStreamLoader **result,
+ nsIUnicharStreamLoaderObserver *observer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIUnicharStreamLoader> loader =
+ do_CreateInstance(NS_UNICHARSTREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewSyncStreamListener(nsIStreamListener **result,
+ nsIInputStream **stream)
+{
+ nsresult rv;
+ nsCOMPtr<nsISyncStreamListener> listener =
+ do_CreateInstance(NS_SYNCSTREAMLISTENER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = listener->GetInputStream(stream);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_ImplementChannelOpen(nsIChannel *channel,
+ nsIInputStream **result)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewSyncStreamListener(getter_AddRefs(listener),
+ getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_MaybeOpenChannelUsingAsyncOpen2(channel, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t n;
+ // block until the initial response is received or an error occurs.
+ rv = stream->Available(&n);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = nullptr;
+ stream.swap(*result);
+
+ return NS_OK;
+ }
+
+nsresult
+NS_NewRequestObserverProxy(nsIRequestObserver **result,
+ nsIRequestObserver *observer,
+ nsISupports *context)
+{
+ nsresult rv;
+ nsCOMPtr<nsIRequestObserverProxy> proxy =
+ do_CreateInstance(NS_REQUESTOBSERVERPROXY_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = proxy->Init(observer, context);
+ if (NS_SUCCEEDED(rv)) {
+ proxy.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewSimpleStreamListener(nsIStreamListener **result,
+ nsIOutputStream *sink,
+ nsIRequestObserver *observer /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsISimpleStreamListener> listener =
+ do_CreateInstance(NS_SIMPLESTREAMLISTENER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = listener->Init(sink, observer);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_CheckPortSafety(int32_t port,
+ const char *scheme,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) {
+ bool allow;
+ rv = ioService->AllowPort(port, scheme, &allow);
+ if (NS_SUCCEEDED(rv) && !allow) {
+ NS_WARNING("port blocked");
+ rv = NS_ERROR_PORT_ACCESS_NOT_ALLOWED;
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_CheckPortSafety(nsIURI *uri)
+{
+ int32_t port;
+ nsresult rv = uri->GetPort(&port);
+ if (NS_FAILED(rv) || port == -1) // port undefined or default-valued
+ return NS_OK;
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ return NS_CheckPortSafety(port, scheme.get());
+}
+
+nsresult
+NS_NewProxyInfo(const nsACString &type,
+ const nsACString &host,
+ int32_t port,
+ uint32_t flags,
+ nsIProxyInfo **result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = pps->NewProxyInfo(type, host, port, flags, UINT32_MAX, nullptr,
+ result);
+ return rv;
+}
+
+nsresult
+NS_GetFileProtocolHandler(nsIFileProtocolHandler **result,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) {
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler("file", getter_AddRefs(handler));
+ if (NS_SUCCEEDED(rv))
+ rv = CallQueryInterface(handler, result);
+ }
+ return rv;
+}
+
+nsresult
+NS_GetFileFromURLSpec(const nsACString &inURL,
+ nsIFile **result,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv))
+ rv = fileHandler->GetFileFromURLSpec(inURL, result);
+ return rv;
+}
+
+nsresult
+NS_GetURLSpecFromFile(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv))
+ rv = fileHandler->GetURLSpecFromFile(file, url);
+ return rv;
+}
+
+nsresult
+NS_GetURLSpecFromActualFile(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv))
+ rv = fileHandler->GetURLSpecFromActualFile(file, url);
+ return rv;
+}
+
+nsresult
+NS_GetURLSpecFromDir(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv))
+ rv = fileHandler->GetURLSpecFromDir(file, url);
+ return rv;
+}
+
+nsresult
+NS_GetReferrerFromChannel(nsIChannel *channel,
+ nsIURI **referrer)
+{
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ *referrer = nullptr;
+
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(channel));
+ if (props) {
+ // We have to check for a property on a property bag because the
+ // referrer may be empty for security reasons (for example, when loading
+ // an http page with an https referrer).
+ rv = props->GetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
+ NS_GET_IID(nsIURI),
+ reinterpret_cast<void **>(referrer));
+ if (NS_FAILED(rv))
+ *referrer = nullptr;
+ }
+
+ // if that didn't work, we can still try to get the referrer from the
+ // nsIHttpChannel (if we can QI to it)
+ if (!(*referrer)) {
+ nsCOMPtr<nsIHttpChannel> chan(do_QueryInterface(channel));
+ if (chan) {
+ rv = chan->GetReferrer(referrer);
+ if (NS_FAILED(rv))
+ *referrer = nullptr;
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_ParseRequestContentType(const nsACString &rawContentType,
+ nsCString &contentType,
+ nsCString &contentCharset)
+{
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString charset;
+ bool hadCharset;
+ rv = util->ParseRequestContentType(rawContentType, charset, &hadCharset,
+ contentType);
+ if (NS_SUCCEEDED(rv) && hadCharset)
+ contentCharset = charset;
+ return rv;
+}
+
+nsresult
+NS_ParseResponseContentType(const nsACString &rawContentType,
+ nsCString &contentType,
+ nsCString &contentCharset)
+{
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString charset;
+ bool hadCharset;
+ rv = util->ParseResponseContentType(rawContentType, charset, &hadCharset,
+ contentType);
+ if (NS_SUCCEEDED(rv) && hadCharset)
+ contentCharset = charset;
+ return rv;
+}
+
+nsresult
+NS_ExtractCharsetFromContentType(const nsACString &rawContentType,
+ nsCString &contentCharset,
+ bool *hadCharset,
+ int32_t *charsetStart,
+ int32_t *charsetEnd)
+{
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return util->ExtractCharsetFromContentType(rawContentType,
+ contentCharset,
+ charsetStart,
+ charsetEnd,
+ hadCharset);
+}
+
+nsresult
+NS_NewPartialLocalFileInputStream(nsIInputStream **result,
+ nsIFile *file,
+ uint64_t offset,
+ uint64_t length,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPartialFileInputStream> in =
+ do_CreateInstance(NS_PARTIALLOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(file, offset, length, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ rv = CallQueryInterface(in, result);
+ }
+ return rv;
+}
+
+nsresult
+NS_NewAtomicFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_ATOMICLOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ out.forget(result);
+ }
+ return rv;
+}
+
+nsresult
+NS_NewSafeLocalFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ out.forget(result);
+ }
+ return rv;
+}
+
+nsresult
+NS_NewLocalFileStream(nsIFileStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileStream> stream =
+ do_CreateInstance(NS_LOCALFILESTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = stream->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ stream.forget(result);
+ }
+ return rv;
+}
+
+nsresult
+NS_BackgroundInputStream(nsIInputStream **result,
+ nsIInputStream *stream,
+ uint32_t segmentSize /* = 0 */,
+ uint32_t segmentCount /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsITransport> inTransport;
+ rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1),
+ true, getter_AddRefs(inTransport));
+ if (NS_SUCCEEDED(rv))
+ rv = inTransport->OpenInputStream(nsITransport::OPEN_BLOCKING,
+ segmentSize, segmentCount,
+ result);
+ }
+ return rv;
+}
+
+nsresult
+NS_BackgroundOutputStream(nsIOutputStream **result,
+ nsIOutputStream *stream,
+ uint32_t segmentSize /* = 0 */,
+ uint32_t segmentCount /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsITransport> inTransport;
+ rv = sts->CreateOutputTransport(stream, int64_t(-1), int64_t(-1),
+ true, getter_AddRefs(inTransport));
+ if (NS_SUCCEEDED(rv))
+ rv = inTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING,
+ segmentSize, segmentCount,
+ result);
+ }
+ return rv;
+}
+
+nsresult
+NS_NewBufferedOutputStream(nsIOutputStream **result,
+ nsIOutputStream *str,
+ uint32_t bufferSize)
+{
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> out =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(str, bufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ out.forget(result);
+ }
+ }
+ return rv;
+}
+
+already_AddRefed<nsIOutputStream>
+NS_BufferOutputStream(nsIOutputStream *aOutputStream,
+ uint32_t aBufferSize)
+{
+ NS_ASSERTION(aOutputStream, "No output stream given!");
+
+ nsCOMPtr<nsIOutputStream> bos;
+ nsresult rv = NS_NewBufferedOutputStream(getter_AddRefs(bos), aOutputStream,
+ aBufferSize);
+ if (NS_SUCCEEDED(rv))
+ return bos.forget();
+
+ bos = aOutputStream;
+ return bos.forget();
+}
+
+already_AddRefed<nsIInputStream>
+NS_BufferInputStream(nsIInputStream *aInputStream,
+ uint32_t aBufferSize)
+{
+ NS_ASSERTION(aInputStream, "No input stream given!");
+
+ nsCOMPtr<nsIInputStream> bis;
+ nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bis), aInputStream,
+ aBufferSize);
+ if (NS_SUCCEEDED(rv))
+ return bis.forget();
+
+ bis = aInputStream;
+ return bis.forget();
+}
+
+nsresult
+NS_ReadInputStreamToBuffer(nsIInputStream *aInputStream,
+ void **aDest,
+ uint32_t aCount)
+{
+ nsresult rv;
+
+ if (!*aDest) {
+ *aDest = malloc(aCount);
+ if (!*aDest)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char * p = reinterpret_cast<char*>(*aDest);
+ uint32_t bytesRead;
+ uint32_t totalRead = 0;
+ while (1) {
+ rv = aInputStream->Read(p + totalRead, aCount - totalRead, &bytesRead);
+ if (!NS_SUCCEEDED(rv))
+ return rv;
+ totalRead += bytesRead;
+ if (totalRead == aCount)
+ break;
+ // if Read reads 0 bytes, we've hit EOF
+ if (bytesRead == 0)
+ return NS_ERROR_UNEXPECTED;
+ }
+ return rv;
+}
+
+#ifdef MOZILLA_INTERNAL_API
+
+nsresult
+NS_ReadInputStreamToString(nsIInputStream *aInputStream,
+ nsACString &aDest,
+ uint32_t aCount)
+{
+ if (!aDest.SetLength(aCount, mozilla::fallible))
+ return NS_ERROR_OUT_OF_MEMORY;
+ void* dest = aDest.BeginWriting();
+ return NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount);
+}
+
+#endif
+
+nsresult
+NS_LoadPersistentPropertiesFromURISpec(nsIPersistentProperties **outResult,
+ const nsACString &aSpec)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open2(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPersistentProperties> properties =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = properties->Load(in);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ properties.swap(*outResult);
+ return NS_OK;
+}
+
+bool
+NS_UsePrivateBrowsing(nsIChannel *channel)
+{
+ bool isPrivate = false;
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
+ if (pbChannel && NS_SUCCEEDED(pbChannel->GetIsChannelPrivate(&isPrivate))) {
+ return isPrivate;
+ }
+
+ // Some channels may not implement nsIPrivateBrowsingChannel
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(channel, loadContext);
+ return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+bool
+NS_GetOriginAttributes(nsIChannel *aChannel,
+ mozilla::NeckoOriginAttributes &aAttributes)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (!loadInfo) {
+ return false;
+ }
+
+ loadInfo->GetOriginAttributes(&aAttributes);
+ aAttributes.SyncAttributesWithPrivateBrowsing(NS_UsePrivateBrowsing(aChannel));
+ return true;
+}
+
+bool
+NS_GetAppInfo(nsIChannel *aChannel,
+ uint32_t *aAppID,
+ bool *aIsInIsolatedMozBrowserElement)
+{
+ NeckoOriginAttributes attrs;
+
+ if (!NS_GetOriginAttributes(aChannel, attrs)) {
+ return false;
+ }
+
+ *aAppID = attrs.mAppId;
+ *aIsInIsolatedMozBrowserElement = attrs.mInIsolatedMozBrowser;
+
+ return true;
+}
+
+bool
+NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ MOZ_RELEASE_ASSERT(loadInfo, "Origin tracking only works for channels created with a loadinfo");
+
+#ifdef DEBUG
+ // Don't enforce TYPE_DOCUMENT assertions for loads
+ // initiated by javascript tests.
+ bool skipContentTypeCheck = false;
+ skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion");
+#endif
+
+ MOZ_ASSERT(skipContentTypeCheck ||
+ loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT,
+ "calling NS_HasBeenCrossOrigin on a top level load");
+
+ // Always treat tainted channels as cross-origin.
+ if (loadInfo->GetTainting() != LoadTainting::Basic) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->LoadingPrincipal();
+ uint32_t mode = loadInfo->GetSecurityMode();
+ bool dataInherits =
+ mode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS ||
+ mode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS ||
+ mode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+
+ bool aboutBlankInherits = dataInherits && loadInfo->GetAboutBlankInherits();
+
+ for (nsIPrincipal* principal : loadInfo->RedirectChain()) {
+ nsCOMPtr<nsIURI> uri;
+ principal->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ return true;
+ }
+
+ if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+ continue;
+ }
+
+ if (NS_FAILED(loadingPrincipal->CheckMayLoad(uri, aReport, dataInherits))) {
+ return true;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ if (!uri) {
+ return true;
+ }
+
+ if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+ return false;
+ }
+
+ return NS_FAILED(loadingPrincipal->CheckMayLoad(uri, aReport, dataInherits));
+}
+
+nsresult
+NS_GetAppInfoFromClearDataNotification(nsISupports *aSubject,
+ uint32_t *aAppID,
+ bool *aBrowserOnly)
+{
+ nsresult rv;
+
+ nsCOMPtr<mozIApplicationClearPrivateDataParams>
+ clearParams(do_QueryInterface(aSubject));
+ MOZ_ASSERT(clearParams);
+ if (!clearParams) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t appId;
+ rv = clearParams->GetAppId(&appId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(appId != NECKO_UNKNOWN_APP_ID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (appId == NECKO_UNKNOWN_APP_ID) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool browserOnly = false;
+ rv = clearParams->GetBrowserOnly(&browserOnly);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aAppID = appId;
+ *aBrowserOnly = browserOnly;
+ return NS_OK;
+}
+
+bool
+NS_ShouldCheckAppCache(nsIURI *aURI, bool usePrivateBrowsing)
+{
+ if (usePrivateBrowsing) {
+ return false;
+ }
+
+ nsCOMPtr<nsIOfflineCacheUpdateService> offlineService =
+ do_GetService("@mozilla.org/offlinecacheupdate-service;1");
+ if (!offlineService) {
+ return false;
+ }
+
+ bool allowed;
+ nsresult rv = offlineService->OfflineAppAllowedForURI(aURI,
+ nullptr,
+ &allowed);
+ return NS_SUCCEEDED(rv) && allowed;
+}
+
+bool
+NS_ShouldCheckAppCache(nsIPrincipal *aPrincipal, bool usePrivateBrowsing)
+{
+ if (usePrivateBrowsing) {
+ return false;
+ }
+
+ nsCOMPtr<nsIOfflineCacheUpdateService> offlineService =
+ do_GetService("@mozilla.org/offlinecacheupdate-service;1");
+ if (!offlineService) {
+ return false;
+ }
+
+ bool allowed;
+ nsresult rv = offlineService->OfflineAppAllowed(aPrincipal,
+ nullptr,
+ &allowed);
+ return NS_SUCCEEDED(rv) && allowed;
+}
+
+void
+NS_WrapAuthPrompt(nsIAuthPrompt *aAuthPrompt,
+ nsIAuthPrompt2 **aAuthPrompt2)
+{
+ nsCOMPtr<nsIAuthPromptAdapterFactory> factory =
+ do_GetService(NS_AUTHPROMPT_ADAPTER_FACTORY_CONTRACTID);
+ if (!factory)
+ return;
+
+ NS_WARNING("Using deprecated nsIAuthPrompt");
+ factory->CreateAdapter(aAuthPrompt, aAuthPrompt2);
+}
+
+void
+NS_QueryAuthPrompt2(nsIInterfaceRequestor *aCallbacks,
+ nsIAuthPrompt2 **aAuthPrompt)
+{
+ CallGetInterface(aCallbacks, aAuthPrompt);
+ if (*aAuthPrompt)
+ return;
+
+ // Maybe only nsIAuthPrompt is provided and we have to wrap it.
+ nsCOMPtr<nsIAuthPrompt> prompt(do_GetInterface(aCallbacks));
+ if (!prompt)
+ return;
+
+ NS_WrapAuthPrompt(prompt, aAuthPrompt);
+}
+
+void
+NS_QueryAuthPrompt2(nsIChannel *aChannel,
+ nsIAuthPrompt2 **aAuthPrompt)
+{
+ *aAuthPrompt = nullptr;
+
+ // We want to use any auth prompt we can find on the channel's callbacks,
+ // and if that fails use the loadgroup's prompt (if any)
+ // Therefore, we can't just use NS_QueryNotificationCallbacks, because
+ // that would prefer a loadgroup's nsIAuthPrompt2 over a channel's
+ // nsIAuthPrompt.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+ if (*aAuthPrompt)
+ return;
+ }
+
+ nsCOMPtr<nsILoadGroup> group;
+ aChannel->GetLoadGroup(getter_AddRefs(group));
+ if (!group)
+ return;
+
+ group->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (!callbacks)
+ return;
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+}
+
+nsresult
+NS_NewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIEventTarget *target,
+ nsIInterfaceRequestor **result)
+{
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ if (loadGroup)
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ return NS_NewInterfaceRequestorAggregation(callbacks, cbs, target, result);
+}
+
+nsresult
+NS_NewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIInterfaceRequestor **result)
+{
+ return NS_NewNotificationCallbacksAggregation(callbacks, loadGroup, nullptr, result);
+}
+
+nsresult
+NS_DoImplGetInnermostURI(nsINestedURI *nestedURI, nsIURI **result)
+{
+ NS_PRECONDITION(nestedURI, "Must have a nested URI!");
+ NS_PRECONDITION(!*result, "Must have null *result");
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv = nestedURI->GetInnerURI(getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We may need to loop here until we reach the innermost
+ // URI.
+ nsCOMPtr<nsINestedURI> nestedInner(do_QueryInterface(inner));
+ while (nestedInner) {
+ rv = nestedInner->GetInnerURI(getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nestedInner = do_QueryInterface(inner);
+ }
+
+ // Found the innermost one if we reach here.
+ inner.swap(*result);
+
+ return rv;
+}
+
+nsresult
+NS_ImplGetInnermostURI(nsINestedURI *nestedURI, nsIURI **result)
+{
+ // Make it safe to use swap()
+ *result = nullptr;
+
+ return NS_DoImplGetInnermostURI(nestedURI, result);
+}
+
+nsresult
+NS_EnsureSafeToReturn(nsIURI *uri, nsIURI **result)
+{
+ NS_PRECONDITION(uri, "Must have a URI");
+
+ // Assume mutable until told otherwise
+ bool isMutable = true;
+ nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(uri));
+ if (mutableObj) {
+ nsresult rv = mutableObj->GetMutable(&isMutable);
+ isMutable = NS_FAILED(rv) || isMutable;
+ }
+
+ if (!isMutable) {
+ NS_ADDREF(*result = uri);
+ return NS_OK;
+ }
+
+ nsresult rv = uri->Clone(result);
+ if (NS_SUCCEEDED(rv) && !*result) {
+ NS_ERROR("nsIURI.clone contract was violated");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return rv;
+}
+
+void
+NS_TryToSetImmutable(nsIURI *uri)
+{
+ nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(uri));
+ if (mutableObj) {
+ mutableObj->SetMutable(false);
+ }
+}
+
+already_AddRefed<nsIURI>
+NS_TryToMakeImmutable(nsIURI *uri,
+ nsresult *outRv /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+
+ nsCOMPtr<nsIURI> result;
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(util, "do_GetNetUtil lied");
+ rv = util->ToImmutableURI(uri, getter_AddRefs(result));
+ }
+
+ if (NS_FAILED(rv)) {
+ result = uri;
+ }
+
+ if (outRv) {
+ *outRv = rv;
+ }
+
+ return result.forget();
+}
+
+already_AddRefed<nsIURI>
+NS_GetInnermostURI(nsIURI *aURI)
+{
+ NS_PRECONDITION(aURI, "Must have URI");
+
+ nsCOMPtr<nsIURI> uri = aURI;
+
+ nsCOMPtr<nsINestedURI> nestedURI(do_QueryInterface(uri));
+ if (!nestedURI) {
+ return uri.forget();
+ }
+
+ nsresult rv = nestedURI->GetInnermostURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+nsresult
+NS_GetFinalChannelURI(nsIChannel *channel, nsIURI **uri)
+{
+ *uri = nullptr;
+ nsLoadFlags loadFlags = 0;
+ nsresult rv = channel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (loadFlags & nsIChannel::LOAD_REPLACE) {
+ return channel->GetURI(uri);
+ }
+
+ return channel->GetOriginalURI(uri);
+}
+
+uint32_t
+NS_SecurityHashURI(nsIURI *aURI)
+{
+ nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
+
+ nsAutoCString scheme;
+ uint32_t schemeHash = 0;
+ if (NS_SUCCEEDED(baseURI->GetScheme(scheme)))
+ schemeHash = mozilla::HashString(scheme);
+
+ // TODO figure out how to hash file:// URIs
+ if (scheme.EqualsLiteral("file"))
+ return schemeHash; // sad face
+
+ bool hasFlag;
+ if (NS_FAILED(NS_URIChainHasFlags(baseURI,
+ nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) ||
+ hasFlag)
+ {
+ nsAutoCString spec;
+ uint32_t specHash;
+ nsresult res = baseURI->GetSpec(spec);
+ if (NS_SUCCEEDED(res))
+ specHash = mozilla::HashString(spec);
+ else
+ specHash = static_cast<uint32_t>(res);
+ return specHash;
+ }
+
+ nsAutoCString host;
+ uint32_t hostHash = 0;
+ if (NS_SUCCEEDED(baseURI->GetAsciiHost(host)))
+ hostHash = mozilla::HashString(host);
+
+ return mozilla::AddToHash(schemeHash, hostHash, NS_GetRealPort(baseURI));
+}
+
+bool
+NS_SecurityCompareURIs(nsIURI *aSourceURI,
+ nsIURI *aTargetURI,
+ bool aStrictFileOriginPolicy)
+{
+ // Note that this is not an Equals() test on purpose -- for URIs that don't
+ // support host/port, we want equality to basically be object identity, for
+ // security purposes. Otherwise, for example, two javascript: URIs that
+ // are otherwise unrelated could end up "same origin", which would be
+ // unfortunate.
+ if (aSourceURI && aSourceURI == aTargetURI)
+ {
+ return true;
+ }
+
+ if (!aTargetURI || !aSourceURI)
+ {
+ return false;
+ }
+
+ // If either URI is a nested URI, get the base URI
+ nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(aSourceURI);
+ nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI);
+
+ // If either uri is an nsIURIWithPrincipal
+ nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(sourceBaseURI);
+ if (uriPrinc) {
+ uriPrinc->GetPrincipalUri(getter_AddRefs(sourceBaseURI));
+ }
+
+ uriPrinc = do_QueryInterface(targetBaseURI);
+ if (uriPrinc) {
+ uriPrinc->GetPrincipalUri(getter_AddRefs(targetBaseURI));
+ }
+
+ if (!sourceBaseURI || !targetBaseURI)
+ return false;
+
+ // Compare schemes
+ nsAutoCString targetScheme;
+ bool sameScheme = false;
+ if (NS_FAILED( targetBaseURI->GetScheme(targetScheme) ) ||
+ NS_FAILED( sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme) ) ||
+ !sameScheme)
+ {
+ // Not same-origin if schemes differ
+ return false;
+ }
+
+ // For file scheme, reject unless the files are identical. See
+ // NS_RelaxStrictFileOriginPolicy for enforcing file same-origin checking
+ if (targetScheme.EqualsLiteral("file"))
+ {
+ // in traditional unsafe behavior all files are the same origin
+ if (!aStrictFileOriginPolicy)
+ return true;
+
+ nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(sourceBaseURI));
+ nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(targetBaseURI));
+
+ if (!sourceFileURL || !targetFileURL)
+ return false;
+
+ nsCOMPtr<nsIFile> sourceFile, targetFile;
+
+ sourceFileURL->GetFile(getter_AddRefs(sourceFile));
+ targetFileURL->GetFile(getter_AddRefs(targetFile));
+
+ if (!sourceFile || !targetFile)
+ return false;
+
+ // Otherwise they had better match
+ bool filesAreEqual = false;
+ nsresult rv = sourceFile->Equals(targetFile, &filesAreEqual);
+ return NS_SUCCEEDED(rv) && filesAreEqual;
+ }
+
+ bool hasFlag;
+ if (NS_FAILED(NS_URIChainHasFlags(targetBaseURI,
+ nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) ||
+ hasFlag)
+ {
+ // URIs with this flag have the whole spec as a distinct trust
+ // domain; use the whole spec for comparison
+ nsAutoCString targetSpec;
+ nsAutoCString sourceSpec;
+ return ( NS_SUCCEEDED( targetBaseURI->GetSpec(targetSpec) ) &&
+ NS_SUCCEEDED( sourceBaseURI->GetSpec(sourceSpec) ) &&
+ targetSpec.Equals(sourceSpec) );
+ }
+
+ // Compare hosts
+ nsAutoCString targetHost;
+ nsAutoCString sourceHost;
+ if (NS_FAILED( targetBaseURI->GetAsciiHost(targetHost) ) ||
+ NS_FAILED( sourceBaseURI->GetAsciiHost(sourceHost) ))
+ {
+ return false;
+ }
+
+ nsCOMPtr<nsIStandardURL> targetURL(do_QueryInterface(targetBaseURI));
+ nsCOMPtr<nsIStandardURL> sourceURL(do_QueryInterface(sourceBaseURI));
+ if (!targetURL || !sourceURL)
+ {
+ return false;
+ }
+
+#ifdef MOZILLA_INTERNAL_API
+ if (!targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator() ))
+#else
+ if (!targetHost.Equals(sourceHost, CaseInsensitiveCompare))
+#endif
+ {
+ return false;
+ }
+
+ return NS_GetRealPort(targetBaseURI) == NS_GetRealPort(sourceBaseURI);
+}
+
+bool
+NS_URIIsLocalFile(nsIURI *aURI)
+{
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil();
+
+ bool isFile;
+ return util && NS_SUCCEEDED(util->ProtocolHasFlags(aURI,
+ nsIProtocolHandler::URI_IS_LOCAL_FILE,
+ &isFile)) &&
+ isFile;
+}
+
+bool
+NS_RelaxStrictFileOriginPolicy(nsIURI *aTargetURI,
+ nsIURI *aSourceURI,
+ bool aAllowDirectoryTarget /* = false */)
+{
+ if (!NS_URIIsLocalFile(aTargetURI)) {
+ // This is probably not what the caller intended
+ NS_NOTREACHED("NS_RelaxStrictFileOriginPolicy called with non-file URI");
+ return false;
+ }
+
+ if (!NS_URIIsLocalFile(aSourceURI)) {
+ // If the source is not also a file: uri then forget it
+ // (don't want resource: principals in a file: doc)
+ //
+ // note: we're not de-nesting jar: uris here, we want to
+ // keep archive content bottled up in its own little island
+ return false;
+ }
+
+ //
+ // pull out the internal files
+ //
+ nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(aTargetURI));
+ nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(aSourceURI));
+ nsCOMPtr<nsIFile> targetFile;
+ nsCOMPtr<nsIFile> sourceFile;
+ bool targetIsDir;
+
+ // Make sure targetFile is not a directory (bug 209234)
+ // and that it exists w/out unescaping (bug 395343)
+ if (!sourceFileURL || !targetFileURL ||
+ NS_FAILED(targetFileURL->GetFile(getter_AddRefs(targetFile))) ||
+ NS_FAILED(sourceFileURL->GetFile(getter_AddRefs(sourceFile))) ||
+ !targetFile || !sourceFile ||
+ NS_FAILED(targetFile->Normalize()) ||
+#ifndef MOZ_WIDGET_ANDROID
+ NS_FAILED(sourceFile->Normalize()) ||
+#endif
+ (!aAllowDirectoryTarget &&
+ (NS_FAILED(targetFile->IsDirectory(&targetIsDir)) || targetIsDir))) {
+ return false;
+ }
+
+ //
+ // If the file to be loaded is in a subdirectory of the source
+ // (or same-dir if source is not a directory) then it will
+ // inherit its source principal and be scriptable by that source.
+ //
+ bool sourceIsDir;
+ bool allowed = false;
+ nsresult rv = sourceFile->IsDirectory(&sourceIsDir);
+ if (NS_SUCCEEDED(rv) && sourceIsDir) {
+ rv = sourceFile->Contains(targetFile, &allowed);
+ } else {
+ nsCOMPtr<nsIFile> sourceParent;
+ rv = sourceFile->GetParent(getter_AddRefs(sourceParent));
+ if (NS_SUCCEEDED(rv) && sourceParent) {
+ rv = sourceParent->Equals(targetFile, &allowed);
+ if (NS_FAILED(rv) || !allowed) {
+ rv = sourceParent->Contains(targetFile, &allowed);
+ } else {
+ MOZ_ASSERT(aAllowDirectoryTarget,
+ "sourceFile->Parent == targetFile, but targetFile "
+ "should've been disallowed if it is a directory");
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && allowed) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+NS_IsInternalSameURIRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags)
+{
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(oldURI->Equals(newURI, &res)) && res;
+}
+
+bool
+NS_IsHSTSUpgradeRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags)
+{
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ bool isHttp;
+ if (NS_FAILED(oldURI->SchemeIs("http", &isHttp)) || !isHttp) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ nsresult rv = NS_GetSecureUpgradedURI(oldURI, getter_AddRefs(upgradedURI));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(upgradedURI->Equals(newURI, &res)) && res;
+}
+
+nsresult
+NS_LinkRedirectChannels(uint32_t channelId,
+ nsIParentChannel *parentChannel,
+ nsIChannel **_result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return registrar->LinkChannels(channelId,
+ parentChannel,
+ _result);
+}
+
+#define NS_FAKE_SCHEME "http://"
+#define NS_FAKE_TLD ".invalid"
+nsresult NS_MakeRandomInvalidURLString(nsCString &result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsID idee;
+ rv = uuidgen->GenerateUUIDInPlace(&idee);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char chars[NSID_LENGTH];
+ idee.ToProvidedString(chars);
+
+ result.AssignLiteral(NS_FAKE_SCHEME);
+ // Strip off the '{' and '}' at the beginning and end of the UUID
+ result.Append(chars + 1, NSID_LENGTH - 3);
+ result.AppendLiteral(NS_FAKE_TLD);
+
+ return NS_OK;
+}
+#undef NS_FAKE_SCHEME
+#undef NS_FAKE_TLD
+
+nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel,
+ nsIInputStream **aStream)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetSecurityMode() != 0) {
+ return aChannel->Open2(aStream);
+ }
+ return aChannel->Open(aStream);
+}
+
+nsresult NS_MaybeOpenChannelUsingAsyncOpen2(nsIChannel* aChannel,
+ nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetSecurityMode() != 0) {
+ return aChannel->AsyncOpen2(aListener);
+ }
+ return aChannel->AsyncOpen(aListener, nullptr);
+}
+
+nsresult
+NS_CheckIsJavaCompatibleURLString(nsCString &urlString, bool *result)
+{
+ *result = false; // Default to "no"
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURLParser> urlParser =
+ do_GetService(NS_STDURLPARSER_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !urlParser)
+ return NS_ERROR_FAILURE;
+
+ bool compatible = true;
+ uint32_t schemePos = 0;
+ int32_t schemeLen = 0;
+ urlParser->ParseURL(urlString.get(), -1, &schemePos, &schemeLen,
+ nullptr, nullptr, nullptr, nullptr);
+ if (schemeLen != -1) {
+ nsCString scheme;
+ scheme.Assign(urlString.get() + schemePos, schemeLen);
+ // By default Java only understands a small number of URL schemes, and of
+ // these only some can legitimately represent a browser page's "origin"
+ // (and be something we can legitimately expect Java to handle ... or not
+ // to mishandle).
+ //
+ // Besides those listed below, the OJI plugin understands the "jar",
+ // "mailto", "netdoc", "javascript" and "rmi" schemes, and Java Plugin2
+ // also understands the "about" scheme. We actually pass "about" URLs
+ // to Java ("about:blank" when processing a javascript: URL (one that
+ // calls Java) from the location bar of a blank page, and (in FF4 and up)
+ // "about:home" when processing a javascript: URL from the home page).
+ // And Java doesn't appear to mishandle them (for example it doesn't allow
+ // connections to "about" URLs). But it doesn't make any sense to do
+ // same-origin checks on "about" URLs, so we don't include them in our
+ // scheme whitelist.
+ //
+ // The OJI plugin doesn't understand "chrome" URLs (only Java Plugin2
+ // does) -- so we mustn't pass them to the OJI plugin. But we do need to
+ // pass "chrome" URLs to Java Plugin2: Java Plugin2 grants additional
+ // privileges to chrome "origins", and some extensions take advantage of
+ // this. For more information see bug 620773.
+ //
+ // As of FF4, we no longer support the OJI plugin.
+ if (PL_strcasecmp(scheme.get(), "http") &&
+ PL_strcasecmp(scheme.get(), "https") &&
+ PL_strcasecmp(scheme.get(), "file") &&
+ PL_strcasecmp(scheme.get(), "ftp") &&
+ PL_strcasecmp(scheme.get(), "gopher") &&
+ PL_strcasecmp(scheme.get(), "chrome"))
+ compatible = false;
+ } else {
+ compatible = false;
+ }
+
+ *result = compatible;
+
+ return NS_OK;
+}
+
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+uint32_t
+NS_GetContentDispositionFromToken(const nsAString &aDispToken)
+{
+ // RFC 2183, section 2.8 says that an unknown disposition
+ // value should be treated as "attachment"
+ // If all of these tests eval to false, then we have a content-disposition of
+ // "attachment" or unknown
+ if (aDispToken.IsEmpty() ||
+ aDispToken.LowerCaseEqualsLiteral("inline") ||
+ // Broken sites just send
+ // Content-Disposition: filename="file"
+ // without a disposition token... screen those out.
+ StringHead(aDispToken, 8).LowerCaseEqualsLiteral("filename"))
+ return nsIChannel::DISPOSITION_INLINE;
+
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+}
+
+uint32_t
+NS_GetContentDispositionFromHeader(const nsACString &aHeader,
+ nsIChannel *aChan /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+
+ nsAutoCString fallbackCharset;
+ if (aChan) {
+ nsCOMPtr<nsIURI> uri;
+ aChan->GetURI(getter_AddRefs(uri));
+ if (uri)
+ uri->GetOriginCharset(fallbackCharset);
+ }
+
+ nsAutoString dispToken;
+ rv = mimehdrpar->GetParameterHTTP(aHeader, "", fallbackCharset, true, nullptr,
+ dispToken);
+
+ if (NS_FAILED(rv)) {
+ // special case (see bug 272541): empty disposition type handled as "inline"
+ if (rv == NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY)
+ return nsIChannel::DISPOSITION_INLINE;
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+ }
+
+ return NS_GetContentDispositionFromToken(dispToken);
+}
+
+nsresult
+NS_GetFilenameFromDisposition(nsAString &aFilename,
+ const nsACString &aDisposition,
+ nsIURI *aURI /* = nullptr */)
+{
+ aFilename.Truncate();
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+
+ nsAutoCString fallbackCharset;
+ if (url)
+ url->GetOriginCharset(fallbackCharset);
+ // Get the value of 'filename' parameter
+ rv = mimehdrpar->GetParameterHTTP(aDisposition, "filename",
+ fallbackCharset, true, nullptr,
+ aFilename);
+
+ if (NS_FAILED(rv)) {
+ aFilename.Truncate();
+ return rv;
+ }
+
+ if (aFilename.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+void net_EnsurePSMInit()
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> psm = do_GetService(PSM_COMPONENT_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+bool NS_IsAboutBlank(nsIURI *uri)
+{
+ // GetSpec can be expensive for some URIs, so check the scheme first.
+ bool isAbout = false;
+ if (NS_FAILED(uri->SchemeIs("about", &isAbout)) || !isAbout) {
+ return false;
+ }
+
+ return uri->GetSpecOrDefault().EqualsLiteral("about:blank");
+}
+
+nsresult
+NS_GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString &hostLine)
+{
+ if (strchr(host.get(), ':')) {
+ // host is an IPv6 address literal and must be encapsulated in []'s
+ hostLine.Assign('[');
+ // scope id is not needed for Host header.
+ int scopeIdPos = host.FindChar('%');
+ if (scopeIdPos == -1)
+ hostLine.Append(host);
+ else if (scopeIdPos > 0)
+ hostLine.Append(Substring(host, 0, scopeIdPos));
+ else
+ return NS_ERROR_MALFORMED_URI;
+ hostLine.Append(']');
+ }
+ else
+ hostLine.Assign(host);
+ if (port != -1) {
+ hostLine.Append(':');
+ hostLine.AppendInt(port);
+ }
+ return NS_OK;
+}
+
+void
+NS_SniffContent(const char *aSnifferType, nsIRequest *aRequest,
+ const uint8_t *aData, uint32_t aLength,
+ nsACString &aSniffedType)
+{
+ typedef nsCategoryCache<nsIContentSniffer> ContentSnifferCache;
+ extern ContentSnifferCache* gNetSniffers;
+ extern ContentSnifferCache* gDataSniffers;
+ ContentSnifferCache* cache = nullptr;
+ if (!strcmp(aSnifferType, NS_CONTENT_SNIFFER_CATEGORY)) {
+ if (!gNetSniffers) {
+ gNetSniffers = new ContentSnifferCache(NS_CONTENT_SNIFFER_CATEGORY);
+ }
+ cache = gNetSniffers;
+ } else if (!strcmp(aSnifferType, NS_DATA_SNIFFER_CATEGORY)) {
+ if (!gDataSniffers) {
+ gDataSniffers = new ContentSnifferCache(NS_DATA_SNIFFER_CATEGORY);
+ }
+ cache = gDataSniffers;
+ } else {
+ // Invalid content sniffer type was requested
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ nsCOMArray<nsIContentSniffer> sniffers;
+ cache->GetEntries(sniffers);
+ for (int32_t i = 0; i < sniffers.Count(); ++i) {
+ nsresult rv = sniffers[i]->GetMIMETypeFromContent(aRequest, aData, aLength, aSniffedType);
+ if (NS_SUCCEEDED(rv) && !aSniffedType.IsEmpty()) {
+ return;
+ }
+ }
+
+ aSniffedType.Truncate();
+}
+
+bool
+NS_IsSrcdocChannel(nsIChannel *aChannel)
+{
+ bool isSrcdoc;
+ nsCOMPtr<nsIInputStreamChannel> isr = do_QueryInterface(aChannel);
+ if (isr) {
+ isr->GetIsSrcdocChannel(&isSrcdoc);
+ return isSrcdoc;
+ }
+ nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(aChannel);
+ if (vsc) {
+ vsc->GetIsSrcdocChannel(&isSrcdoc);
+ return isSrcdoc;
+ }
+ return false;
+}
+
+nsresult
+NS_ShouldSecureUpgrade(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIPrincipal* aChannelResultPrincipal,
+ bool aPrivateBrowsing,
+ bool aAllowSTS,
+ bool& aShouldUpgrade)
+{
+ // Even if we're in private browsing mode, we still enforce existing STS
+ // data (it is read-only).
+ // if the connection is not using SSL and either the exact host matches or
+ // a superdomain wants to force HTTPS, do it.
+ bool isHttps = false;
+ nsresult rv = aURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isHttps) {
+ // If any of the documents up the chain to the root doucment makes use of
+ // the CSP directive 'upgrade-insecure-requests', then it's time to fulfill
+ // the promise to CSP and mixed content blocking to upgrade the channel
+ // from http to https.
+ if (aLoadInfo) {
+ // Please note that cross origin top level navigations are not subject
+ // to upgrade-insecure-requests, see:
+ // http://www.w3.org/TR/upgrade-insecure-requests/#examples
+ // Compare the principal we are navigating to (aChannelResultPrincipal)
+ // with the referring/triggering Principal.
+ bool crossOriginNavigation =
+ (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) &&
+ (!aChannelResultPrincipal->Equals(aLoadInfo->TriggeringPrincipal()));
+
+ if (aLoadInfo->GetUpgradeInsecureRequests() && !crossOriginNavigation) {
+ // let's log a message to the console that we are upgrading a request
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ // append the additional 's' for security to the scheme :-)
+ scheme.AppendASCII("s");
+ NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 reportScheme(scheme);
+
+ const char16_t* params[] = { reportSpec.get(), reportScheme.get() };
+ uint32_t innerWindowId = aLoadInfo->GetInnerWindowID();
+ CSP_LogLocalizedStr(u"upgradeInsecureRequest",
+ params, ArrayLength(params),
+ EmptyString(), // aSourceFile
+ EmptyString(), // aScriptSample
+ 0, // aLineNumber
+ 0, // aColumnNumber
+ nsIScriptError::warningFlag, "CSP",
+ innerWindowId);
+
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 4);
+ aShouldUpgrade = true;
+ return NS_OK;
+ }
+ }
+
+ // enforce Strict-Transport-Security
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+
+ bool isStsHost = false;
+ uint32_t flags = aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
+ nullptr, &isStsHost);
+
+ // if the SSS check fails, it's likely because this load is on a
+ // malformed URI or something else in the setup is wrong, so any error
+ // should be reported.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isStsHost) {
+ LOG(("nsHttpChannel::Connect() STS permissions found\n"));
+ if (aAllowSTS) {
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3);
+ aShouldUpgrade = true;
+ return NS_OK;
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0);
+ }
+ aShouldUpgrade = false;
+ return NS_OK;
+}
+
+nsresult
+NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI)
+{
+ nsCOMPtr<nsIURI> upgradedURI;
+
+ nsresult rv = aURI->Clone(getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Change the scheme to HTTPS:
+ upgradedURI->SetScheme(NS_LITERAL_CSTRING("https"));
+
+ // Change the default port to 443:
+ nsCOMPtr<nsIStandardURL> upgradedStandardURL = do_QueryInterface(upgradedURI);
+ if (upgradedStandardURL) {
+ upgradedStandardURL->SetDefaultPort(443);
+ } else {
+ // If we don't have a nsStandardURL, fall back to using GetPort/SetPort.
+ // XXXdholbert Is this function even called with a non-nsStandardURL arg,
+ // in practice?
+ int32_t oldPort = -1;
+ rv = aURI->GetPort(&oldPort);
+ if (NS_FAILED(rv)) return rv;
+
+ // Keep any nonstandard ports so only the scheme is changed.
+ // For example:
+ // http://foo.com:80 -> https://foo.com:443
+ // http://foo.com:81 -> https://foo.com:81
+
+ if (oldPort == 80 || oldPort == -1) {
+ upgradedURI->SetPort(-1);
+ } else {
+ upgradedURI->SetPort(oldPort);
+ }
+ }
+
+ upgradedURI.forget(aUpgradedURI);
+ return NS_OK;
+}
+
+nsresult
+NS_CompareLoadInfoAndLoadContext(nsIChannel *aChannel)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ if (!loadInfo || !loadContext) {
+ return NS_OK;
+ }
+
+ // We try to skip about:newtab and about:sync-tabs.
+ // about:newtab will use SystemPrincipal to download thumbnails through
+ // https:// and blob URLs.
+ // about:sync-tabs will fetch icons through moz-icon://.
+ bool isAboutPage = false;
+ nsINode* node = loadInfo->LoadingNode();
+ if (node) {
+ nsIDocument* doc = node->OwnerDoc();
+ if (doc) {
+ nsIURI* uri = doc->GetDocumentURI();
+ nsresult rv = uri->SchemeIs("about", &isAboutPage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (isAboutPage) {
+ return NS_OK;
+ }
+
+ // We skip the favicon loading here. The favicon loading might be
+ // triggered by the XUL image. For that case, the loadContext will have
+ // default originAttributes since the XUL image uses SystemPrincipal, but
+ // the loadInfo will use originAttributes from the content. Thus, the
+ // originAttributes between loadInfo and loadContext will be different.
+ // That's why we have to skip the comparison for the favicon loading.
+ if (nsContentUtils::IsSystemPrincipal(loadInfo->LoadingPrincipal()) &&
+ loadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return NS_OK;
+ }
+
+ uint32_t loadContextAppId = 0;
+ nsresult rv = loadContext->GetAppId(&loadContextAppId);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool loadContextIsInBE = false;
+ rv = loadContext->GetIsInIsolatedMozBrowserElement(&loadContextIsInBE);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ OriginAttributes originAttrsLoadInfo = loadInfo->GetOriginAttributes();
+ DocShellOriginAttributes originAttrsLoadContext;
+ loadContext->GetOriginAttributes(originAttrsLoadContext);
+
+ LOG(("NS_CompareLoadInfoAndLoadContext - loadInfo: %d, %d, %d, %d; "
+ "loadContext: %d %d, %d, %d. [channel=%p]",
+ originAttrsLoadInfo.mAppId, originAttrsLoadInfo.mInIsolatedMozBrowser,
+ originAttrsLoadInfo.mUserContextId, originAttrsLoadInfo.mPrivateBrowsingId,
+ loadContextAppId, loadContextIsInBE,
+ originAttrsLoadContext.mUserContextId, originAttrsLoadContext.mPrivateBrowsingId,
+ aChannel));
+
+ MOZ_ASSERT(originAttrsLoadInfo.mAppId == loadContextAppId,
+ "AppId in the loadContext and in the loadInfo are not the "
+ "same!");
+
+ MOZ_ASSERT(originAttrsLoadInfo.mInIsolatedMozBrowser ==
+ loadContextIsInBE,
+ "The value of InIsolatedMozBrowser in the loadContext and in "
+ "the loadInfo are not the same!");
+
+ MOZ_ASSERT(originAttrsLoadInfo.mUserContextId ==
+ originAttrsLoadContext.mUserContextId,
+ "The value of mUserContextId in the loadContext and in the "
+ "loadInfo are not the same!");
+
+ MOZ_ASSERT(originAttrsLoadInfo.mPrivateBrowsingId ==
+ originAttrsLoadContext.mPrivateBrowsingId,
+ "The value of mPrivateBrowsingId in the loadContext and in the "
+ "loadInfo are not the same!");
+
+ return NS_OK;
+}
+
+namespace mozilla {
+namespace net {
+
+bool
+InScriptableRange(int64_t val)
+{
+ return (val <= kJS_MAX_SAFE_INTEGER) && (val >= kJS_MIN_SAFE_INTEGER);
+}
+
+bool
+InScriptableRange(uint64_t val)
+{
+ return val <= kJS_MAX_SAFE_UINTEGER;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h
new file mode 100644
index 0000000000..bd89ca8aeb
--- /dev/null
+++ b/netwerk/base/nsNetUtil.h
@@ -0,0 +1,1000 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetUtil_h__
+#define nsNetUtil_h__
+
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadGroup.h"
+#include "nsINetUtil.h"
+#include "nsIRequest.h"
+#include "nsILoadInfo.h"
+#include "nsIIOService.h"
+#include "mozilla/Services.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+class nsIURI;
+class nsIPrincipal;
+class nsIAsyncStreamCopier;
+class nsIAuthPrompt;
+class nsIAuthPrompt2;
+class nsIChannel;
+class nsIChannelPolicy;
+class nsIDownloadObserver;
+class nsIEventTarget;
+class nsIFileProtocolHandler;
+class nsIFileStream;
+class nsIInputStream;
+class nsIInputStreamPump;
+class nsIInterfaceRequestor;
+class nsINestedURI;
+class nsINetworkInterface;
+class nsIOutputStream;
+class nsIParentChannel;
+class nsIPersistentProperties;
+class nsIProxyInfo;
+class nsIRequestObserver;
+class nsIStreamListener;
+class nsIStreamLoader;
+class nsIStreamLoaderObserver;
+class nsIIncrementalStreamLoader;
+class nsIIncrementalStreamLoaderObserver;
+class nsIUnicharStreamLoader;
+class nsIUnicharStreamLoaderObserver;
+
+namespace mozilla { class NeckoOriginAttributes; }
+
+template <class> class nsCOMPtr;
+template <typename> struct already_AddRefed;
+
+#ifdef MOZILLA_INTERNAL_API
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#else
+#include "nsStringAPI.h"
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+already_AddRefed<nsIIOService> do_GetIOService(nsresult *error = 0);
+
+already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult *error = 0);
+
+#else
+// Helper, to simplify getting the I/O service.
+const nsGetServiceByContractIDWithError do_GetIOService(nsresult *error = 0);
+
+// An alias to do_GetIOService
+const nsGetServiceByContractIDWithError do_GetNetUtil(nsresult *error = 0);
+
+#endif
+
+// private little helper function... don't call this directly!
+nsresult net_EnsureIOService(nsIIOService **ios, nsCOMPtr<nsIIOService> &grip);
+
+nsresult NS_NewURI(nsIURI **result,
+ const nsACString &spec,
+ const char *charset = nullptr,
+ nsIURI *baseURI = nullptr,
+ nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers
+
+nsresult NS_NewURI(nsIURI **result,
+ const nsAString &spec,
+ const char *charset = nullptr,
+ nsIURI *baseURI = nullptr,
+ nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers
+
+nsresult NS_NewURI(nsIURI **result,
+ const char *spec,
+ nsIURI *baseURI = nullptr,
+ nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers
+
+nsresult NS_NewFileURI(nsIURI **result,
+ nsIFile *spec,
+ nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers
+
+/*
+* How to create a new Channel, using NS_NewChannel,
+* NS_NewChannelWithTriggeringPrincipal,
+* NS_NewInputStreamChannel, NS_NewChannelInternal
+* and it's variations:
+*
+* What specific API function to use:
+* * The NS_NewChannelInternal functions should almost never be directly
+* called outside of necko code.
+* * If possible, use NS_NewChannel() providing a loading *nsINode*
+* * If no loading *nsINode* is avaialable, call NS_NewChannel() providing
+* a loading *nsIPrincipal*.
+* * Call NS_NewChannelWithTriggeringPrincipal if the triggeringPrincipal
+* is different from the loadingPrincipal.
+* * Call NS_NewChannelInternal() providing aLoadInfo object in cases where
+* you already have loadInfo object, e.g in case of a channel redirect.
+*
+* @param aURI
+* nsIURI from which to make a channel
+* @param aLoadingNode
+* @param aLoadingPrincipal
+* @param aTriggeringPrincipal
+* @param aSecurityFlags
+* @param aContentPolicyType
+* These will be used as values for the nsILoadInfo object on the
+* created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+*
+* Please note, if you provide both a loadingNode and a loadingPrincipal,
+* then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+* But less error prone is to just supply a loadingNode.
+*
+* Keep in mind that URIs coming from a webpage should *never* use the
+* systemPrincipal as the loadingPrincipal.
+*/
+nsresult NS_NewChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsILoadInfo *aLoadInfo,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /*NS_NewChannelWithPrincipalAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /* NS_NewChannelNode */
+NS_NewChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /* NS_NewChannelPrincipal */
+NS_NewChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+nsresult NS_MakeAbsoluteURI(nsACString &result,
+ const nsACString &spec,
+ nsIURI *baseURI);
+
+nsresult NS_MakeAbsoluteURI(char **result,
+ const char *spec,
+ nsIURI *baseURI);
+
+nsresult NS_MakeAbsoluteURI(nsAString &result,
+ const nsAString &spec,
+ nsIURI *baseURI);
+
+/**
+ * This function is a helper function to get a scheme's default port.
+ */
+int32_t NS_GetDefaultPort(const char *scheme,
+ nsIIOService *ioService = nullptr);
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool NS_StringToACE(const nsACString &idn, nsACString &result);
+
+/**
+ * This function is a helper function to get a protocol's default port if the
+ * URI does not specify a port explicitly. Returns -1 if this protocol has no
+ * concept of ports or if there was an error getting the port.
+ */
+int32_t NS_GetRealPort(nsIURI *aURI);
+
+nsresult /* NS_NewInputStreamChannelWithLoadInfo */
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ const nsACString &aContentType,
+ const nsACString &aContentCharset,
+ nsILoadInfo *aLoadInfo);
+
+nsresult NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ const nsACString &aContentType,
+ const nsACString &aContentCharset,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType);
+
+
+nsresult /* NS_NewInputStreamChannelPrincipal */
+NS_NewInputStreamChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const nsACString &aContentType = EmptyCString(),
+ const nsACString &aContentCharset = EmptyCString());
+
+nsresult NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel = false);
+
+nsresult
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsILoadInfo *aLoadInfo,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamPump(nsIInputStreamPump **result,
+ nsIInputStream *stream,
+ int64_t streamPos = int64_t(-1),
+ int64_t streamLen = int64_t(-1),
+ uint32_t segsize = 0,
+ uint32_t segcount = 0,
+ bool closeWhenDone = false);
+
+// NOTE: you will need to specify whether or not your streams are buffered
+// (i.e., do they implement ReadSegments/WriteSegments). the default
+// assumption of TRUE for both streams might not be right for you!
+nsresult NS_NewAsyncStreamCopier(nsIAsyncStreamCopier **result,
+ nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ bool sourceBuffered = true,
+ bool sinkBuffered = true,
+ uint32_t chunkSize = 0,
+ bool closeSource = true,
+ bool closeSink = true);
+
+nsresult NS_NewLoadGroup(nsILoadGroup **result,
+ nsIRequestObserver *obs);
+
+// Create a new nsILoadGroup that will match the given principal.
+nsresult
+NS_NewLoadGroup(nsILoadGroup **aResult, nsIPrincipal* aPrincipal);
+
+// Determine if the given loadGroup/principal pair will produce a principal
+// with similar permissions when passed to NS_NewChannel(). This checks for
+// things like making sure the appId and browser element flags match. Without
+// an appropriate load group these values can be lost when getting the result
+// principal back out of the channel. Null principals are also always allowed
+// as they do not have permissions to actually use the load group.
+bool
+NS_LoadGroupMatchesPrincipal(nsILoadGroup *aLoadGroup,
+ nsIPrincipal *aPrincipal);
+
+nsresult NS_NewDownloader(nsIStreamListener **result,
+ nsIDownloadObserver *observer,
+ nsIFile *downloadLocation = nullptr);
+
+nsresult NS_NewStreamLoader(nsIStreamLoader **result,
+ nsIStreamLoaderObserver *observer,
+ nsIRequestObserver *requestObserver = nullptr);
+
+nsresult NS_NewIncrementalStreamLoader(nsIIncrementalStreamLoader **result,
+ nsIIncrementalStreamLoaderObserver *observer);
+
+nsresult NS_NewStreamLoaderInternal(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIURI *aReferrer = nullptr);
+
+nsresult /* NS_NewStreamLoaderNode */
+NS_NewStreamLoader(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsINode *aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIURI *aReferrer = nullptr);
+
+nsresult /* NS_NewStreamLoaderPrincipal */
+NS_NewStreamLoader(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIURI *aReferrer = nullptr);
+
+nsresult NS_NewUnicharStreamLoader(nsIUnicharStreamLoader **result,
+ nsIUnicharStreamLoaderObserver *observer);
+
+nsresult NS_NewSyncStreamListener(nsIStreamListener **result,
+ nsIInputStream **stream);
+
+/**
+ * Implement the nsIChannel::Open(nsIInputStream**) method using the channel's
+ * AsyncOpen method.
+ *
+ * NOTE: Reading from the returned nsIInputStream may spin the current
+ * thread's event queue, which could result in any event being processed.
+ */
+nsresult NS_ImplementChannelOpen(nsIChannel *channel,
+ nsIInputStream **result);
+
+nsresult NS_NewRequestObserverProxy(nsIRequestObserver **result,
+ nsIRequestObserver *observer,
+ nsISupports *context);
+
+nsresult NS_NewSimpleStreamListener(nsIStreamListener **result,
+ nsIOutputStream *sink,
+ nsIRequestObserver *observer = nullptr);
+
+nsresult NS_CheckPortSafety(int32_t port,
+ const char *scheme,
+ nsIIOService *ioService = nullptr);
+
+// Determine if this URI is using a safe port.
+nsresult NS_CheckPortSafety(nsIURI *uri);
+
+nsresult NS_NewProxyInfo(const nsACString &type,
+ const nsACString &host,
+ int32_t port,
+ uint32_t flags,
+ nsIProxyInfo **result);
+
+nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler **result,
+ nsIIOService *ioService = nullptr);
+
+nsresult NS_GetFileFromURLSpec(const nsACString &inURL,
+ nsIFile **result,
+ nsIIOService *ioService = nullptr);
+
+nsresult NS_GetURLSpecFromFile(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService = nullptr);
+
+/**
+ * Converts the nsIFile to the corresponding URL string.
+ * Should only be called on files which are not directories,
+ * is otherwise identical to NS_GetURLSpecFromFile, but is
+ * usually more efficient.
+ * Warning: this restriction may not be enforced at runtime!
+ */
+nsresult NS_GetURLSpecFromActualFile(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService = nullptr);
+
+/**
+ * Converts the nsIFile to the corresponding URL string.
+ * Should only be called on files which are directories,
+ * is otherwise identical to NS_GetURLSpecFromFile, but is
+ * usually more efficient.
+ * Warning: this restriction may not be enforced at runtime!
+ */
+nsresult NS_GetURLSpecFromDir(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService = nullptr);
+
+/**
+ * Obtains the referrer for a given channel. This first tries to obtain the
+ * referrer from the property docshell.internalReferrer, and if that doesn't
+ * work and the channel is an nsIHTTPChannel, we check it's referrer property.
+ *
+ * @returns NS_ERROR_NOT_AVAILABLE if no referrer is available.
+ */
+nsresult NS_GetReferrerFromChannel(nsIChannel *channel,
+ nsIURI **referrer);
+
+nsresult NS_ParseRequestContentType(const nsACString &rawContentType,
+ nsCString &contentType,
+ nsCString &contentCharset);
+
+nsresult NS_ParseResponseContentType(const nsACString &rawContentType,
+ nsCString &contentType,
+ nsCString &contentCharset);
+
+nsresult NS_ExtractCharsetFromContentType(const nsACString &rawContentType,
+ nsCString &contentCharset,
+ bool *hadCharset,
+ int32_t *charsetStart,
+ int32_t *charsetEnd);
+
+nsresult NS_NewLocalFileInputStream(nsIInputStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewPartialLocalFileInputStream(nsIInputStream **result,
+ nsIFile *file,
+ uint64_t offset,
+ uint64_t length,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+// returns a file output stream which can be QI'ed to nsISafeOutputStream.
+nsresult NS_NewAtomicFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+// returns a file output stream which can be QI'ed to nsISafeOutputStream.
+nsresult NS_NewSafeLocalFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileStream(nsIFileStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+// returns the input end of a pipe. the output end of the pipe
+// is attached to the original stream. data from the original
+// stream is read into the pipe on a background thread.
+nsresult NS_BackgroundInputStream(nsIInputStream **result,
+ nsIInputStream *stream,
+ uint32_t segmentSize = 0,
+ uint32_t segmentCount = 0);
+
+// returns the output end of a pipe. the input end of the pipe
+// is attached to the original stream. data written to the pipe
+// is copied to the original stream on a background thread.
+nsresult NS_BackgroundOutputStream(nsIOutputStream **result,
+ nsIOutputStream *stream,
+ uint32_t segmentSize = 0,
+ uint32_t segmentCount = 0);
+
+MOZ_MUST_USE nsresult
+NS_NewBufferedInputStream(nsIInputStream **result,
+ nsIInputStream *str,
+ uint32_t bufferSize);
+
+// note: the resulting stream can be QI'ed to nsISafeOutputStream iff the
+// provided stream supports it.
+nsresult NS_NewBufferedOutputStream(nsIOutputStream **result,
+ nsIOutputStream *str,
+ uint32_t bufferSize);
+
+/**
+ * Attempts to buffer a given stream. If this fails, it returns the
+ * passed-in stream.
+ *
+ * @param aOutputStream
+ * The output stream we want to buffer. This cannot be null.
+ * @param aBufferSize
+ * The size of the buffer for the buffered output stream.
+ * @returns an nsIOutputStream that is buffered with the specified buffer size,
+ * or is aOutputStream if creating the new buffered stream failed.
+ */
+already_AddRefed<nsIOutputStream>
+NS_BufferOutputStream(nsIOutputStream *aOutputStream,
+ uint32_t aBufferSize);
+already_AddRefed<nsIInputStream>
+NS_BufferInputStream(nsIInputStream *aInputStream,
+ uint32_t aBufferSize);
+
+// returns an input stream compatible with nsIUploadChannel::SetUploadStream()
+nsresult NS_NewPostDataStream(nsIInputStream **result,
+ bool isFile,
+ const nsACString &data);
+
+nsresult NS_ReadInputStreamToBuffer(nsIInputStream *aInputStream,
+ void **aDest,
+ uint32_t aCount);
+
+// external code can't see fallible_t
+#ifdef MOZILLA_INTERNAL_API
+
+nsresult NS_ReadInputStreamToString(nsIInputStream *aInputStream,
+ nsACString &aDest,
+ uint32_t aCount);
+
+#endif
+
+nsresult
+NS_LoadPersistentPropertiesFromURISpec(nsIPersistentProperties **outResult,
+ const nsACString &aSpec);
+
+/**
+ * NS_QueryNotificationCallbacks implements the canonical algorithm for
+ * querying interfaces from a channel's notification callbacks. It first
+ * searches the channel's notificationCallbacks attribute, and if the interface
+ * is not found there, then it inspects the notificationCallbacks attribute of
+ * the channel's loadGroup.
+ *
+ * Note: templatized only because nsIWebSocketChannel is currently not an
+ * nsIChannel.
+ */
+template <class T> inline void
+NS_QueryNotificationCallbacks(T *channel,
+ const nsIID &iid,
+ void **result)
+{
+ NS_PRECONDITION(channel, "null channel");
+ *result = nullptr;
+
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ channel->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs)
+ cbs->GetInterface(iid, result);
+ if (!*result) {
+ // try load group's notification callbacks...
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ channel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs)
+ cbs->GetInterface(iid, result);
+ }
+ }
+}
+
+// template helper:
+// Note: "class C" templatized only because nsIWebSocketChannel is currently not
+// an nsIChannel.
+
+template <class C, class T> inline void
+NS_QueryNotificationCallbacks(C *channel,
+ nsCOMPtr<T> &result)
+{
+ NS_QueryNotificationCallbacks(channel, NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(result));
+}
+
+/**
+ * Alternate form of NS_QueryNotificationCallbacks designed for use by
+ * nsIChannel implementations.
+ */
+inline void
+NS_QueryNotificationCallbacks(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ const nsIID &iid,
+ void **result)
+{
+ *result = nullptr;
+
+ if (callbacks)
+ callbacks->GetInterface(iid, result);
+ if (!*result) {
+ // try load group's notification callbacks...
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs)
+ cbs->GetInterface(iid, result);
+ }
+ }
+}
+
+/**
+ * Returns true if channel is using Private Browsing, or false if not.
+ * Returns false if channel's callbacks don't implement nsILoadContext.
+ */
+bool NS_UsePrivateBrowsing(nsIChannel *channel);
+
+/**
+ * Extract the NeckoOriginAttributes from the channel's triggering principal.
+ */
+bool NS_GetOriginAttributes(nsIChannel *aChannel,
+ mozilla::NeckoOriginAttributes &aAttributes);
+
+/**
+ * Returns true if the channel has visited any cross-origin URLs on any
+ * URLs that it was redirected through.
+ */
+bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport = false);
+
+// Constants duplicated from nsIScriptSecurityManager so we avoid having necko
+// know about script security manager.
+#define NECKO_NO_APP_ID 0
+#define NECKO_UNKNOWN_APP_ID UINT32_MAX
+// special app id reserved for separating the safebrowsing cookie
+#define NECKO_SAFEBROWSING_APP_ID UINT32_MAX - 1
+
+/**
+ * Gets AppId and isInIsolatedMozBrowserElement from channel's nsILoadContext.
+ * Returns false if error or channel's callbacks don't implement nsILoadContext.
+ */
+bool NS_GetAppInfo(nsIChannel *aChannel,
+ uint32_t *aAppID,
+ bool *aIsInIsolatedMozBrowserElement);
+
+/**
+ * Gets appId and browserOnly parameters from the TOPIC_WEB_APP_CLEAR_DATA
+ * nsIObserverService notification. Used when clearing user data or
+ * uninstalling web apps.
+ */
+nsresult NS_GetAppInfoFromClearDataNotification(nsISupports *aSubject,
+ uint32_t *aAppID,
+ bool *aBrowserOnly);
+
+/**
+ * Determines whether appcache should be checked for a given URI.
+ */
+bool NS_ShouldCheckAppCache(nsIURI *aURI, bool usePrivateBrowsing);
+
+bool NS_ShouldCheckAppCache(nsIPrincipal *aPrincipal, bool usePrivateBrowsing);
+
+/**
+ * Wraps an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2. This
+ * method is provided mainly for use by other methods in this file.
+ *
+ * *aAuthPrompt2 should be set to null before calling this function.
+ */
+void NS_WrapAuthPrompt(nsIAuthPrompt *aAuthPrompt,
+ nsIAuthPrompt2 **aAuthPrompt2);
+
+/**
+ * Gets an auth prompt from an interface requestor. This takes care of wrapping
+ * an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2.
+ */
+void NS_QueryAuthPrompt2(nsIInterfaceRequestor *aCallbacks,
+ nsIAuthPrompt2 **aAuthPrompt);
+
+/**
+ * Gets an nsIAuthPrompt2 from a channel. Use this instead of
+ * NS_QueryNotificationCallbacks for better backwards compatibility.
+ */
+void NS_QueryAuthPrompt2(nsIChannel *aChannel,
+ nsIAuthPrompt2 **aAuthPrompt);
+
+/* template helper */
+template <class T> inline void
+NS_QueryNotificationCallbacks(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsCOMPtr<T> &result)
+{
+ NS_QueryNotificationCallbacks(callbacks, loadGroup,
+ NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(result));
+}
+
+/* template helper */
+template <class T> inline void
+NS_QueryNotificationCallbacks(const nsCOMPtr<nsIInterfaceRequestor> &aCallbacks,
+ const nsCOMPtr<nsILoadGroup> &aLoadGroup,
+ nsCOMPtr<T> &aResult)
+{
+ NS_QueryNotificationCallbacks(aCallbacks.get(), aLoadGroup.get(), aResult);
+}
+
+/* template helper */
+template <class T> inline void
+NS_QueryNotificationCallbacks(const nsCOMPtr<nsIChannel> &aChannel,
+ nsCOMPtr<T> &aResult)
+{
+ NS_QueryNotificationCallbacks(aChannel.get(), aResult);
+}
+
+/**
+ * This function returns a nsIInterfaceRequestor instance that returns the
+ * same result as NS_QueryNotificationCallbacks when queried. It is useful
+ * as the value for nsISocketTransport::securityCallbacks.
+ */
+nsresult
+NS_NewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIEventTarget *target,
+ nsIInterfaceRequestor **result);
+
+nsresult
+NS_NewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIInterfaceRequestor **result);
+
+/**
+ * Helper function for testing online/offline state of the browser.
+ */
+bool NS_IsOffline();
+
+/**
+ * Helper functions for implementing nsINestedURI::innermostURI.
+ *
+ * Note that NS_DoImplGetInnermostURI is "private" -- call
+ * NS_ImplGetInnermostURI instead.
+ */
+nsresult NS_DoImplGetInnermostURI(nsINestedURI *nestedURI, nsIURI **result);
+
+nsresult NS_ImplGetInnermostURI(nsINestedURI *nestedURI, nsIURI **result);
+
+/**
+ * Helper function that ensures that |result| is a URI that's safe to
+ * return. If |uri| is immutable, just returns it, otherwise returns
+ * a clone. |uri| must not be null.
+ */
+nsresult NS_EnsureSafeToReturn(nsIURI *uri, nsIURI **result);
+
+/**
+ * Helper function that tries to set the argument URI to be immutable
+ */
+void NS_TryToSetImmutable(nsIURI *uri);
+
+/**
+ * Helper function for calling ToImmutableURI. If all else fails, returns
+ * the input URI. The optional second arg indicates whether we had to fall
+ * back to the input URI. Passing in a null URI is ok.
+ */
+already_AddRefed<nsIURI> NS_TryToMakeImmutable(nsIURI *uri,
+ nsresult *outRv = nullptr);
+
+/**
+ * Helper function for testing whether the given URI, or any of its
+ * inner URIs, has all the given protocol flags.
+ */
+nsresult NS_URIChainHasFlags(nsIURI *uri,
+ uint32_t flags,
+ bool *result);
+
+/**
+ * Helper function for getting the innermost URI for a given URI. The return
+ * value could be just the object passed in if it's not a nested URI.
+ */
+already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI *aURI);
+
+/**
+ * Get the "final" URI for a channel. This is either the same as GetURI or
+ * GetOriginalURI, depending on whether this channel has
+ * nsIChanel::LOAD_REPLACE set. For channels without that flag set, the final
+ * URI is the original URI, while for ones with the flag the final URI is the
+ * channel URI.
+ */
+nsresult NS_GetFinalChannelURI(nsIChannel *channel, nsIURI **uri);
+
+// NS_SecurityHashURI must return the same hash value for any two URIs that
+// compare equal according to NS_SecurityCompareURIs. Unfortunately, in the
+// case of files, it's not clear we can do anything better than returning
+// the schemeHash, so hashing files degenerates to storing them in a list.
+uint32_t NS_SecurityHashURI(nsIURI *aURI);
+
+bool NS_SecurityCompareURIs(nsIURI *aSourceURI,
+ nsIURI *aTargetURI,
+ bool aStrictFileOriginPolicy);
+
+bool NS_URIIsLocalFile(nsIURI *aURI);
+
+// When strict file origin policy is enabled, SecurityCompareURIs will fail for
+// file URIs that do not point to the same local file. This call provides an
+// alternate file-specific origin check that allows target files that are
+// contained in the same directory as the source.
+//
+// https://developer.mozilla.org/en-US/docs/Same-origin_policy_for_file:_URIs
+bool NS_RelaxStrictFileOriginPolicy(nsIURI *aTargetURI,
+ nsIURI *aSourceURI,
+ bool aAllowDirectoryTarget = false);
+
+bool NS_IsInternalSameURIRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags);
+
+bool NS_IsHSTSUpgradeRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags);
+
+nsresult NS_LinkRedirectChannels(uint32_t channelId,
+ nsIParentChannel *parentChannel,
+ nsIChannel **_result);
+
+/**
+ * Helper function to create a random URL string that's properly formed
+ * but guaranteed to be invalid.
+ */
+nsresult NS_MakeRandomInvalidURLString(nsCString &result);
+
+/**
+ * Helper function which checks whether the channel can be
+ * openend using Open2() or has to fall back to opening
+ * the channel using Open().
+ */
+nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel,
+ nsIInputStream **aStream);
+
+/**
+ * Helper function which checks whether the channel can be
+ * openend using AsyncOpen2() or has to fall back to opening
+ * the channel using AsyncOpen().
+ */
+nsresult NS_MaybeOpenChannelUsingAsyncOpen2(nsIChannel* aChannel,
+ nsIStreamListener *aListener);
+
+/**
+ * Helper function to determine whether urlString is Java-compatible --
+ * whether it can be passed to the Java URL(String) constructor without the
+ * latter throwing a MalformedURLException, or without Java otherwise
+ * mishandling it. This function (in effect) implements a scheme whitelist
+ * for Java.
+ */
+nsresult NS_CheckIsJavaCompatibleURLString(nsCString& urlString, bool *result);
+
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+uint32_t NS_GetContentDispositionFromToken(const nsAString &aDispToken);
+
+/** Determine the disposition (inline/attachment) of the content based on the
+ * Content-Disposition header
+ * @param aHeader the content-disposition header (full value)
+ * @param aChan the channel the header came from
+ */
+uint32_t NS_GetContentDispositionFromHeader(const nsACString &aHeader,
+ nsIChannel *aChan = nullptr);
+
+/** Extracts the filename out of a content-disposition header
+ * @param aFilename [out] The filename. Can be empty on error.
+ * @param aDisposition Value of a Content-Disposition header
+ * @param aURI Optional. Will be used to get a fallback charset for the
+ * filename, if it is QI'able to nsIURL
+ */
+nsresult NS_GetFilenameFromDisposition(nsAString &aFilename,
+ const nsACString &aDisposition,
+ nsIURI *aURI = nullptr);
+
+/**
+ * Make sure Personal Security Manager is initialized
+ */
+void net_EnsurePSMInit();
+
+/**
+ * Test whether a URI is "about:blank". |uri| must not be null
+ */
+bool NS_IsAboutBlank(nsIURI *uri);
+
+nsresult NS_GenerateHostPort(const nsCString &host, int32_t port,
+ nsACString &hostLine);
+
+/**
+ * Sniff the content type for a given request or a given buffer.
+ *
+ * aSnifferType can be either NS_CONTENT_SNIFFER_CATEGORY or
+ * NS_DATA_SNIFFER_CATEGORY. The function returns the sniffed content type
+ * in the aSniffedType argument. This argument will not be modified if the
+ * content type could not be sniffed.
+ */
+void NS_SniffContent(const char *aSnifferType, nsIRequest *aRequest,
+ const uint8_t *aData, uint32_t aLength,
+ nsACString &aSniffedType);
+
+/**
+ * Whether the channel was created to load a srcdoc document.
+ * Note that view-source:about:srcdoc is classified as a srcdoc document by
+ * this function, which may not be applicable everywhere.
+ */
+bool NS_IsSrcdocChannel(nsIChannel *aChannel);
+
+/**
+ * Return true if the given string is a reasonable HTTP header value given the
+ * definition in RFC 2616 section 4.2. Currently we don't pay the cost to do
+ * full, sctrict validation here since it would require fulling parsing the
+ * value.
+ */
+bool NS_IsReasonableHTTPHeaderValue(const nsACString &aValue);
+
+/**
+ * Return true if the given string is a valid HTTP token per RFC 2616 section
+ * 2.2.
+ */
+bool NS_IsValidHTTPToken(const nsACString &aToken);
+
+/**
+ * Return true if the given request must be upgraded to HTTPS.
+ */
+nsresult NS_ShouldSecureUpgrade(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIPrincipal* aChannelResultPrincipal,
+ bool aPrivateBrowsing,
+ bool aAllowSTS,
+ bool& aShouldUpgrade);
+
+/**
+ * Returns an https URI for channels that need to go through secure upgrades.
+ */
+nsresult NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI);
+
+nsresult NS_CompareLoadInfoAndLoadContext(nsIChannel *aChannel);
+
+namespace mozilla {
+namespace net {
+
+const static uint64_t kJS_MAX_SAFE_UINTEGER = +9007199254740991ULL;
+const static int64_t kJS_MIN_SAFE_INTEGER = -9007199254740991LL;
+const static int64_t kJS_MAX_SAFE_INTEGER = +9007199254740991LL;
+
+// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER
+bool InScriptableRange(int64_t val);
+
+// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER
+bool InScriptableRange(uint64_t val);
+
+} // namespace net
+} // namespace mozilla
+
+// Include some function bodies for callers with external linkage
+#ifndef MOZILLA_INTERNAL_API
+#include "nsNetUtilInlines.h"
+#endif
+
+#endif // !nsNetUtil_h__
diff --git a/netwerk/base/nsNetUtilInlines.h b/netwerk/base/nsNetUtilInlines.h
new file mode 100644
index 0000000000..7003814d59
--- /dev/null
+++ b/netwerk/base/nsNetUtilInlines.h
@@ -0,0 +1,395 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetUtil_inl
+#define nsNetUtil_inl
+
+#include "mozilla/Services.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsIBufferedStreams.h"
+#include "nsIChannel.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIIOService.h"
+#include "nsINestedURI.h"
+#include "nsINode.h"
+#include "nsIProtocolHandler.h"
+#include "nsIStandardURL.h"
+#include "nsIStreamLoader.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsIURI.h"
+#include "nsIURIWithPrincipal.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsNetCID.h"
+#include "nsStringStream.h"
+
+#ifdef MOZILLA_INTERNAL_API
+// Don't allow functions that end up in nsNetUtil.cpp to be inlined out.
+#define INLINE_IF_EXTERN MOZ_NEVER_INLINE
+#else
+// Make sure that functions included via nsNetUtil.h don't get multiply defined.
+#define INLINE_IF_EXTERN MOZ_ALWAYS_INLINE
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+
+INLINE_IF_EXTERN already_AddRefed<nsIIOService>
+do_GetIOService(nsresult *error /* = 0 */)
+{
+ nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService();
+ if (error)
+ *error = io ? NS_OK : NS_ERROR_FAILURE;
+ return io.forget();
+}
+
+INLINE_IF_EXTERN already_AddRefed<nsINetUtil>
+do_GetNetUtil(nsresult *error /* = 0 */)
+{
+ nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService();
+ nsCOMPtr<nsINetUtil> util;
+ if (io)
+ util = do_QueryInterface(io);
+
+ if (error)
+ *error = !!util ? NS_OK : NS_ERROR_FAILURE;
+ return util.forget();
+}
+
+#else
+
+INLINE_IF_EXTERN const nsGetServiceByContractIDWithError
+do_GetIOService(nsresult *error /* = 0 */)
+{
+ return nsGetServiceByContractIDWithError(NS_IOSERVICE_CONTRACTID, error);
+}
+
+INLINE_IF_EXTERN const nsGetServiceByContractIDWithError
+do_GetNetUtil(nsresult *error /* = 0 */)
+{
+ return do_GetIOService(error);
+}
+#endif
+
+// private little helper function... don't call this directly!
+MOZ_ALWAYS_INLINE nsresult
+net_EnsureIOService(nsIIOService **ios, nsCOMPtr<nsIIOService> &grip)
+{
+ nsresult rv = NS_OK;
+ if (!*ios) {
+ grip = do_GetIOService(&rv);
+ *ios = grip;
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_URIChainHasFlags(nsIURI *uri,
+ uint32_t flags,
+ bool *result)
+{
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return util->URIChainHasFlags(uri, flags, result);
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewURI(nsIURI **result,
+ const nsACString &spec,
+ const char *charset /* = nullptr */,
+ nsIURI *baseURI /* = nullptr */,
+ nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService)
+ rv = ioService->NewURI(spec, charset, baseURI, result);
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewURI(nsIURI **result,
+ const nsAString &spec,
+ const char *charset /* = nullptr */,
+ nsIURI *baseURI /* = nullptr */,
+ nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ return NS_NewURI(result, NS_ConvertUTF16toUTF8(spec), charset, baseURI, ioService);
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewURI(nsIURI **result,
+ const char *spec,
+ nsIURI *baseURI /* = nullptr */,
+ nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ return NS_NewURI(result, nsDependentCString(spec), nullptr, baseURI, ioService);
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewFileURI(nsIURI **result,
+ nsIFile *spec,
+ nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService)
+ rv = ioService->NewFileURI(spec, result);
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(outChannel);
+
+ nsCOMPtr<nsIIOService> grip;
+ nsresult rv = net_EnsureIOService(&aIoService, grip);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = aIoService->NewChannelFromURI2(
+ aUri,
+ aLoadingNode ?
+ aLoadingNode->AsDOMNode() : nullptr,
+ aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadGroup) {
+ rv = channel->SetLoadGroup(aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ // Retain the LOAD_REPLACE load flag if set.
+ nsLoadFlags normalLoadFlags = 0;
+ channel->GetLoadFlags(&normalLoadFlags);
+ rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsILoadInfo *aLoadInfo,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ // NS_NewChannelInternal is mostly called for channel redirects. We should allow
+ // the creation of a channel even if the original channel did not have a loadinfo
+ // attached.
+ NS_ENSURE_ARG_POINTER(outChannel);
+
+ nsCOMPtr<nsIIOService> grip;
+ nsresult rv = net_EnsureIOService(&aIoService, grip);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = aIoService->NewChannelFromURIWithLoadInfo(
+ aUri,
+ aLoadInfo,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadGroup) {
+ rv = channel->SetLoadGroup(aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ // Retain the LOAD_REPLACE load flag if set.
+ nsLoadFlags normalLoadFlags = 0;
+ channel->GetLoadFlags(&normalLoadFlags);
+ rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+INLINE_IF_EXTERN nsresult /* NS_NewChannelPrincipal */
+NS_NewChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ return NS_NewChannelInternal(outChannel,
+ aUri,
+ nullptr, // aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aIoService);
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewStreamLoader(nsIStreamLoader **result,
+ nsIStreamLoaderObserver *observer,
+ nsIRequestObserver *requestObserver /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStreamLoader> loader =
+ do_CreateInstance(NS_STREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer, requestObserver);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewLocalFileInputStream(nsIInputStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileInputStream> in =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ in.forget(result);
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewLocalFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ out.forget(result);
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN MOZ_MUST_USE nsresult
+NS_NewBufferedInputStream(nsIInputStream **result,
+ nsIInputStream *str,
+ uint32_t bufferSize)
+{
+ nsresult rv;
+ nsCOMPtr<nsIBufferedInputStream> in =
+ do_CreateInstance(NS_BUFFEREDINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(str, bufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ in.forget(result);
+ }
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewPostDataStream(nsIInputStream **result,
+ bool isFile,
+ const nsACString &data)
+{
+ nsresult rv;
+
+ if (isFile) {
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIInputStream> fileStream;
+
+ rv = NS_NewNativeLocalFile(data, false, getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
+ if (NS_SUCCEEDED(rv)) {
+ // wrap the file stream with a buffered input stream
+ rv = NS_NewBufferedInputStream(result, fileStream, 8192);
+ }
+ }
+ return rv;
+ }
+
+ // otherwise, create a string stream for the data (copies)
+ nsCOMPtr<nsIStringInputStream> stream
+ (do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = stream->SetData(data.BeginReading(), data.Length());
+ if (NS_FAILED(rv))
+ return rv;
+
+ stream.forget(result);
+ return NS_OK;
+}
+
+INLINE_IF_EXTERN bool
+NS_IsOffline()
+{
+ bool offline = true;
+ bool connectivity = true;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (ios) {
+ ios->GetOffline(&offline);
+ ios->GetConnectivity(&connectivity);
+ }
+ return offline || !connectivity;
+}
+
+#endif // nsNetUtil_inl
diff --git a/netwerk/base/nsNetworkInfoService.cpp b/netwerk/base/nsNetworkInfoService.cpp
new file mode 100644
index 0000000000..5b188c7f14
--- /dev/null
+++ b/netwerk/base/nsNetworkInfoService.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(XP_MACOSX) || defined(XP_LINUX)
+#include <unistd.h>
+#elif defined(XP_WIN)
+#include <winsock2.h>
+#endif
+
+#include "nsNetworkInfoService.h"
+#include "mozilla/ScopeExit.h"
+
+#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
+#include "NetworkInfoServiceImpl.h"
+#else
+#error "Unsupported platform for nsNetworkInfoService! Check moz.build"
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsNetworkInfoService,
+ nsINetworkInfoService)
+
+nsNetworkInfoService::nsNetworkInfoService()
+{
+}
+
+nsresult
+nsNetworkInfoService::Init()
+{
+ return NS_OK;
+}
+
+nsresult
+nsNetworkInfoService::ListNetworkAddresses(nsIListNetworkAddressesListener* aListener)
+{
+ nsresult rv;
+
+ AddrMapType addrMap;
+ rv = DoListAddresses(addrMap);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aListener->OnListNetworkAddressesFailed();
+ return NS_OK;
+ }
+
+ uint32_t addrCount = addrMap.Count();
+ const char** addrStrings = (const char**) malloc(sizeof(*addrStrings) * addrCount);
+ if (!addrStrings) {
+ aListener->OnListNetworkAddressesFailed();
+ return NS_OK;
+ }
+ auto autoFreeAddrStrings = MakeScopeExit([&] {
+ free(addrStrings);
+ });
+
+ uint32_t idx = 0;
+ for (auto iter = addrMap.Iter(); !iter.Done(); iter.Next()) {
+ addrStrings[idx++] = iter.Data().get();
+ }
+ aListener->OnListedNetworkAddresses(addrStrings, addrCount);
+ return NS_OK;
+}
+
+// TODO: Bug 1275373: https://bugzilla.mozilla.org/show_bug.cgi?id=1275373
+// Use platform-specific implementation of DoGetHostname on Cocoa and Windows.
+static nsresult
+DoGetHostname(nsACString& aHostname)
+{
+ char hostnameBuf[256];
+ int result = gethostname(hostnameBuf, 256);
+ if (result == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Ensure that there is always a terminating NUL byte.
+ hostnameBuf[255] = '\0';
+
+ // Find the first '.', terminate string there.
+ char* dotLocation = strchr(hostnameBuf, '.');
+ if (dotLocation) {
+ *dotLocation = '\0';
+ }
+
+ if (strlen(hostnameBuf) == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aHostname.AssignASCII(hostnameBuf);
+ return NS_OK;
+}
+
+nsresult
+nsNetworkInfoService::GetHostname(nsIGetHostnameListener* aListener)
+{
+ nsresult rv;
+ nsCString hostnameStr;
+ rv = DoGetHostname(hostnameStr);
+ if (NS_FAILED(rv)) {
+ aListener->OnGetHostnameFailed();
+ return NS_OK;
+ }
+
+ aListener->OnGotHostname(hostnameStr);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsNetworkInfoService.h b/netwerk/base/nsNetworkInfoService.h
new file mode 100644
index 0000000000..be6f686bd2
--- /dev/null
+++ b/netwerk/base/nsNetworkInfoService.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_nsNetworkInfoService_h
+#define mozilla_net_nsNetworkInfoService_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/ErrorResult.h"
+
+#include "nsINetworkInfoService.h"
+
+#define NETWORKINFOSERVICE_CID \
+{ 0x296d0900, 0xf8ef, 0x4df0, \
+ { 0x9c, 0x35, 0xdb, 0x58, 0x62, 0xab, 0xc5, 0x8d } }
+
+namespace mozilla {
+namespace net {
+
+class nsNetworkInfoService final
+ : public nsINetworkInfoService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKINFOSERVICE
+
+ nsresult Init();
+
+ explicit nsNetworkInfoService();
+
+private:
+ virtual ~nsNetworkInfoService() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_dom_nsNetworkInfoService_h
diff --git a/netwerk/base/nsPACMan.cpp b/netwerk/base/nsPACMan.cpp
new file mode 100644
index 0000000000..37d3e8b6bb
--- /dev/null
+++ b/netwerk/base/nsPACMan.cpp
@@ -0,0 +1,769 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPACMan.h"
+#include "nsThreadUtils.h"
+#include "nsIAuthPrompt.h"
+#include "nsIPromptFactory.h"
+#include "nsIHttpChannel.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsNetUtil.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsISystemProxySettings.h"
+#include "nsContentUtils.h"
+#include "mozilla/Preferences.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gProxyLog("proxy");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+
+// The PAC thread does evaluations of both PAC files and
+// nsISystemProxySettings because they can both block the calling thread and we
+// don't want that on the main thread
+
+// Check to see if the underlying request was not an error page in the case of
+// a HTTP request. For other types of channels, just return true.
+static bool
+HttpRequestSucceeded(nsIStreamLoader *loader)
+{
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+
+ bool result = true; // default to assuming success
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ if (httpChannel)
+ httpChannel->GetRequestSucceeded(&result);
+
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+// The ExecuteCallback runnable is triggered by
+// nsPACManCallback::OnQueryComplete on the Main thread when its completion is
+// discovered on the pac thread
+
+class ExecuteCallback final : public Runnable
+{
+public:
+ ExecuteCallback(nsPACManCallback *aCallback,
+ nsresult status)
+ : mCallback(aCallback)
+ , mStatus(status)
+ {
+ }
+
+ void SetPACString(const nsCString &pacString)
+ {
+ mPACString = pacString;
+ }
+
+ void SetPACURL(const nsCString &pacURL)
+ {
+ mPACURL = pacURL;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mCallback->OnQueryComplete(mStatus, mPACString, mPACURL);
+ mCallback = nullptr;
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPACManCallback> mCallback;
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+};
+
+//-----------------------------------------------------------------------------
+
+// The PAC thread must be deleted from the main thread, this class
+// acts as a proxy to do that, as the PACMan is reference counted
+// and might be destroyed on either thread
+
+class ShutdownThread final : public Runnable
+{
+public:
+ explicit ShutdownThread(nsIThread *thread)
+ : mThread(thread)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mThread->Shutdown();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+// Dispatch this to wait until the PAC thread shuts down.
+
+class WaitForThreadShutdown final : public Runnable
+{
+public:
+ explicit WaitForThreadShutdown(nsPACMan *aPACMan)
+ : mPACMan(aPACMan)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mPACMan->mPACThread) {
+ mPACMan->mPACThread->Shutdown();
+ mPACMan->mPACThread = nullptr;
+ }
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPACMan> mPACMan;
+};
+
+//-----------------------------------------------------------------------------
+
+// PACLoadComplete allows the PAC thread to tell the main thread that
+// the javascript PAC file has been installed (perhaps unsuccessfully)
+// and that there is no reason to queue executions anymore
+
+class PACLoadComplete final : public Runnable
+{
+public:
+ explicit PACLoadComplete(nsPACMan *aPACMan)
+ : mPACMan(aPACMan)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mPACMan->mLoader = nullptr;
+ mPACMan->PostProcessPendingQ();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPACMan> mPACMan;
+};
+
+//-----------------------------------------------------------------------------
+
+// ExecutePACThreadAction is used to proxy actions from the main
+// thread onto the PAC thread. There are 3 options: process the queue,
+// cancel the queue, and setup the javascript context with a new PAC file
+
+class ExecutePACThreadAction final : public Runnable
+{
+public:
+ // by default we just process the queue
+ explicit ExecutePACThreadAction(nsPACMan *aPACMan)
+ : mPACMan(aPACMan)
+ , mCancel(false)
+ , mCancelStatus(NS_OK)
+ , mSetupPAC(false)
+ { }
+
+ void CancelQueue (nsresult status)
+ {
+ mCancel = true;
+ mCancelStatus = status;
+ }
+
+ void SetupPAC (const char *text, uint32_t datalen, nsCString &pacURI)
+ {
+ mSetupPAC = true;
+ mSetupPACData.Assign(text, datalen);
+ mSetupPACURI = pacURI;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ if (mCancel) {
+ mPACMan->CancelPendingQ(mCancelStatus);
+ mCancel = false;
+ return NS_OK;
+ }
+
+ if (mSetupPAC) {
+ mSetupPAC = false;
+
+ mPACMan->mPAC.Init(mSetupPACURI,
+ mSetupPACData,
+ mPACMan->mIncludePath);
+
+ RefPtr<PACLoadComplete> runnable = new PACLoadComplete(mPACMan);
+ NS_DispatchToMainThread(runnable);
+ return NS_OK;
+ }
+
+ mPACMan->ProcessPendingQ();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPACMan> mPACMan;
+
+ bool mCancel;
+ nsresult mCancelStatus;
+
+ bool mSetupPAC;
+ nsCString mSetupPACData;
+ nsCString mSetupPACURI;
+};
+
+//-----------------------------------------------------------------------------
+
+PendingPACQuery::PendingPACQuery(nsPACMan *pacMan, nsIURI *uri,
+ nsPACManCallback *callback,
+ bool mainThreadResponse)
+ : mPACMan(pacMan)
+ , mCallback(callback)
+ , mOnMainThreadOnly(mainThreadResponse)
+{
+ uri->GetAsciiSpec(mSpec);
+ uri->GetAsciiHost(mHost);
+ uri->GetScheme(mScheme);
+ uri->GetPort(&mPort);
+}
+
+void
+PendingPACQuery::Complete(nsresult status, const nsCString &pacString)
+{
+ if (!mCallback)
+ return;
+ RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, status);
+ runnable->SetPACString(pacString);
+ if (mOnMainThreadOnly)
+ NS_DispatchToMainThread(runnable);
+ else
+ runnable->Run();
+}
+
+void
+PendingPACQuery::UseAlternatePACFile(const nsCString &pacURL)
+{
+ if (!mCallback)
+ return;
+
+ RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, NS_OK);
+ runnable->SetPACURL(pacURL);
+ if (mOnMainThreadOnly)
+ NS_DispatchToMainThread(runnable);
+ else
+ runnable->Run();
+}
+
+NS_IMETHODIMP
+PendingPACQuery::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ mPACMan->PostQuery(this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+static bool sThreadLocalSetup = false;
+static uint32_t sThreadLocalIndex = 0xdeadbeef; // out of range
+
+static const char *kPACIncludePath =
+ "network.proxy.autoconfig_url.include_path";
+
+nsPACMan::nsPACMan()
+ : mLoadPending(false)
+ , mShutdown(false)
+ , mLoadFailureCount(0)
+ , mInProgress(false)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread");
+ if (!sThreadLocalSetup){
+ sThreadLocalSetup = true;
+ PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr);
+ }
+ mPAC.SetThreadLocalIndex(sThreadLocalIndex);
+ mIncludePath = Preferences::GetBool(kPACIncludePath, false);
+}
+
+nsPACMan::~nsPACMan()
+{
+ if (mPACThread) {
+ if (NS_IsMainThread()) {
+ mPACThread->Shutdown();
+ }
+ else {
+ RefPtr<ShutdownThread> runnable = new ShutdownThread(mPACThread);
+ NS_DispatchToMainThread(runnable);
+ }
+ }
+
+ NS_ASSERTION(mLoader == nullptr, "pac man not shutdown properly");
+ NS_ASSERTION(mPendingQ.isEmpty(), "pac man not shutdown properly");
+}
+
+void
+nsPACMan::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "pacman must be shutdown on main thread");
+ if (mShutdown) {
+ return;
+ }
+ mShutdown = true;
+ CancelExistingLoad();
+ PostCancelPendingQ(NS_ERROR_ABORT);
+
+ RefPtr<WaitForThreadShutdown> runnable = new WaitForThreadShutdown(this);
+ NS_DispatchToMainThread(runnable);
+}
+
+nsresult
+nsPACMan::AsyncGetProxyForURI(nsIURI *uri,
+ nsPACManCallback *callback,
+ bool mainThreadResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mShutdown)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Maybe Reload PAC
+ if (!mPACURISpec.IsEmpty() && !mScheduledReload.IsNull() &&
+ TimeStamp::Now() > mScheduledReload) {
+ LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n"));
+
+ LoadPACFromURI(EmptyCString());
+ }
+
+ RefPtr<PendingPACQuery> query =
+ new PendingPACQuery(this, uri, callback, mainThreadResponse);
+
+ if (IsPACURI(uri)) {
+ // deal with this directly instead of queueing it
+ query->Complete(NS_OK, EmptyCString());
+ return NS_OK;
+ }
+
+ return mPACThread->Dispatch(query, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+nsresult
+nsPACMan::PostQuery(PendingPACQuery *query)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+
+ if (mShutdown) {
+ query->Complete(NS_ERROR_NOT_AVAILABLE, EmptyCString());
+ return NS_OK;
+ }
+
+ // add a reference to the query while it is in the pending list
+ RefPtr<PendingPACQuery> addref(query);
+ mPendingQ.insertBack(addref.forget().take());
+ ProcessPendingQ();
+ return NS_OK;
+}
+
+nsresult
+nsPACMan::LoadPACFromURI(const nsCString &spec)
+{
+ NS_ENSURE_STATE(!mShutdown);
+ NS_ENSURE_ARG(!spec.IsEmpty() || !mPACURISpec.IsEmpty());
+
+ nsCOMPtr<nsIStreamLoader> loader =
+ do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
+ NS_ENSURE_STATE(loader);
+
+ LOG(("nsPACMan::LoadPACFromURI %s\n", spec.get()));
+ // Since we might get called from nsProtocolProxyService::Init, we need to
+ // post an event back to the main thread before we try to use the IO service.
+ //
+ // But, we need to flag ourselves as loading, so that we queue up any PAC
+ // queries the enter between now and when we actually load the PAC file.
+
+ if (!mLoadPending) {
+ nsresult rv;
+ if (NS_FAILED(rv = NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsPACMan::StartLoading))))
+ return rv;
+ mLoadPending = true;
+ }
+
+ CancelExistingLoad();
+
+ mLoader = loader;
+ if (!spec.IsEmpty()) {
+ mPACURISpec = spec;
+ mPACURIRedirectSpec.Truncate();
+ mNormalPACURISpec.Truncate(); // set at load time
+ mLoadFailureCount = 0; // reset
+ }
+
+ // reset to Null
+ mScheduledReload = TimeStamp();
+ return NS_OK;
+}
+
+void
+nsPACMan::StartLoading()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mLoadPending = false;
+
+ // CancelExistingLoad was called...
+ if (!mLoader) {
+ PostCancelPendingQ(NS_ERROR_ABORT);
+ return;
+ }
+
+ if (NS_SUCCEEDED(mLoader->Init(this, nullptr))) {
+ // Always hit the origin server when loading PAC.
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (ios) {
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIURI> pacURI;
+ NS_NewURI(getter_AddRefs(pacURI), mPACURISpec);
+
+ // NOTE: This results in GetProxyForURI being called
+ if (pacURI) {
+ nsresult rv = pacURI->GetSpec(mNormalPACURISpec);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ NS_NewChannel(getter_AddRefs(channel),
+ pacURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ios);
+ }
+ else {
+ LOG(("nsPACMan::StartLoading Failed pacspec uri conversion %s\n",
+ mPACURISpec.get()));
+ }
+
+ if (channel) {
+ channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE);
+ channel->SetNotificationCallbacks(this);
+ if (NS_SUCCEEDED(channel->AsyncOpen2(mLoader)))
+ return;
+ }
+ }
+ }
+
+ CancelExistingLoad();
+ PostCancelPendingQ(NS_ERROR_UNEXPECTED);
+}
+
+
+void
+nsPACMan::OnLoadFailure()
+{
+ int32_t minInterval = 5; // 5 seconds
+ int32_t maxInterval = 300; // 5 minutes
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min",
+ &minInterval);
+ prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max",
+ &maxInterval);
+ }
+
+ int32_t interval = minInterval << mLoadFailureCount++; // seconds
+ if (!interval || interval > maxInterval)
+ interval = maxInterval;
+
+ mScheduledReload = TimeStamp::Now() + TimeDuration::FromSeconds(interval);
+
+ LOG(("OnLoadFailure: retry in %d seconds (%d fails)\n",
+ interval, mLoadFailureCount));
+
+ // while we wait for the retry queued members should try direct
+ // even if that means fast failure.
+ PostCancelPendingQ(NS_ERROR_NOT_AVAILABLE);
+}
+
+void
+nsPACMan::CancelExistingLoad()
+{
+ if (mLoader) {
+ nsCOMPtr<nsIRequest> request;
+ mLoader->GetRequest(getter_AddRefs(request));
+ if (request)
+ request->Cancel(NS_ERROR_ABORT);
+ mLoader = nullptr;
+ }
+}
+
+void
+nsPACMan::PostProcessPendingQ()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ RefPtr<ExecutePACThreadAction> pending =
+ new ExecutePACThreadAction(this);
+ if (mPACThread)
+ mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void
+nsPACMan::PostCancelPendingQ(nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ RefPtr<ExecutePACThreadAction> pending =
+ new ExecutePACThreadAction(this);
+ pending->CancelQueue(status);
+ if (mPACThread)
+ mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void
+nsPACMan::CancelPendingQ(nsresult status)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ RefPtr<PendingPACQuery> query;
+
+ while (!mPendingQ.isEmpty()) {
+ query = dont_AddRef(mPendingQ.popLast());
+ query->Complete(status, EmptyCString());
+ }
+
+ if (mShutdown)
+ mPAC.Shutdown();
+}
+
+void
+nsPACMan::ProcessPendingQ()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ while (ProcessPending());
+
+ if (mShutdown) {
+ mPAC.Shutdown();
+ } else {
+ // do GC while the thread has nothing pending
+ mPAC.GC();
+ }
+}
+
+// returns true if progress was made by shortening the queue
+bool
+nsPACMan::ProcessPending()
+{
+ if (mPendingQ.isEmpty())
+ return false;
+
+ // queue during normal load, but if we are retrying a failed load then
+ // fast fail the queries
+ if (mInProgress || (IsLoading() && !mLoadFailureCount))
+ return false;
+
+ RefPtr<PendingPACQuery> query(dont_AddRef(mPendingQ.popFirst()));
+
+ if (mShutdown || IsLoading()) {
+ query->Complete(NS_ERROR_NOT_AVAILABLE, EmptyCString());
+ return true;
+ }
+
+ nsAutoCString pacString;
+ bool completed = false;
+ mInProgress = true;
+ nsAutoCString PACURI;
+
+ // first we need to consider the system proxy changing the pac url
+ if (mSystemProxySettings &&
+ NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
+ !PACURI.IsEmpty() &&
+ !PACURI.Equals(mPACURISpec)) {
+ query->UseAlternatePACFile(PACURI);
+ LOG(("Use PAC from system settings: %s\n", PACURI.get()));
+ completed = true;
+ }
+
+ // now try the system proxy settings for this particular url if
+ // PAC was not specified
+ if (!completed && mSystemProxySettings && PACURI.IsEmpty() &&
+ NS_SUCCEEDED(mSystemProxySettings->
+ GetProxyForURI(query->mSpec, query->mScheme,
+ query->mHost, query->mPort,
+ pacString))) {
+ LOG(("Use proxy from system settings: %s\n", pacString.get()));
+ query->Complete(NS_OK, pacString);
+ completed = true;
+ }
+
+ // the systemproxysettings didn't complete the resolution. try via PAC
+ if (!completed) {
+ nsresult status = mPAC.GetProxyForURI(query->mSpec, query->mHost,
+ pacString);
+ LOG(("Use proxy from PAC: %s\n", pacString.get()));
+ query->Complete(status, pacString);
+ }
+
+ mInProgress = false;
+ return true;
+}
+
+NS_IMPL_ISUPPORTS(nsPACMan, nsIStreamLoaderObserver,
+ nsIInterfaceRequestor, nsIChannelEventSink)
+
+NS_IMETHODIMP
+nsPACMan::OnStreamComplete(nsIStreamLoader *loader,
+ nsISupports *context,
+ nsresult status,
+ uint32_t dataLen,
+ const uint8_t *data)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mLoader != loader) {
+ // If this happens, then it means that LoadPACFromURI was called more
+ // than once before the initial call completed. In this case, status
+ // should be NS_ERROR_ABORT, and if so, then we know that we can and
+ // should delay any processing.
+ LOG(("OnStreamComplete: called more than once\n"));
+ if (status == NS_ERROR_ABORT)
+ return NS_OK;
+ }
+
+ LOG(("OnStreamComplete: entry\n"));
+
+ if (NS_SUCCEEDED(status) && HttpRequestSucceeded(loader)) {
+ // Get the URI spec used to load this PAC script.
+ nsAutoCString pacURI;
+ {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ uri->GetAsciiSpec(pacURI);
+ }
+ }
+
+ // We assume that the PAC text is ASCII (or ISO-Latin-1). We've had this
+ // assumption forever, and some real-world PAC scripts actually have some
+ // non-ASCII text in comment blocks (see bug 296163).
+ const char *text = (const char *) data;
+
+ // we have succeeded in loading the pac file using a bunch of interfaces that
+ // are main thread only, unfortunately we have to initialize the instance of
+ // the PAC evaluator (NS_PROXYAUTOCONFIG_CONTRACTID) on the pac thread, because
+ // that is where it will be used.
+
+ RefPtr<ExecutePACThreadAction> pending =
+ new ExecutePACThreadAction(this);
+ pending->SetupPAC(text, dataLen, pacURI);
+ if (mPACThread)
+ mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL);
+
+ LOG(("OnStreamComplete: process the PAC contents\n"));
+
+ // Even if the PAC file could not be parsed, we did succeed in loading the
+ // data for it.
+ mLoadFailureCount = 0;
+ } else {
+ // We were unable to load the PAC file (presumably because of a network
+ // failure). Try again a little later.
+ LOG(("OnStreamComplete: unable to load PAC, retry later\n"));
+ OnLoadFailure();
+ }
+
+ if (NS_SUCCEEDED(status))
+ PostProcessPendingQ();
+ else
+ PostCancelPendingQ(status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPACMan::GetInterface(const nsIID &iid, void **result)
+{
+ // In case loading the PAC file requires authentication.
+ if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ nsCOMPtr<nsIPromptFactory> promptFac = do_GetService("@mozilla.org/prompter;1");
+ NS_ENSURE_TRUE(promptFac, NS_ERROR_FAILURE);
+ return promptFac->GetPrompt(nullptr, iid, reinterpret_cast<void**>(result));
+ }
+
+ // In case loading the PAC file results in a redirect.
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink *>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsPACMan::AsyncOnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> pacURI;
+ if (NS_FAILED((rv = newChannel->GetURI(getter_AddRefs(pacURI)))))
+ return rv;
+
+ rv = pacURI->GetSpec(mPACURIRedirectSpec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ LOG(("nsPACMan redirect from original %s to redirected %s\n",
+ mPACURISpec.get(), mPACURIRedirectSpec.get()));
+
+ // do not update mPACURISpec - that needs to stay as the
+ // configured URI so that we can determine when the config changes.
+ // However do track the most recent URI in the redirect change
+ // as mPACURIRedirectSpec so that URI can be allowed to bypass
+ // the proxy and actually fetch the pac file.
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+void
+nsPACMan::NamePACThread()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ PR_SetCurrentThreadName("Proxy Resolution");
+}
+
+nsresult
+nsPACMan::Init(nsISystemProxySettings *systemProxySettings)
+{
+ mSystemProxySettings = systemProxySettings;
+
+ nsresult rv = NS_NewThread(getter_AddRefs(mPACThread), nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // don't check return value as it is not a big deal for this to fail.
+ mPACThread->Dispatch(NewRunnableMethod(this, &nsPACMan::NamePACThread),
+ nsIEventTarget::DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPACMan.h b/netwerk/base/nsPACMan.h
new file mode 100644
index 0000000000..def0843cb8
--- /dev/null
+++ b/netwerk/base/nsPACMan.h
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPACMan_h__
+#define nsPACMan_h__
+
+#include "nsIStreamLoader.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "ProxyAutoConfig.h"
+#include "nsThreadUtils.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "nsAutoPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Atomics.h"
+
+class nsISystemProxySettings;
+class nsIThread;
+
+namespace mozilla {
+namespace net {
+
+class nsPACMan;
+class WaitForThreadShutdown;
+
+/**
+ * This class defines a callback interface used by AsyncGetProxyForURI.
+ */
+class NS_NO_VTABLE nsPACManCallback : public nsISupports
+{
+public:
+ /**
+ * This method is invoked on the same thread that called AsyncGetProxyForURI.
+ *
+ * @param status
+ * This parameter indicates whether or not the PAC query succeeded.
+ * @param pacString
+ * This parameter holds the value of the PAC string. It is empty when
+ * status is a failure code.
+ * @param newPACURL
+ * This parameter holds the URL of a new PAC file that should be loaded
+ * before the query is evaluated again. At least one of pacString and
+ * newPACURL should be 0 length.
+ */
+ virtual void OnQueryComplete(nsresult status,
+ const nsCString &pacString,
+ const nsCString &newPACURL) = 0;
+};
+
+class PendingPACQuery final : public Runnable,
+ public LinkedListElement<PendingPACQuery>
+{
+public:
+ PendingPACQuery(nsPACMan *pacMan, nsIURI *uri,
+ nsPACManCallback *callback,
+ bool mainThreadResponse);
+
+ // can be called from either thread
+ void Complete(nsresult status, const nsCString &pacString);
+ void UseAlternatePACFile(const nsCString &pacURL);
+
+ nsCString mSpec;
+ nsCString mScheme;
+ nsCString mHost;
+ int32_t mPort;
+
+ NS_IMETHOD Run(void); /* Runnable */
+
+private:
+ nsPACMan *mPACMan; // weak reference
+
+private:
+ RefPtr<nsPACManCallback> mCallback;
+ bool mOnMainThreadOnly;
+};
+
+/**
+ * This class provides an abstraction layer above the PAC thread. The methods
+ * defined on this class are intended to be called on the main thread only.
+ */
+
+class nsPACMan final : public nsIStreamLoaderObserver
+ , public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsPACMan();
+
+ /**
+ * This method may be called to shutdown the PAC manager. Any async queries
+ * that have not yet completed will either finish normally or be canceled by
+ * the time this method returns.
+ */
+ void Shutdown();
+
+ /**
+ * This method queries a PAC result asynchronously. The callback runs on the
+ * calling thread. If the PAC file has not yet been loaded, then this method
+ * will queue up the request, and complete it once the PAC file has been
+ * loaded.
+ *
+ * @param uri
+ * The URI to query.
+ * @param callback
+ * The callback to run once the PAC result is available.
+ * @param mustCallbackOnMainThread
+ * If set to false the callback can be made from the PAC thread
+ */
+ nsresult AsyncGetProxyForURI(nsIURI *uri,
+ nsPACManCallback *callback,
+ bool mustCallbackOnMainThread);
+
+ /**
+ * This method may be called to reload the PAC file. While we are loading
+ * the PAC file, any asynchronous PAC queries will be queued up to be
+ * processed once the PAC file finishes loading.
+ *
+ * @param pacSpec
+ * The non normalized uri spec of this URI used for comparison with
+ * system proxy settings to determine if the PAC uri has changed.
+ */
+ nsresult LoadPACFromURI(const nsCString &pacSpec);
+
+ /**
+ * Returns true if we are currently loading the PAC file.
+ */
+ bool IsLoading() { return mLoader != nullptr; }
+
+ /**
+ * Returns true if the given URI matches the URI of our PAC file or the
+ * URI it has been redirected to. In the case of a chain of redirections
+ * only the current one being followed and the original are considered
+ * becuase this information is used, respectively, to determine if we
+ * should bypass the proxy (to fetch the pac file) or if the pac
+ * configuration has changed (and we should reload the pac file)
+ */
+ bool IsPACURI(const nsACString &spec)
+ {
+ return mPACURISpec.Equals(spec) || mPACURIRedirectSpec.Equals(spec) ||
+ mNormalPACURISpec.Equals(spec);
+ }
+
+ bool IsPACURI(nsIURI *uri) {
+ if (mPACURISpec.IsEmpty() && mPACURIRedirectSpec.IsEmpty()) {
+ return false;
+ }
+
+ nsAutoCString tmp;
+ nsresult rv = uri->GetSpec(tmp);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return IsPACURI(tmp);
+ }
+
+ nsresult Init(nsISystemProxySettings *);
+ static nsPACMan *sInstance;
+
+ // PAC thread operations only
+ void ProcessPendingQ();
+ void CancelPendingQ(nsresult);
+
+private:
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ friend class PendingPACQuery;
+ friend class PACLoadComplete;
+ friend class ExecutePACThreadAction;
+ friend class WaitForThreadShutdown;
+
+ ~nsPACMan();
+
+ /**
+ * Cancel any existing load if any.
+ */
+ void CancelExistingLoad();
+
+ /**
+ * Start loading the PAC file.
+ */
+ void StartLoading();
+
+ /**
+ * Reload the PAC file if there is reason to.
+ */
+ void MaybeReloadPAC();
+
+ /**
+ * Called when we fail to load the PAC file.
+ */
+ void OnLoadFailure();
+
+ /**
+ * PostQuery() only runs on the PAC thread and it is used to
+ * place a pendingPACQuery into the queue and potentially
+ * execute the queue if it was otherwise empty
+ */
+ nsresult PostQuery(PendingPACQuery *query);
+
+ // PAC thread operations only
+ void PostProcessPendingQ();
+ void PostCancelPendingQ(nsresult);
+ bool ProcessPending();
+ void NamePACThread();
+
+private:
+ ProxyAutoConfig mPAC;
+ nsCOMPtr<nsIThread> mPACThread;
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ LinkedList<PendingPACQuery> mPendingQ; /* pac thread only */
+
+ // These specs are not nsIURI so that they can be used off the main thread.
+ // The non-normalized versions are directly from the configuration, the
+ // normalized version has been extracted from an nsIURI
+ nsCString mPACURISpec;
+ nsCString mPACURIRedirectSpec;
+ nsCString mNormalPACURISpec;
+
+ nsCOMPtr<nsIStreamLoader> mLoader;
+ bool mLoadPending;
+ Atomic<bool, Relaxed> mShutdown;
+ TimeStamp mScheduledReload;
+ uint32_t mLoadFailureCount;
+
+ bool mInProgress;
+ bool mIncludePath;
+};
+
+extern LazyLogModule gProxyLog;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsPACMan_h__
diff --git a/netwerk/base/nsPILoadGroupInternal.idl b/netwerk/base/nsPILoadGroupInternal.idl
new file mode 100644
index 0000000000..6b12304a4f
--- /dev/null
+++ b/netwerk/base/nsPILoadGroupInternal.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * Dumping ground for load group experimental work.
+ * This interface will never be frozen. If you are
+ * using any feature exposed by this interface, be aware that this interface
+ * will change and you will be broken. You have been warned.
+ */
+[scriptable, uuid(6ef2f8ac-9584-48f3-957a-0c94fff0c8c7)]
+interface nsPILoadGroupInternal : nsISupports
+{
+
+ /**
+ * Called when the load group has loaded main page and
+ * subresources. (i.e.essentially DOMComplete)
+ *
+ * @param aDefaultChanel
+ * The request channel for the base apge
+ */
+ void OnEndPageLoad(in nsIChannel aDefaultChannel);
+};
diff --git a/netwerk/base/nsPISocketTransportService.idl b/netwerk/base/nsPISocketTransportService.idl
new file mode 100644
index 0000000000..d49745fac2
--- /dev/null
+++ b/netwerk/base/nsPISocketTransportService.idl
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISocketTransportService.idl"
+
+/**
+ * This is a private interface used by the internals of the networking library.
+ * It will never be frozen. Do not use it in external code.
+ */
+[scriptable, uuid(18f73bf1-b35b-4b7b-aa9a-11bcbdbc389c)]
+
+interface nsPISocketTransportService : nsIRoutedSocketTransportService
+{
+ /**
+ * init/shutdown routines.
+ */
+ void init();
+ void shutdown(in bool aXpcomShutdown);
+
+ /**
+ * controls the TCP sender window clamp
+ */
+ readonly attribute long sendBufferSize;
+
+ /**
+ * Controls whether the socket transport service is offline.
+ * Setting it offline will cause non-local socket detachment.
+ */
+ attribute boolean offline;
+
+ /**
+ * Controls the default timeout (in seconds) for sending keepalive probes.
+ */
+ readonly attribute long keepaliveIdleTime;
+
+ /**
+ * Controls the default interval (in seconds) between retrying keepalive probes.
+ */
+ readonly attribute long keepaliveRetryInterval;
+
+ /**
+ * Controls the default retransmission count for keepalive probes.
+ */
+ readonly attribute long keepaliveProbeCount;
+};
+
+%{C++
+/*
+ * Network activity indicator: we send out these topics no more than every
+ * blipIntervalMilliseconds (as set by the
+ * "network.activity.blipIntervalMilliseconds" preference: if 0 no notifications
+ * are sent) if the network is currently active (i.e. we're sending/receiving
+ * data to/from the socket).
+ */
+#define NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC "network-activity-blip-upload"
+#define NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC "network-activity-blip-download"
+
+%}
diff --git a/netwerk/base/nsPreloadedStream.cpp b/netwerk/base/nsPreloadedStream.cpp
new file mode 100644
index 0000000000..d203f55052
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPreloadedStream.h"
+#include "nsIRunnable.h"
+
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsPreloadedStream,
+ nsIInputStream,
+ nsIAsyncInputStream)
+
+nsPreloadedStream::nsPreloadedStream(nsIAsyncInputStream *aStream,
+ const char *data, uint32_t datalen)
+ : mStream(aStream),
+ mOffset(0),
+ mLen(datalen)
+{
+ mBuf = (char *) moz_xmalloc(datalen);
+ memcpy(mBuf, data, datalen);
+}
+
+nsPreloadedStream::~nsPreloadedStream()
+{
+ free(mBuf);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::Close()
+{
+ mLen = 0;
+ return mStream->Close();
+}
+
+
+NS_IMETHODIMP
+nsPreloadedStream::Available(uint64_t *_retval)
+{
+ uint64_t avail = 0;
+
+ nsresult rv = mStream->Available(&avail);
+ if (NS_FAILED(rv))
+ return rv;
+ *_retval = avail + mLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::Read(char *aBuf, uint32_t aCount,
+ uint32_t *_retval)
+{
+ if (!mLen)
+ return mStream->Read(aBuf, aCount, _retval);
+
+ uint32_t toRead = std::min(mLen, aCount);
+ memcpy(aBuf, mBuf + mOffset, toRead);
+ mOffset += toRead;
+ mLen -= toRead;
+ *_retval = toRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void *aClosure, uint32_t aCount,
+ uint32_t *result)
+{
+ if (!mLen)
+ return mStream->ReadSegments(aWriter, aClosure, aCount, result);
+
+ *result = 0;
+ while (mLen > 0 && aCount > 0) {
+ uint32_t toRead = std::min(mLen, aCount);
+ uint32_t didRead = 0;
+ nsresult rv;
+
+ rv = aWriter(this, aClosure, mBuf + mOffset, *result, toRead, &didRead);
+
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ *result += didRead;
+ mOffset += didRead;
+ mLen -= didRead;
+ aCount -= didRead;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::IsNonBlocking(bool *_retval)
+{
+ return mStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::CloseWithStatus(nsresult aStatus)
+{
+ mLen = 0;
+ return mStream->CloseWithStatus(aStatus);
+}
+
+class RunOnThread : public Runnable
+{
+public:
+ RunOnThread(nsIAsyncInputStream *aStream,
+ nsIInputStreamCallback *aCallback)
+ : mStream(aStream),
+ mCallback(aCallback) {}
+
+ virtual ~RunOnThread() {}
+
+ NS_IMETHOD Run() override
+ {
+ mCallback->OnInputStreamReady(mStream);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+};
+
+NS_IMETHODIMP
+nsPreloadedStream::AsyncWait(nsIInputStreamCallback *aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget *aEventTarget)
+{
+ if (!mLen)
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount,
+ aEventTarget);
+
+ if (!aCallback)
+ return NS_OK;
+
+ if (!aEventTarget)
+ return aCallback->OnInputStreamReady(this);
+
+ nsCOMPtr<nsIRunnable> event =
+ new RunOnThread(this, aCallback);
+ return aEventTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPreloadedStream.h b/netwerk/base/nsPreloadedStream.h
new file mode 100644
index 0000000000..afdc960e75
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This class allows you to prefix an existing nsIAsyncInputStream
+ * with a preloaded block of data known at construction time by wrapping the
+ * two data sources into a new nsIAsyncInputStream. Readers of the new
+ * stream initially see the preloaded data and when that has been exhausted
+ * they automatically read from the wrapped stream.
+ *
+ * It is used by nsHttpConnection when it has over buffered while reading from
+ * the HTTP input socket and accidentally consumed data that belongs to
+ * a different protocol via the HTTP Upgrade mechanism. That over-buffered
+ * data is preloaded together with the input socket to form the new input socket
+ * given to the new protocol handler.
+*/
+
+#ifndef nsPreloadedStream_h__
+#define nsPreloadedStream_h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace net {
+
+class nsPreloadedStream final : public nsIAsyncInputStream
+{
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ nsPreloadedStream(nsIAsyncInputStream *aStream,
+ const char *data, uint32_t datalen);
+private:
+ ~nsPreloadedStream();
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+
+ char *mBuf;
+ uint32_t mOffset;
+ uint32_t mLen;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp
new file mode 100644
index 0000000000..26eca0e884
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -0,0 +1,2146 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+
+#include "nsProtocolProxyService.h"
+#include "nsProxyInfo.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIIOService.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIChannel.h"
+#include "nsICancelable.h"
+#include "nsIDNSService.h"
+#include "nsPIDNSService.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsThreadUtils.h"
+#include "nsSOCKSIOLayer.h"
+#include "nsString.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "plstr.h"
+#include "prnetdb.h"
+#include "nsPACMan.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+#include "nsISystemProxySettings.h"
+#include "nsINetworkLinkService.h"
+#include "nsIHttpChannelInternal.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Tokenizer.h"
+
+//----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+ extern const char kProxyType_HTTP[];
+ extern const char kProxyType_HTTPS[];
+ extern const char kProxyType_SOCKS[];
+ extern const char kProxyType_SOCKS4[];
+ extern const char kProxyType_SOCKS5[];
+ extern const char kProxyType_DIRECT[];
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+
+//----------------------------------------------------------------------------
+
+#define PROXY_PREF_BRANCH "network.proxy"
+#define PROXY_PREF(x) PROXY_PREF_BRANCH "." x
+
+#define WPAD_URL "http://wpad/wpad.dat"
+
+//----------------------------------------------------------------------------
+
+// This structure is intended to be allocated on the stack
+struct nsProtocolInfo {
+ nsAutoCString scheme;
+ uint32_t flags;
+ int32_t defaultPort;
+};
+
+//----------------------------------------------------------------------------
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+static nsresult
+GetProxyURI(nsIChannel *channel, nsIURI **aOut)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> proxyURI;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ rv = httpChannel->GetProxyURI(getter_AddRefs(proxyURI));
+ }
+ if (!proxyURI) {
+ rv = channel->GetURI(getter_AddRefs(proxyURI));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ proxyURI.forget(aOut);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+// The nsPACManCallback portion of this implementation should be run
+// on the main thread - so call nsPACMan::AsyncGetProxyForURI() with
+// a true mainThreadResponse parameter.
+class nsAsyncResolveRequest final : public nsIRunnable
+ , public nsPACManCallback
+ , public nsICancelable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAsyncResolveRequest(nsProtocolProxyService *pps, nsIChannel *channel,
+ uint32_t aResolveFlags,
+ nsIProtocolProxyCallback *callback)
+ : mStatus(NS_OK)
+ , mDispatched(false)
+ , mResolveFlags(aResolveFlags)
+ , mPPS(pps)
+ , mXPComPPS(pps)
+ , mChannel(channel)
+ , mCallback(callback)
+ {
+ NS_ASSERTION(mCallback, "null callback");
+ }
+
+private:
+ ~nsAsyncResolveRequest()
+ {
+ if (!NS_IsMainThread()) {
+ // these xpcom pointers might need to be proxied back to the
+ // main thread to delete safely, but if this request had its
+ // callbacks called normally they will all be null and this is a nop
+
+ if (mChannel) {
+ NS_ReleaseOnMainThread(mChannel.forget());
+ }
+
+ if (mCallback) {
+ NS_ReleaseOnMainThread(mCallback.forget());
+ }
+
+ if (mProxyInfo) {
+ NS_ReleaseOnMainThread(mProxyInfo.forget());
+ }
+
+ if (mXPComPPS) {
+ NS_ReleaseOnMainThread(mXPComPPS.forget());
+ }
+ }
+ }
+
+public:
+ void SetResult(nsresult status, nsIProxyInfo *pi)
+ {
+ mStatus = status;
+ mProxyInfo = pi;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mCallback)
+ DoCallback();
+ return NS_OK;
+ }
+
+ NS_IMETHOD Cancel(nsresult reason) override
+ {
+ NS_ENSURE_ARG(NS_FAILED(reason));
+
+ // If we've already called DoCallback then, nothing more to do.
+ if (!mCallback)
+ return NS_OK;
+
+ SetResult(reason, nullptr);
+ return DispatchCallback();
+ }
+
+ nsresult DispatchCallback()
+ {
+ if (mDispatched) // Only need to dispatch once
+ return NS_OK;
+
+ nsresult rv = NS_DispatchToCurrentThread(this);
+ if (NS_FAILED(rv))
+ NS_WARNING("unable to dispatch callback event");
+ else {
+ mDispatched = true;
+ return NS_OK;
+ }
+
+ mCallback = nullptr; // break possible reference cycle
+ return rv;
+ }
+
+private:
+
+ // Called asynchronously, so we do not need to post another PLEvent
+ // before calling DoCallback.
+ void OnQueryComplete(nsresult status,
+ const nsCString &pacString,
+ const nsCString &newPACURL) override
+ {
+ // If we've already called DoCallback then, nothing more to do.
+ if (!mCallback)
+ return;
+
+ // Provided we haven't been canceled...
+ if (mStatus == NS_OK) {
+ mStatus = status;
+ mPACString = pacString;
+ mPACURL = newPACURL;
+ }
+
+ // In the cancelation case, we may still have another PLEvent in
+ // the queue that wants to call DoCallback. No need to wait for
+ // it, just run the callback now.
+ DoCallback();
+ }
+
+ void DoCallback()
+ {
+ bool pacAvailable = true;
+ if (mStatus == NS_ERROR_NOT_AVAILABLE && !mProxyInfo) {
+ // If the PAC service is not avail (e.g. failed pac load
+ // or shutdown) then we will be going direct. Make that
+ // mapping now so that any filters are still applied.
+ mPACString = NS_LITERAL_CSTRING("DIRECT;");
+ mStatus = NS_OK;
+
+ LOG(("pac not available, use DIRECT\n"));
+ pacAvailable = false;
+ }
+
+ // Generate proxy info from the PAC string if appropriate
+ if (NS_SUCCEEDED(mStatus) && !mProxyInfo && !mPACString.IsEmpty()) {
+ mPPS->ProcessPACString(mPACString, mResolveFlags,
+ getter_AddRefs(mProxyInfo));
+ nsCOMPtr<nsIURI> proxyURI;
+ GetProxyURI(mChannel, getter_AddRefs(proxyURI));
+
+ // Now apply proxy filters
+ nsProtocolInfo info;
+ mStatus = mPPS->GetProtocolInfo(proxyURI, &info);
+ if (NS_SUCCEEDED(mStatus))
+ mPPS->ApplyFilters(mChannel, info, mProxyInfo);
+ else
+ mProxyInfo = nullptr;
+
+ if(pacAvailable) {
+ // if !pacAvailable, it was already logged above
+ LOG(("pac thread callback %s\n", mPACString.get()));
+ }
+ if (NS_SUCCEEDED(mStatus))
+ mPPS->MaybeDisableDNSPrefetch(mProxyInfo);
+ mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus);
+ }
+ else if (NS_SUCCEEDED(mStatus) && !mPACURL.IsEmpty()) {
+ LOG(("pac thread callback indicates new pac file load\n"));
+
+ nsCOMPtr<nsIURI> proxyURI;
+ GetProxyURI(mChannel, getter_AddRefs(proxyURI));
+
+ // trigger load of new pac url
+ nsresult rv = mPPS->ConfigureFromPAC(mPACURL, false);
+ if (NS_SUCCEEDED(rv)) {
+ // now that the load is triggered, we can resubmit the query
+ RefPtr<nsAsyncResolveRequest> newRequest =
+ new nsAsyncResolveRequest(mPPS, mChannel, mResolveFlags,
+ mCallback);
+ rv = mPPS->mPACMan->AsyncGetProxyForURI(proxyURI,
+ newRequest,
+ true);
+ }
+
+ if (NS_FAILED(rv))
+ mCallback->OnProxyAvailable(this, mChannel, nullptr, rv);
+
+ // do not call onproxyavailable() in SUCCESS case - the newRequest will
+ // take care of that
+ }
+ else {
+ LOG(("pac thread callback did not provide information %X\n", mStatus));
+ if (NS_SUCCEEDED(mStatus))
+ mPPS->MaybeDisableDNSPrefetch(mProxyInfo);
+ mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus);
+ }
+
+ // We are on the main thread now and don't need these any more so
+ // release them to avoid having to proxy them back to the main thread
+ // in the dtor
+ mCallback = nullptr; // in case the callback holds an owning ref to us
+ mPPS = nullptr;
+ mXPComPPS = nullptr;
+ mChannel = nullptr;
+ mProxyInfo = nullptr;
+ }
+
+private:
+
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+ bool mDispatched;
+ uint32_t mResolveFlags;
+
+ nsProtocolProxyService *mPPS;
+ nsCOMPtr<nsIProtocolProxyService> mXPComPPS;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIProtocolProxyCallback> mCallback;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+};
+
+NS_IMPL_ISUPPORTS(nsAsyncResolveRequest, nsICancelable, nsIRunnable)
+
+//----------------------------------------------------------------------------
+
+#define IS_ASCII_SPACE(_c) ((_c) == ' ' || (_c) == '\t')
+
+//
+// apply mask to address (zeros out excluded bits).
+//
+// NOTE: we do the byte swapping here to minimize overall swapping.
+//
+static void
+proxy_MaskIPv6Addr(PRIPv6Addr &addr, uint16_t mask_len)
+{
+ if (mask_len == 128)
+ return;
+
+ if (mask_len > 96) {
+ addr.pr_s6_addr32[3] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[3]) & (~0L << (128 - mask_len)));
+ }
+ else if (mask_len > 64) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[2]) & (~0L << (96 - mask_len)));
+ }
+ else if (mask_len > 32) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[1]) & (~0L << (64 - mask_len)));
+ }
+ else {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = 0;
+ addr.pr_s6_addr32[0] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[0]) & (~0L << (32 - mask_len)));
+ }
+}
+
+static void
+proxy_GetStringPref(nsIPrefBranch *aPrefBranch,
+ const char *aPref,
+ nsCString &aResult)
+{
+ nsXPIDLCString temp;
+ nsresult rv = aPrefBranch->GetCharPref(aPref, getter_Copies(temp));
+ if (NS_FAILED(rv))
+ aResult.Truncate();
+ else {
+ aResult.Assign(temp);
+ // all of our string prefs are hostnames, so we should remove any
+ // whitespace characters that the user might have unknowingly entered.
+ aResult.StripWhitespace();
+ }
+}
+
+static void
+proxy_GetIntPref(nsIPrefBranch *aPrefBranch,
+ const char *aPref,
+ int32_t &aResult)
+{
+ int32_t temp;
+ nsresult rv = aPrefBranch->GetIntPref(aPref, &temp);
+ if (NS_FAILED(rv))
+ aResult = -1;
+ else
+ aResult = temp;
+}
+
+static void
+proxy_GetBoolPref(nsIPrefBranch *aPrefBranch,
+ const char *aPref,
+ bool &aResult)
+{
+ bool temp;
+ nsresult rv = aPrefBranch->GetBoolPref(aPref, &temp);
+ if (NS_FAILED(rv))
+ aResult = false;
+ else
+ aResult = temp;
+}
+
+//----------------------------------------------------------------------------
+
+static const int32_t PROXYCONFIG_DIRECT4X = 3;
+static const int32_t PROXYCONFIG_COUNT = 6;
+
+NS_IMPL_ADDREF(nsProtocolProxyService)
+NS_IMPL_RELEASE(nsProtocolProxyService)
+NS_IMPL_CLASSINFO(nsProtocolProxyService, nullptr, nsIClassInfo::SINGLETON,
+ NS_PROTOCOLPROXYSERVICE_CID)
+
+// NS_IMPL_QUERY_INTERFACE_CI with the nsProtocolProxyService QI change
+NS_INTERFACE_MAP_BEGIN(nsProtocolProxyService)
+NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService)
+NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService2)
+NS_INTERFACE_MAP_ENTRY(nsIObserver)
+if ( aIID.Equals(NS_GET_IID(nsProtocolProxyService)) ) foundInterface = static_cast<nsIProtocolProxyService2*>(this); else
+NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolProxyService)
+NS_IMPL_QUERY_CLASSINFO(nsProtocolProxyService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(nsProtocolProxyService,
+ nsIProtocolProxyService,
+ nsIProtocolProxyService2)
+
+nsProtocolProxyService::nsProtocolProxyService()
+ : mFilterLocalHosts(false)
+ , mFilters(nullptr)
+ , mProxyConfig(PROXYCONFIG_DIRECT)
+ , mHTTPProxyPort(-1)
+ , mFTPProxyPort(-1)
+ , mHTTPSProxyPort(-1)
+ , mSOCKSProxyPort(-1)
+ , mSOCKSProxyVersion(4)
+ , mSOCKSProxyRemoteDNS(false)
+ , mProxyOverTLS(true)
+ , mPACMan(nullptr)
+ , mSessionStart(PR_Now())
+ , mFailedProxyTimeout(30 * 60) // 30 minute default
+{
+}
+
+nsProtocolProxyService::~nsProtocolProxyService()
+{
+ // These should have been cleaned up in our Observe method.
+ NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters == nullptr &&
+ mPACMan == nullptr, "what happened to xpcom-shutdown?");
+}
+
+// nsProtocolProxyService methods
+nsresult
+nsProtocolProxyService::Init()
+{
+ // failure to access prefs is non-fatal
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ // monitor proxy prefs
+ prefBranch->AddObserver(PROXY_PREF_BRANCH, this, false);
+
+ // read all prefs
+ PrefsChanged(prefBranch, nullptr);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ // register for shutdown notification so we can clean ourselves up
+ // properly.
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obs->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ }
+
+ return NS_OK;
+}
+
+// ReloadNetworkPAC() checks if there's a non-networked PAC in use then avoids
+// to call ReloadPAC()
+nsresult
+nsProtocolProxyService::ReloadNetworkPAC()
+{
+ nsCOMPtr<nsIPrefBranch> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return NS_OK;
+ }
+
+ int32_t type;
+ nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ if (type == PROXYCONFIG_PAC) {
+ nsXPIDLCString pacSpec;
+ prefs->GetCharPref(PROXY_PREF("autoconfig_url"),
+ getter_Copies(pacSpec));
+ if (!pacSpec.IsEmpty()) {
+ nsCOMPtr<nsIURI> pacURI;
+ rv = NS_NewURI(getter_AddRefs(pacURI), pacSpec);
+ if(!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ nsProtocolInfo pac;
+ rv = GetProtocolInfo(pacURI, &pac);
+ if(!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ if (!pac.scheme.EqualsLiteral("file") &&
+ !pac.scheme.EqualsLiteral("data")) {
+ LOG((": received network changed event, reload PAC"));
+ ReloadPAC();
+ }
+ }
+ } else if ((type == PROXYCONFIG_WPAD) || (type == PROXYCONFIG_SYSTEM)) {
+ ReloadPAC();
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsProtocolProxyService::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ // cleanup
+ if (mHostFiltersArray.Length() > 0) {
+ mHostFiltersArray.Clear();
+ }
+ if (mFilters) {
+ delete mFilters;
+ mFilters = nullptr;
+ }
+ if (mPACMan) {
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ } else if (strcmp(aTopic, NS_NETWORK_LINK_TOPIC) == 0) {
+ nsCString converted = NS_ConvertUTF16toUTF8(aData);
+ const char *state = converted.get();
+ if (!strcmp(state, NS_NETWORK_LINK_DATA_CHANGED)) {
+ ReloadNetworkPAC();
+ }
+ }
+ else {
+ NS_ASSERTION(strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
+ "what is this random observer event?");
+ nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
+ if (prefs)
+ PrefsChanged(prefs, NS_LossyConvertUTF16toASCII(aData).get());
+ }
+ return NS_OK;
+}
+
+void
+nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch,
+ const char *pref)
+{
+ nsresult rv = NS_OK;
+ bool reloadPAC = false;
+ nsXPIDLCString tempString;
+
+ if (!pref || !strcmp(pref, PROXY_PREF("type"))) {
+ int32_t type = -1;
+ rv = prefBranch->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_SUCCEEDED(rv)) {
+ // bug 115720 - for ns4.x backwards compatibility
+ if (type == PROXYCONFIG_DIRECT4X) {
+ type = PROXYCONFIG_DIRECT;
+ // Reset the type so that the dialog looks correct, and we
+ // don't have to handle this case everywhere else
+ // I'm paranoid about a loop of some sort - only do this
+ // if we're enumerating all prefs, and ignore any error
+ if (!pref)
+ prefBranch->SetIntPref(PROXY_PREF("type"), type);
+ } else if (type >= PROXYCONFIG_COUNT) {
+ LOG(("unknown proxy type: %lu; assuming direct\n", type));
+ type = PROXYCONFIG_DIRECT;
+ }
+ mProxyConfig = type;
+ reloadPAC = true;
+ }
+
+ if (mProxyConfig == PROXYCONFIG_SYSTEM) {
+ mSystemProxySettings = do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID);
+ if (!mSystemProxySettings)
+ mProxyConfig = PROXYCONFIG_DIRECT;
+ ResetPACThread();
+ } else {
+ if (mSystemProxySettings) {
+ mSystemProxySettings = nullptr;
+ ResetPACThread();
+ }
+ }
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("http")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("http"), mHTTPProxyHost);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("http_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("http_port"), mHTTPProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ssl")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("ssl"), mHTTPSProxyHost);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ssl_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("ssl_port"), mHTTPSProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ftp")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("ftp"), mFTPProxyHost);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ftp_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("ftp_port"), mFTPProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("socks"), mSOCKSProxyTarget);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_port"), mSOCKSProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_version"))) {
+ int32_t version;
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_version"), version);
+ // make sure this preference value remains sane
+ if (version == 5)
+ mSOCKSProxyVersion = 5;
+ else
+ mSOCKSProxyVersion = 4;
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_remote_dns")))
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"),
+ mSOCKSProxyRemoteDNS);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("proxy_over_tls"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("proxy_over_tls"),
+ mProxyOverTLS);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"),
+ mFailedProxyTimeout);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("no_proxies_on"))) {
+ rv = prefBranch->GetCharPref(PROXY_PREF("no_proxies_on"),
+ getter_Copies(tempString));
+ if (NS_SUCCEEDED(rv))
+ LoadHostFilters(tempString);
+ }
+
+ // We're done if not using something that could give us a PAC URL
+ // (PAC, WPAD or System)
+ if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
+ mProxyConfig != PROXYCONFIG_SYSTEM)
+ return;
+
+ // OK, we need to reload the PAC file if:
+ // 1) network.proxy.type changed, or
+ // 2) network.proxy.autoconfig_url changed and PAC is configured
+
+ if (!pref || !strcmp(pref, PROXY_PREF("autoconfig_url")))
+ reloadPAC = true;
+
+ if (reloadPAC) {
+ tempString.Truncate();
+ if (mProxyConfig == PROXYCONFIG_PAC) {
+ prefBranch->GetCharPref(PROXY_PREF("autoconfig_url"),
+ getter_Copies(tempString));
+ if (mPACMan && !mPACMan->IsPACURI(tempString)) {
+ LOG(("PAC Thread URI Changed - Reset Pac Thread"));
+ ResetPACThread();
+ }
+ } else if (mProxyConfig == PROXYCONFIG_WPAD) {
+ // We diverge from the WPAD spec here in that we don't walk the
+ // hosts's FQDN, stripping components until we hit a TLD. Doing so
+ // is dangerous in the face of an incomplete list of TLDs, and TLDs
+ // get added over time. We could consider doing only a single
+ // substitution of the first component, if that proves to help
+ // compatibility.
+ tempString.AssignLiteral(WPAD_URL);
+ } else if (mSystemProxySettings) {
+ // Get System Proxy settings if available
+ mSystemProxySettings->GetPACURI(tempString);
+ }
+ if (!tempString.IsEmpty())
+ ConfigureFromPAC(tempString, false);
+ }
+}
+
+bool
+nsProtocolProxyService::CanUseProxy(nsIURI *aURI, int32_t defaultPort)
+{
+ if (mHostFiltersArray.Length() == 0)
+ return true;
+
+ int32_t port;
+ nsAutoCString host;
+
+ nsresult rv = aURI->GetAsciiHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty())
+ return false;
+
+ rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv))
+ return false;
+ if (port == -1)
+ port = defaultPort;
+
+ PRNetAddr addr;
+ bool is_ipaddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS);
+
+ PRIPv6Addr ipv6;
+ if (is_ipaddr) {
+ // convert parsed address to IPv6
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &ipv6);
+ }
+ else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(&ipv6, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ }
+ else {
+ NS_WARNING("unknown address family");
+ return true; // allow proxying
+ }
+ }
+
+ // Don't use proxy for local hosts (plain hostname, no dots)
+ if ((!is_ipaddr && mFilterLocalHosts && !host.Contains('.')) ||
+ host.EqualsLiteral("127.0.0.1") ||
+ host.EqualsLiteral("::1")) {
+ LOG(("Not using proxy for this local host [%s]!\n", host.get()));
+ return false; // don't allow proxying
+ }
+
+ int32_t index = -1;
+ while (++index < int32_t(mHostFiltersArray.Length())) {
+ HostInfo *hinfo = mHostFiltersArray[index];
+
+ if (is_ipaddr != hinfo->is_ipaddr)
+ continue;
+ if (hinfo->port && hinfo->port != port)
+ continue;
+
+ if (is_ipaddr) {
+ // generate masked version of target IPv6 address
+ PRIPv6Addr masked;
+ memcpy(&masked, &ipv6, sizeof(PRIPv6Addr));
+ proxy_MaskIPv6Addr(masked, hinfo->ip.mask_len);
+
+ // check for a match
+ if (memcmp(&masked, &hinfo->ip.addr, sizeof(PRIPv6Addr)) == 0)
+ return false; // proxy disallowed
+ }
+ else {
+ uint32_t host_len = host.Length();
+ uint32_t filter_host_len = hinfo->name.host_len;
+
+ if (host_len >= filter_host_len) {
+ //
+ // compare last |filter_host_len| bytes of target hostname.
+ //
+ const char *host_tail = host.get() + host_len - filter_host_len;
+ if (!PL_strncasecmp(host_tail, hinfo->name.host, filter_host_len)) {
+ // If the tail of the host string matches the filter
+
+ if (filter_host_len > 0 && hinfo->name.host[0] == '.') {
+ // If the filter was of the form .foo.bar.tld, all such
+ // matches are correct
+ return false; // proxy disallowed
+ }
+
+ // abc-def.example.org should not match def.example.org
+ // however, *.def.example.org should match .def.example.org
+ // We check that the filter doesn't start with a `.`. If it does,
+ // then the strncasecmp above should suffice. If it doesn't,
+ // then we should only consider it a match if the strncasecmp happened
+ // at a subdomain boundary
+ if (host_len > filter_host_len && *(host_tail - 1) == '.') {
+ // If the host was something.foo.bar.tld and the filter
+ // was foo.bar.tld, it's still a match.
+ // the character right before the tail must be a
+ // `.` for this to work
+ return false; // proxy disallowed
+ }
+
+ if (host_len == filter_host_len) {
+ // If the host and filter are of the same length,
+ // they should match
+ return false; // proxy disallowed
+ }
+ }
+
+ }
+ }
+ }
+ return true;
+}
+
+// kProxyType\* may be referred to externally in
+// nsProxyInfo in order to compare by string pointer
+const char kProxyType_HTTP[] = "http";
+const char kProxyType_HTTPS[] = "https";
+const char kProxyType_PROXY[] = "proxy";
+const char kProxyType_SOCKS[] = "socks";
+const char kProxyType_SOCKS4[] = "socks4";
+const char kProxyType_SOCKS5[] = "socks5";
+const char kProxyType_DIRECT[] = "direct";
+
+const char *
+nsProtocolProxyService::ExtractProxyInfo(const char *start,
+ uint32_t aResolveFlags,
+ nsProxyInfo **result)
+{
+ *result = nullptr;
+ uint32_t flags = 0;
+
+ // see BNF in ProxyAutoConfig.h and notes in nsISystemProxySettings.idl
+
+ // find end of proxy info delimiter
+ const char *end = start;
+ while (*end && *end != ';') ++end;
+
+ // find end of proxy type delimiter
+ const char *sp = start;
+ while (sp < end && *sp != ' ' && *sp != '\t') ++sp;
+
+ uint32_t len = sp - start;
+ const char *type = nullptr;
+ switch (len) {
+ case 4:
+ if (PL_strncasecmp(start, kProxyType_HTTP, 5) == 0) {
+ type = kProxyType_HTTP;
+ }
+ break;
+ case 5:
+ if (PL_strncasecmp(start, kProxyType_PROXY, 5) == 0) {
+ type = kProxyType_HTTP;
+ } else if (PL_strncasecmp(start, kProxyType_SOCKS, 5) == 0) {
+ type = kProxyType_SOCKS4; // assume v4 for 4x compat
+ } else if (PL_strncasecmp(start, kProxyType_HTTPS, 5) == 0) {
+ type = kProxyType_HTTPS;
+ }
+ break;
+ case 6:
+ if (PL_strncasecmp(start, kProxyType_DIRECT, 6) == 0)
+ type = kProxyType_DIRECT;
+ else if (PL_strncasecmp(start, kProxyType_SOCKS4, 6) == 0)
+ type = kProxyType_SOCKS4;
+ else if (PL_strncasecmp(start, kProxyType_SOCKS5, 6) == 0)
+ // map "SOCKS5" to "socks" to match contract-id of registered
+ // SOCKS-v5 socket provider.
+ type = kProxyType_SOCKS;
+ break;
+ }
+ if (type) {
+ const char *host = nullptr, *hostEnd = nullptr;
+ int32_t port = -1;
+
+ // If it's a SOCKS5 proxy, do name resolution on the server side.
+ // We could use this with SOCKS4a servers too, but they might not
+ // support it.
+ if (type == kProxyType_SOCKS || mSOCKSProxyRemoteDNS)
+ flags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+
+ // extract host:port
+ start = sp;
+ while ((*start == ' ' || *start == '\t') && start < end)
+ start++;
+
+ // port defaults
+ if (type == kProxyType_HTTP) {
+ port = 80;
+ } else if (type == kProxyType_HTTPS) {
+ port = 443;
+ } else {
+ port = 1080;
+ }
+
+ nsProxyInfo *pi = new nsProxyInfo();
+ pi->mType = type;
+ pi->mFlags = flags;
+ pi->mResolveFlags = aResolveFlags;
+ pi->mTimeout = mFailedProxyTimeout;
+
+ // www.foo.com:8080 and http://www.foo.com:8080
+ nsDependentCSubstring maybeURL(start, end - start);
+ nsCOMPtr<nsIURI> pacURI;
+
+ nsAutoCString urlHost;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pacURI), maybeURL)) &&
+ NS_SUCCEEDED(pacURI->GetAsciiHost(urlHost)) &&
+ !urlHost.IsEmpty()) {
+ // http://www.example.com:8080
+
+ pi->mHost = urlHost;
+
+ int32_t tPort;
+ if (NS_SUCCEEDED(pacURI->GetPort(&tPort)) && tPort != -1) {
+ port = tPort;
+ }
+ pi->mPort = port;
+ }
+ else {
+ // www.example.com:8080
+ if (start < end) {
+ host = start;
+ hostEnd = strchr(host, ':');
+ if (!hostEnd || hostEnd > end) {
+ hostEnd = end;
+ // no port, so assume default
+ }
+ else {
+ port = atoi(hostEnd + 1);
+ }
+ }
+ // YES, it is ok to specify a null proxy host.
+ if (host) {
+ pi->mHost.Assign(host, hostEnd - host);
+ pi->mPort = port;
+ }
+ }
+ NS_ADDREF(*result = pi);
+ }
+
+ while (*end == ';' || *end == ' ' || *end == '\t')
+ ++end;
+ return end;
+}
+
+void
+nsProtocolProxyService::GetProxyKey(nsProxyInfo *pi, nsCString &key)
+{
+ key.AssignASCII(pi->mType);
+ if (!pi->mHost.IsEmpty()) {
+ key.Append(' ');
+ key.Append(pi->mHost);
+ key.Append(':');
+ key.AppendInt(pi->mPort);
+ }
+}
+
+uint32_t
+nsProtocolProxyService::SecondsSinceSessionStart()
+{
+ PRTime now = PR_Now();
+
+ // get time elapsed since session start
+ int64_t diff = now - mSessionStart;
+
+ // convert microseconds to seconds
+ diff /= PR_USEC_PER_SEC;
+
+ // return converted 32 bit value
+ return uint32_t(diff);
+}
+
+void
+nsProtocolProxyService::EnableProxy(nsProxyInfo *pi)
+{
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+ mFailedProxies.Remove(key);
+}
+
+void
+nsProtocolProxyService::DisableProxy(nsProxyInfo *pi)
+{
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // Add timeout to interval (this is the time when the proxy can
+ // be tried again).
+ dsec += pi->mTimeout;
+
+ // NOTE: The classic codebase would increase the timeout value
+ // incrementally each time a subsequent failure occurred.
+ // We could do the same, but it would require that we not
+ // remove proxy entries in IsProxyDisabled or otherwise
+ // change the way we are recording disabled proxies.
+ // Simpler is probably better for now, and at least the
+ // user can tune the timeout setting via preferences.
+
+ LOG(("DisableProxy %s %d\n", key.get(), dsec));
+
+ // If this fails, oh well... means we don't have enough memory
+ // to remember the failed proxy.
+ mFailedProxies.Put(key, dsec);
+}
+
+bool
+nsProtocolProxyService::IsProxyDisabled(nsProxyInfo *pi)
+{
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t val;
+ if (!mFailedProxies.Get(key, &val))
+ return false;
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // if time passed has exceeded interval, then try proxy again.
+ if (dsec > val) {
+ mFailedProxies.Remove(key);
+ return false;
+ }
+
+ return true;
+}
+
+nsresult
+nsProtocolProxyService::SetupPACThread()
+{
+ if (mPACMan)
+ return NS_OK;
+
+ mPACMan = new nsPACMan();
+
+ bool mainThreadOnly;
+ nsresult rv;
+ if (mSystemProxySettings &&
+ NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
+ !mainThreadOnly) {
+ rv = mPACMan->Init(mSystemProxySettings);
+ }
+ else {
+ rv = mPACMan->Init(nullptr);
+ }
+
+ if (NS_FAILED(rv))
+ mPACMan = nullptr;
+ return rv;
+}
+
+nsresult
+nsProtocolProxyService::ResetPACThread()
+{
+ if (!mPACMan)
+ return NS_OK;
+
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ return SetupPACThread();
+}
+
+nsresult
+nsProtocolProxyService::ConfigureFromPAC(const nsCString &spec,
+ bool forceReload)
+{
+ SetupPACThread();
+
+ if (mPACMan->IsPACURI(spec) && !forceReload)
+ return NS_OK;
+
+ mFailedProxies.Clear();
+
+ return mPACMan->LoadPACFromURI(spec);
+}
+
+void
+nsProtocolProxyService::ProcessPACString(const nsCString &pacString,
+ uint32_t aResolveFlags,
+ nsIProxyInfo **result)
+{
+ if (pacString.IsEmpty()) {
+ *result = nullptr;
+ return;
+ }
+
+ const char *proxies = pacString.get();
+
+ nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr;
+ while (*proxies) {
+ proxies = ExtractProxyInfo(proxies, aResolveFlags, &pi);
+ if (pi && (pi->mType == kProxyType_HTTPS) && !mProxyOverTLS) {
+ delete pi;
+ pi = nullptr;
+ }
+
+ if (pi) {
+ if (last) {
+ NS_ASSERTION(last->mNext == nullptr, "leaking nsProxyInfo");
+ last->mNext = pi;
+ }
+ else
+ first = pi;
+ last = pi;
+ }
+ }
+ *result = first;
+}
+
+// nsIProtocolProxyService2
+NS_IMETHODIMP
+nsProtocolProxyService::ReloadPAC()
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return NS_OK;
+
+ int32_t type;
+ nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsXPIDLCString pacSpec;
+ if (type == PROXYCONFIG_PAC)
+ prefs->GetCharPref(PROXY_PREF("autoconfig_url"), getter_Copies(pacSpec));
+ else if (type == PROXYCONFIG_WPAD)
+ pacSpec.AssignLiteral(WPAD_URL);
+
+ if (!pacSpec.IsEmpty())
+ ConfigureFromPAC(pacSpec, true);
+ return NS_OK;
+}
+
+// When sync interface is removed this can go away too
+// The nsPACManCallback portion of this implementation should be run
+// off the main thread, because it uses a condvar for signaling and
+// the main thread is blocking on that condvar -
+// so call nsPACMan::AsyncGetProxyForURI() with
+// a false mainThreadResponse parameter.
+class nsAsyncBridgeRequest final : public nsPACManCallback
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAsyncBridgeRequest()
+ : mMutex("nsDeprecatedCallback")
+ , mCondVar(mMutex, "nsDeprecatedCallback")
+ , mStatus(NS_OK)
+ , mCompleted(false)
+ {
+ }
+
+ void OnQueryComplete(nsresult status,
+ const nsCString &pacString,
+ const nsCString &newPACURL) override
+ {
+ MutexAutoLock lock(mMutex);
+ mCompleted = true;
+ mStatus = status;
+ mPACString = pacString;
+ mPACURL = newPACURL;
+ mCondVar.Notify();
+ }
+
+ void Lock() { mMutex.Lock(); }
+ void Unlock() { mMutex.Unlock(); }
+ void Wait() { mCondVar.Wait(PR_SecondsToInterval(3)); }
+
+private:
+ ~nsAsyncBridgeRequest()
+ {
+ }
+
+ friend class nsProtocolProxyService;
+
+ Mutex mMutex;
+ CondVar mCondVar;
+
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+ bool mCompleted;
+};
+NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest)
+
+// nsProtocolProxyService
+nsresult
+nsProtocolProxyService::DeprecatedBlockingResolve(nsIChannel *aChannel,
+ uint32_t aFlags,
+ nsIProxyInfo **retval)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetProxyURI(aChannel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ nsProtocolInfo info;
+ rv = GetProtocolInfo(uri, &info);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ bool usePACThread;
+
+ // SystemProxySettings and PAC files can block the main thread
+ // but if neither of them are in use, we can just do the work
+ // right here and directly invoke the callback
+
+ rv = Resolve_Internal(aChannel, info, aFlags,
+ &usePACThread, getter_AddRefs(pi));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!usePACThread || !mPACMan) {
+ ApplyFilters(aChannel, info, pi);
+ pi.forget(retval);
+ return NS_OK;
+ }
+
+ // Use the PAC thread to do the work, so we don't have to reimplement that
+ // code, but block this thread on that completion.
+ RefPtr<nsAsyncBridgeRequest> ctx = new nsAsyncBridgeRequest();
+ ctx->Lock();
+ if (NS_SUCCEEDED(mPACMan->AsyncGetProxyForURI(uri, ctx, false))) {
+ // this can really block the main thread, so cap it at 3 seconds
+ ctx->Wait();
+ }
+ ctx->Unlock();
+ if (!ctx->mCompleted)
+ return NS_ERROR_FAILURE;
+ if (NS_FAILED(ctx->mStatus))
+ return ctx->mStatus;
+
+ // pretty much duplicate real DoCallback logic
+
+ // Generate proxy info from the PAC string if appropriate
+ if (!ctx->mPACString.IsEmpty()) {
+ LOG(("sync pac thread callback %s\n", ctx->mPACString.get()));
+ ProcessPACString(ctx->mPACString, 0, getter_AddRefs(pi));
+ ApplyFilters(aChannel, info, pi);
+ pi.forget(retval);
+ return NS_OK;
+ }
+
+ if (!ctx->mPACURL.IsEmpty()) {
+ NS_WARNING("sync pac thread callback indicates new pac file load\n");
+ // This is a problem and is one of the reasons this blocking interface
+ // is deprecated. The main loop needs to spin to make this reload happen. So
+ // we are going to kick off the reload and return an error - it will work
+ // next time. Because this sync interface is only used in the java plugin it
+ // is extremely likely that the pac file has already been loaded anyhow.
+
+ rv = ConfigureFromPAC(ctx->mPACURL, false);
+ if (NS_FAILED(rv))
+ return rv;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *retval = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsProtocolProxyService::AsyncResolveInternal(nsIChannel *channel, uint32_t flags,
+ nsIProtocolProxyCallback *callback,
+ nsICancelable **result,
+ bool isSyncOK)
+{
+ NS_ENSURE_ARG_POINTER(channel);
+ NS_ENSURE_ARG_POINTER(callback);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ *result = nullptr;
+ RefPtr<nsAsyncResolveRequest> ctx =
+ new nsAsyncResolveRequest(this, channel, flags, callback);
+
+ nsProtocolInfo info;
+ rv = GetProtocolInfo(uri, &info);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ bool usePACThread;
+
+ // SystemProxySettings and PAC files can block the main thread
+ // but if neither of them are in use, we can just do the work
+ // right here and directly invoke the callback
+
+ rv = Resolve_Internal(channel, info, flags,
+ &usePACThread, getter_AddRefs(pi));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!usePACThread || !mPACMan) {
+ // we can do it locally
+ ApplyFilters(channel, info, pi);
+ ctx->SetResult(NS_OK, pi);
+ if (isSyncOK) {
+ ctx->Run();
+ return NS_OK;
+ }
+
+ rv = ctx->DispatchCallback();
+ if (NS_SUCCEEDED(rv))
+ ctx.forget(result);
+ return rv;
+ }
+
+ // else kick off a PAC thread query
+
+ rv = mPACMan->AsyncGetProxyForURI(uri, ctx, true);
+ if (NS_SUCCEEDED(rv))
+ ctx.forget(result);
+ return rv;
+}
+
+// nsIProtocolProxyService
+NS_IMETHODIMP
+nsProtocolProxyService::AsyncResolve2(nsIChannel *channel, uint32_t flags,
+ nsIProtocolProxyCallback *callback,
+ nsICancelable **result)
+{
+ return AsyncResolveInternal(channel, flags, callback, result, true);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::AsyncResolve(nsISupports *channelOrURI, uint32_t flags,
+ nsIProtocolProxyCallback *callback,
+ nsICancelable **result)
+{
+
+ nsresult rv;
+ // Check if we got a channel:
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(channelOrURI);
+ if (!channel) {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(channelOrURI);
+ if (!uri) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan(
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // creating a temporary channel from the URI which is not
+ // used to perform any network loads, hence its safe to
+ // use systemPrincipal as the loadingPrincipal.
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return AsyncResolveInternal(channel, flags, callback, result, false);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NewProxyInfo(const nsACString &aType,
+ const nsACString &aHost,
+ int32_t aPort,
+ uint32_t aFlags,
+ uint32_t aFailoverTimeout,
+ nsIProxyInfo *aFailoverProxy,
+ nsIProxyInfo **aResult)
+{
+ return NewProxyInfoWithAuth(aType, aHost, aPort,
+ EmptyCString(), EmptyCString(),
+ aFlags, aFailoverTimeout,
+ aFailoverProxy, aResult);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NewProxyInfoWithAuth(const nsACString &aType,
+ const nsACString &aHost,
+ int32_t aPort,
+ const nsACString &aUsername,
+ const nsACString &aPassword,
+ uint32_t aFlags,
+ uint32_t aFailoverTimeout,
+ nsIProxyInfo *aFailoverProxy,
+ nsIProxyInfo **aResult)
+{
+ static const char *types[] = {
+ kProxyType_HTTP,
+ kProxyType_HTTPS,
+ kProxyType_SOCKS,
+ kProxyType_SOCKS4,
+ kProxyType_DIRECT
+ };
+
+ // resolve type; this allows us to avoid copying the type string into each
+ // proxy info instance. we just reference the string literals directly :)
+ const char *type = nullptr;
+ for (uint32_t i = 0; i < ArrayLength(types); ++i) {
+ if (aType.LowerCaseEqualsASCII(types[i])) {
+ type = types[i];
+ break;
+ }
+ }
+ NS_ENSURE_TRUE(type, NS_ERROR_INVALID_ARG);
+
+ // We have only implemented username/password for SOCKS proxies.
+ if ((!aUsername.IsEmpty() || !aPassword.IsEmpty()) &&
+ !aType.LowerCaseEqualsASCII(kProxyType_SOCKS) &&
+ !aType.LowerCaseEqualsASCII(kProxyType_SOCKS4)) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NewProxyInfo_Internal(type, aHost, aPort,
+ aUsername, aPassword,
+ aFlags, aFailoverTimeout,
+ aFailoverProxy, 0, aResult);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo *aProxy,
+ nsIURI *aURI,
+ nsresult aStatus,
+ nsIProxyInfo **aResult)
+{
+ // We only support failover when a PAC file is configured, either
+ // directly or via system settings
+ if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
+ mProxyConfig != PROXYCONFIG_SYSTEM)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Verify that |aProxy| is one of our nsProxyInfo objects.
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ NS_ENSURE_ARG(pi);
+ // OK, the QI checked out. We can proceed.
+
+ // Remember that this proxy is down.
+ DisableProxy(pi);
+
+ // NOTE: At this point, we might want to prompt the user if we have
+ // not already tried going DIRECT. This is something that the
+ // classic codebase supported; however, IE6 does not prompt.
+
+ if (!pi->mNext)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ LOG(("PAC failover from %s %s:%d to %s %s:%d\n",
+ pi->mType, pi->mHost.get(), pi->mPort,
+ pi->mNext->mType, pi->mNext->mHost.get(), pi->mNext->mPort));
+
+ NS_ADDREF(*aResult = pi->mNext);
+ return NS_OK;
+}
+
+nsresult
+nsProtocolProxyService::InsertFilterLink(FilterLink *link, uint32_t position)
+{
+ if (!mFilters) {
+ mFilters = link;
+ return NS_OK;
+ }
+
+ // insert into mFilters in sorted order
+ FilterLink *last = nullptr;
+ for (FilterLink *iter = mFilters; iter; iter = iter->next) {
+ if (position < iter->position) {
+ if (last) {
+ link->next = last->next;
+ last->next = link;
+ }
+ else {
+ link->next = mFilters;
+ mFilters = link;
+ }
+ return NS_OK;
+ }
+ last = iter;
+ }
+ // our position is equal to or greater than the last link in the list
+ last->next = link;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RegisterFilter(nsIProtocolProxyFilter *filter,
+ uint32_t position)
+{
+ UnregisterFilter(filter); // remove this filter if we already have it
+
+ FilterLink *link = new FilterLink(position, filter);
+ if (!link) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return InsertFilterLink(link, position);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RegisterChannelFilter(nsIProtocolProxyChannelFilter *channelFilter,
+ uint32_t position)
+{
+ UnregisterChannelFilter(channelFilter); // remove this filter if we already have it
+
+ FilterLink *link = new FilterLink(position, channelFilter);
+ if (!link) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return InsertFilterLink(link, position);
+}
+
+nsresult
+nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject)
+{
+ FilterLink *last = nullptr;
+ for (FilterLink *iter = mFilters; iter; iter = iter->next) {
+ nsCOMPtr<nsISupports> object = do_QueryInterface(iter->filter);
+ nsCOMPtr<nsISupports> object2 = do_QueryInterface(iter->channelFilter);
+ if (object == givenObject || object2 == givenObject) {
+ if (last)
+ last->next = iter->next;
+ else
+ mFilters = iter->next;
+ iter->next = nullptr;
+ delete iter;
+ return NS_OK;
+ }
+ last = iter;
+ }
+
+ // No need to throw an exception in this case.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::UnregisterFilter(nsIProtocolProxyFilter *filter) {
+ // QI to nsISupports so we can safely test object identity.
+ nsCOMPtr<nsISupports> givenObject = do_QueryInterface(filter);
+ return RemoveFilterLink(givenObject);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::UnregisterChannelFilter(nsIProtocolProxyChannelFilter *channelFilter) {
+ // QI to nsISupports so we can safely test object identity.
+ nsCOMPtr<nsISupports> givenObject = do_QueryInterface(channelFilter);
+ return RemoveFilterLink(givenObject);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetProxyConfigType(uint32_t* aProxyConfigType)
+{
+ *aProxyConfigType = mProxyConfig;
+ return NS_OK;
+}
+
+void
+nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters)
+{
+ // check to see the owners flag? /!?/ TODO
+ if (mHostFiltersArray.Length() > 0) {
+ mHostFiltersArray.Clear();
+ }
+
+ if (aFilters.IsEmpty()) {
+ return;
+ }
+
+ //
+ // filter = ( host | domain | ipaddr ["/" mask] ) [":" port]
+ // filters = filter *( "," LWS filter)
+ //
+ // Reset mFilterLocalHosts - will be set to true if "<local>" is in pref string
+ mFilterLocalHosts = false;
+
+ mozilla::Tokenizer t(aFilters);
+ mozilla::Tokenizer::Token token;
+ bool eof = false;
+ // while (*filters) {
+ while (!eof) {
+ // skip over spaces and ,
+ t.SkipWhites();
+ while (t.CheckChar(',')) {
+ t.SkipWhites();
+ }
+
+ nsAutoCString portStr;
+ nsAutoCString hostStr;
+ nsAutoCString maskStr;
+ t.Record();
+
+ bool parsingIPv6 = false;
+ bool parsingPort = false;
+ bool parsingMask = false;
+ while (t.Next(token)) {
+ if (token.Equals(mozilla::Tokenizer::Token::EndOfFile())) {
+ eof = true;
+ break;
+ }
+ if (token.Equals(mozilla::Tokenizer::Token::Char(',')) ||
+ token.Type() == mozilla::Tokenizer::TOKEN_WS) {
+ break;
+ }
+
+ if (token.Equals(mozilla::Tokenizer::Token::Char('['))) {
+ parsingIPv6 = true;
+ continue;
+ }
+
+ if (!parsingIPv6 && token.Equals(mozilla::Tokenizer::Token::Char(':'))) {
+ // Port is starting. Claim the previous as host.
+ if (parsingMask) {
+ t.Claim(maskStr);
+ } else {
+ t.Claim(hostStr);
+ }
+ t.Record();
+ parsingPort = true;
+ continue;
+ } else if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) {
+ t.Claim(hostStr);
+ t.Record();
+ parsingMask = true;
+ continue;
+ } else if (token.Equals(mozilla::Tokenizer::Token::Char(']'))) {
+ parsingIPv6 = false;
+ continue;
+ }
+ }
+ if (!parsingPort && !parsingMask) {
+ t.Claim(hostStr);
+ } else if (parsingPort) {
+ t.Claim(portStr);
+ } else if (parsingMask) {
+ t.Claim(maskStr);
+ } else {
+ NS_WARNING("Could not parse this rule");
+ continue;
+ }
+
+ if (hostStr.IsEmpty()) {
+ continue;
+ }
+
+ // If the current host filter is "<local>", then all local (i.e.
+ // no dots in the hostname) hosts should bypass the proxy
+ if (hostStr.EqualsIgnoreCase("<local>")) {
+ mFilterLocalHosts = true;
+ LOG(("loaded filter for local hosts "
+ "(plain host names, no dots)\n"));
+ // Continue to next host filter;
+ continue;
+ }
+
+ // For all other host filters, create HostInfo object and add to list
+ HostInfo *hinfo = new HostInfo();
+ nsresult rv = NS_OK;
+
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ port = 0;
+ }
+ hinfo->port = port;
+
+ int32_t maskLen = maskStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ maskLen = 128;
+ }
+
+ // PR_StringToNetAddr can't parse brackets enclosed IPv6
+ nsAutoCString addrString = hostStr;
+ if (hostStr.First() == '[' && hostStr.Last() == ']') {
+ addrString = Substring(hostStr, 1, hostStr.Length() - 2);
+ }
+
+ PRNetAddr addr;
+ if (PR_StringToNetAddr(addrString.get(), &addr) == PR_SUCCESS) {
+ hinfo->is_ipaddr = true;
+ hinfo->ip.family = PR_AF_INET6; // we always store address as IPv6
+ hinfo->ip.mask_len = maskLen;
+
+ if (hinfo->ip.mask_len == 0) {
+ NS_WARNING("invalid mask");
+ goto loser;
+ }
+
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &hinfo->ip.addr);
+ // adjust mask_len accordingly
+ if (hinfo->ip.mask_len <= 32)
+ hinfo->ip.mask_len += 96;
+ }
+ else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(&hinfo->ip.addr, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ }
+ else {
+ NS_WARNING("unknown address family");
+ goto loser;
+ }
+
+ // apply mask to IPv6 address
+ proxy_MaskIPv6Addr(hinfo->ip.addr, hinfo->ip.mask_len);
+ }
+ else {
+ nsAutoCString host;
+ if (hostStr.First() == '*') {
+ host = Substring(hostStr, 1);
+ } else {
+ host = hostStr;
+ }
+
+ if (host.IsEmpty()) {
+ hinfo->name.host = nullptr;
+ goto loser;
+ }
+
+ hinfo->name.host_len = host.Length();
+
+ hinfo->is_ipaddr = false;
+ hinfo->name.host = ToNewCString(host);
+
+ if (!hinfo->name.host)
+ goto loser;
+ }
+
+//#define DEBUG_DUMP_FILTERS
+#ifdef DEBUG_DUMP_FILTERS
+ printf("loaded filter[%zu]:\n", mHostFiltersArray.Length());
+ printf(" is_ipaddr = %u\n", hinfo->is_ipaddr);
+ printf(" port = %u\n", hinfo->port);
+ printf(" host = %s\n", hostStr.get());
+ if (hinfo->is_ipaddr) {
+ printf(" ip.family = %x\n", hinfo->ip.family);
+ printf(" ip.mask_len = %u\n", hinfo->ip.mask_len);
+
+ PRNetAddr netAddr;
+ PR_SetNetAddr(PR_IpAddrNull, PR_AF_INET6, 0, &netAddr);
+ memcpy(&netAddr.ipv6.ip, &hinfo->ip.addr, sizeof(hinfo->ip.addr));
+
+ char buf[256];
+ PR_NetAddrToString(&netAddr, buf, sizeof(buf));
+
+ printf(" ip.addr = %s\n", buf);
+ }
+ else {
+ printf(" name.host = %s\n", hinfo->name.host);
+ }
+#endif
+
+ mHostFiltersArray.AppendElement(hinfo);
+ hinfo = nullptr;
+loser:
+ delete hinfo;
+ }
+}
+
+nsresult
+nsProtocolProxyService::GetProtocolInfo(nsIURI *uri, nsProtocolInfo *info)
+{
+ NS_PRECONDITION(uri, "URI is null");
+ NS_PRECONDITION(info, "info is null");
+
+ nsresult rv;
+
+ rv = uri->GetScheme(info->scheme);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler(info->scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = handler->DoGetProtocolFlags(uri, &info->flags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = handler->GetDefaultPort(&info->defaultPort);
+ return rv;
+}
+
+nsresult
+nsProtocolProxyService::NewProxyInfo_Internal(const char *aType,
+ const nsACString &aHost,
+ int32_t aPort,
+ const nsACString &aUsername,
+ const nsACString &aPassword,
+ uint32_t aFlags,
+ uint32_t aFailoverTimeout,
+ nsIProxyInfo *aFailoverProxy,
+ uint32_t aResolveFlags,
+ nsIProxyInfo **aResult)
+{
+ if (aPort <= 0)
+ aPort = -1;
+
+ nsCOMPtr<nsProxyInfo> failover;
+ if (aFailoverProxy) {
+ failover = do_QueryInterface(aFailoverProxy);
+ NS_ENSURE_ARG(failover);
+ }
+
+ nsProxyInfo *proxyInfo = new nsProxyInfo();
+ if (!proxyInfo)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ proxyInfo->mType = aType;
+ proxyInfo->mHost = aHost;
+ proxyInfo->mPort = aPort;
+ proxyInfo->mUsername = aUsername;
+ proxyInfo->mPassword = aPassword;
+ proxyInfo->mFlags = aFlags;
+ proxyInfo->mResolveFlags = aResolveFlags;
+ proxyInfo->mTimeout = aFailoverTimeout == UINT32_MAX
+ ? mFailedProxyTimeout : aFailoverTimeout;
+ failover.swap(proxyInfo->mNext);
+
+ NS_ADDREF(*aResult = proxyInfo);
+ return NS_OK;
+}
+
+nsresult
+nsProtocolProxyService::Resolve_Internal(nsIChannel *channel,
+ const nsProtocolInfo &info,
+ uint32_t flags,
+ bool *usePACThread,
+ nsIProxyInfo **result)
+{
+ NS_ENSURE_ARG_POINTER(channel);
+ nsresult rv = SetupPACThread();
+ if (NS_FAILED(rv))
+ return rv;
+
+ *usePACThread = false;
+ *result = nullptr;
+
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY))
+ return NS_OK; // Can't proxy this (filters may not override)
+
+ nsCOMPtr<nsIURI> uri;
+ rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ // See bug #586908.
+ // Avoid endless loop if |uri| is the current PAC-URI. Returning OK
+ // here means that we will not use a proxy for this connection.
+ if (mPACMan && mPACMan->IsPACURI(uri))
+ return NS_OK;
+
+ bool mainThreadOnly;
+ if (mSystemProxySettings &&
+ mProxyConfig == PROXYCONFIG_SYSTEM &&
+ NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
+ !mainThreadOnly) {
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM) {
+ // If the system proxy setting implementation is not threadsafe (e.g
+ // linux gconf), we'll do it inline here. Such implementations promise
+ // not to block
+
+ nsAutoCString PACURI;
+ nsAutoCString pacString;
+
+ if (NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
+ !PACURI.IsEmpty()) {
+ // There is a PAC URI configured. If it is unchanged, then
+ // just execute the PAC thread. If it is changed then load
+ // the new value
+
+ if (mPACMan && mPACMan->IsPACURI(PACURI)) {
+ // unchanged
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ ConfigureFromPAC(PACURI, false);
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+
+ uri->GetAsciiSpec(spec);
+ uri->GetAsciiHost(host);
+ uri->GetScheme(scheme);
+ uri->GetPort(&port);
+
+ // now try the system proxy settings for this particular url
+ if (NS_SUCCEEDED(mSystemProxySettings->
+ GetProxyForURI(spec, scheme, host, port,
+ pacString))) {
+ ProcessPACString(pacString, 0, result);
+ return NS_OK;
+ }
+ }
+
+ // if proxies are enabled and this host:port combo is supposed to use a
+ // proxy, check for a proxy.
+ if (mProxyConfig == PROXYCONFIG_DIRECT ||
+ (mProxyConfig == PROXYCONFIG_MANUAL &&
+ !CanUseProxy(uri, info.defaultPort)))
+ return NS_OK;
+
+ // Proxy auto config magic...
+ if (mProxyConfig == PROXYCONFIG_PAC || mProxyConfig == PROXYCONFIG_WPAD) {
+ // Do not query PAC now.
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ // If we aren't in manual proxy configuration mode then we don't
+ // want to honor any manual specific prefs that might be still set
+ if (mProxyConfig != PROXYCONFIG_MANUAL)
+ return NS_OK;
+
+ // proxy info values for manual configuration mode
+ const char *type = nullptr;
+ const nsACString *host = nullptr;
+ int32_t port = -1;
+
+ uint32_t proxyFlags = 0;
+
+ if ((flags & RESOLVE_PREFER_SOCKS_PROXY) &&
+ !mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4)
+ type = kProxyType_SOCKS4;
+ else
+ type = kProxyType_SOCKS;
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS)
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+ else if ((flags & RESOLVE_PREFER_HTTPS_PROXY) &&
+ !mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0) {
+ host = &mHTTPSProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPSProxyPort;
+ }
+ else if (!mHTTPProxyHost.IsEmpty() && mHTTPProxyPort > 0 &&
+ ((flags & RESOLVE_IGNORE_URI_SCHEME) ||
+ info.scheme.EqualsLiteral("http"))) {
+ host = &mHTTPProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPProxyPort;
+ }
+ else if (!mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0 &&
+ !(flags & RESOLVE_IGNORE_URI_SCHEME) &&
+ info.scheme.EqualsLiteral("https")) {
+ host = &mHTTPSProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPSProxyPort;
+ }
+ else if (!mFTPProxyHost.IsEmpty() && mFTPProxyPort > 0 &&
+ !(flags & RESOLVE_IGNORE_URI_SCHEME) &&
+ info.scheme.EqualsLiteral("ftp")) {
+ host = &mFTPProxyHost;
+ type = kProxyType_HTTP;
+ port = mFTPProxyPort;
+ }
+ else if (!mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4)
+ type = kProxyType_SOCKS4;
+ else
+ type = kProxyType_SOCKS;
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS)
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+
+ if (type) {
+ rv = NewProxyInfo_Internal(type, *host, port,
+ EmptyCString(), EmptyCString(),
+ proxyFlags, UINT32_MAX, nullptr, flags,
+ result);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+nsProtocolProxyService::MaybeDisableDNSPrefetch(nsIProxyInfo *aProxy)
+{
+ // Disable Prefetch in the DNS service if a proxy is in use.
+ if (!aProxy)
+ return;
+
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ if (!pi ||
+ !pi->mType ||
+ pi->mType == kProxyType_DIRECT)
+ return;
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns)
+ return;
+ nsCOMPtr<nsPIDNSService> pdns = do_QueryInterface(dns);
+ if (!pdns)
+ return;
+
+ // We lose the prefetch optimization for the life of the dns service.
+ pdns->SetPrefetchEnabled(false);
+}
+
+void
+nsProtocolProxyService::ApplyFilters(nsIChannel *channel,
+ const nsProtocolInfo &info,
+ nsIProxyInfo **list)
+{
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY))
+ return;
+
+ // We prune the proxy list prior to invoking each filter. This may be
+ // somewhat inefficient, but it seems like a good idea since we want each
+ // filter to "see" a valid proxy list.
+
+ nsCOMPtr<nsIProxyInfo> result;
+
+ for (FilterLink *iter = mFilters; iter; iter = iter->next) {
+ PruneProxyInfo(info, list);
+ nsresult rv = NS_OK;
+ if (iter->filter) {
+ nsCOMPtr<nsIURI> uri;
+ rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (uri) {
+ rv = iter->filter->ApplyFilter(this, uri, *list,
+ getter_AddRefs(result));
+ }
+ } else if (iter->channelFilter) {
+ rv = iter->channelFilter->ApplyFilter(this, channel, *list,
+ getter_AddRefs(result));
+ }
+ if (NS_FAILED(rv))
+ continue;
+ result.swap(*list);
+ }
+
+ PruneProxyInfo(info, list);
+}
+
+void
+nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo &info,
+ nsIProxyInfo **list)
+{
+ if (!*list)
+ return;
+ nsProxyInfo *head = nullptr;
+ CallQueryInterface(*list, &head);
+ if (!head) {
+ NS_NOTREACHED("nsIProxyInfo must QI to nsProxyInfo");
+ return;
+ }
+ NS_RELEASE(*list);
+
+ // Pruning of disabled proxies works like this:
+ // - If all proxies are disabled, return the full list
+ // - Otherwise, remove the disabled proxies.
+ //
+ // Pruning of disallowed proxies works like this:
+ // - If the protocol handler disallows the proxy, then we disallow it.
+
+ // Start by removing all disallowed proxies if required:
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY_HTTP)) {
+ nsProxyInfo *last = nullptr, *iter = head;
+ while (iter) {
+ if ((iter->Type() == kProxyType_HTTP) ||
+ (iter->Type() == kProxyType_HTTPS)) {
+ // reject!
+ if (last)
+ last->mNext = iter->mNext;
+ else
+ head = iter->mNext;
+ nsProxyInfo *next = iter->mNext;
+ iter->mNext = nullptr;
+ iter->Release();
+ iter = next;
+ } else {
+ last = iter;
+ iter = iter->mNext;
+ }
+ }
+ if (!head)
+ return;
+ }
+
+ // Now, scan to see if all remaining proxies are disabled. If so, then
+ // we'll just bail and return them all. Otherwise, we'll go and prune the
+ // disabled ones.
+
+ bool allDisabled = true;
+
+ nsProxyInfo *iter;
+ for (iter = head; iter; iter = iter->mNext) {
+ if (!IsProxyDisabled(iter)) {
+ allDisabled = false;
+ break;
+ }
+ }
+
+ if (allDisabled)
+ LOG(("All proxies are disabled, so trying all again"));
+ else {
+ // remove any disabled proxies.
+ nsProxyInfo *last = nullptr;
+ for (iter = head; iter; ) {
+ if (IsProxyDisabled(iter)) {
+ // reject!
+ nsProxyInfo *reject = iter;
+
+ iter = iter->mNext;
+ if (last)
+ last->mNext = iter;
+ else
+ head = iter;
+
+ reject->mNext = nullptr;
+ NS_RELEASE(reject);
+ continue;
+ }
+
+ // since we are about to use this proxy, make sure it is not on
+ // the disabled proxy list. we'll add it back to that list if
+ // we have to (in GetFailoverForProxy).
+ //
+ // XXX(darin): It might be better to do this as a final pass.
+ //
+ EnableProxy(iter);
+
+ last = iter;
+ iter = iter->mNext;
+ }
+ }
+
+ // if only DIRECT was specified then return no proxy info, and we're done.
+ if (head && !head->mNext && head->mType == kProxyType_DIRECT)
+ NS_RELEASE(head);
+
+ *list = head; // Transfer ownership
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProtocolProxyService.h b/netwerk/base/nsProtocolProxyService.h
new file mode 100644
index 0000000000..9fe24699eb
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -0,0 +1,414 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProtocolProxyService_h__
+#define nsProtocolProxyService_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIProtocolProxyFilter.h"
+#include "nsIProxyInfo.h"
+#include "nsIObserver.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "prio.h"
+#include "mozilla/Attributes.h"
+
+class nsIPrefBranch;
+class nsISystemProxySettings;
+
+namespace mozilla {
+namespace net {
+
+typedef nsDataHashtable<nsCStringHashKey, uint32_t> nsFailedProxyTable;
+
+class nsPACMan;
+class nsProxyInfo;
+struct nsProtocolInfo;
+
+// CID for the nsProtocolProxyService class
+// 091eedd8-8bae-4fe3-ad62-0c87351e640d
+#define NS_PROTOCOL_PROXY_SERVICE_IMPL_CID \
+{ 0x091eedd8, 0x8bae, 0x4fe3, \
+ { 0xad, 0x62, 0x0c, 0x87, 0x35, 0x1e, 0x64, 0x0d } }
+
+class nsProtocolProxyService final : public nsIProtocolProxyService2
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYSERVICE2
+ NS_DECL_NSIPROTOCOLPROXYSERVICE
+ NS_DECL_NSIOBSERVER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+ nsProtocolProxyService();
+
+ nsresult Init();
+ nsresult DeprecatedBlockingResolve(nsIChannel *aChannel,
+ uint32_t aFlags,
+ nsIProxyInfo **retval);
+
+protected:
+ friend class nsAsyncResolveRequest;
+ friend class TestProtocolProxyService_LoadHostFilters_Test; // for gtest
+
+ ~nsProtocolProxyService();
+
+ /**
+ * This method is called whenever a preference may have changed or
+ * to initialize all preferences.
+ *
+ * @param prefs
+ * This must be a pointer to the root pref branch.
+ * @param name
+ * This can be the name of a fully-qualified preference, or it can
+ * be null, in which case all preferences will be initialized.
+ */
+ void PrefsChanged(nsIPrefBranch *prefs, const char *name);
+
+ /**
+ * This method is called to create a nsProxyInfo instance from the given
+ * PAC-style proxy string. It parses up to the end of the string, or to
+ * the next ';' character.
+ *
+ * @param proxy
+ * The PAC-style proxy string to parse. This must not be null.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * Upon return this points to a newly allocated nsProxyInfo or null
+ * if the proxy string was invalid.
+ *
+ * @return A pointer beyond the parsed proxy string (never null).
+ */
+ const char * ExtractProxyInfo(const char *proxy,
+ uint32_t aResolveFlags,
+ nsProxyInfo **result);
+
+ /**
+ * Load the specified PAC file.
+ *
+ * @param pacURI
+ * The URI spec of the PAC file to load.
+ */
+ nsresult ConfigureFromPAC(const nsCString &pacURI, bool forceReload);
+
+ /**
+ * This method builds a list of nsProxyInfo objects from the given PAC-
+ * style string.
+ *
+ * @param pacString
+ * The PAC-style proxy string to parse. This may be empty.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * The resulting list of proxy info objects.
+ */
+ void ProcessPACString(const nsCString &pacString,
+ uint32_t aResolveFlags,
+ nsIProxyInfo **result);
+
+ /**
+ * This method generates a string valued identifier for the given
+ * nsProxyInfo object.
+ *
+ * @param pi
+ * The nsProxyInfo object from which to generate the key.
+ * @param result
+ * Upon return, this parameter holds the generated key.
+ */
+ void GetProxyKey(nsProxyInfo *pi, nsCString &result);
+
+ /**
+ * @return Seconds since start of session.
+ */
+ uint32_t SecondsSinceSessionStart();
+
+ /**
+ * This method removes the specified proxy from the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to enable.
+ */
+ void EnableProxy(nsProxyInfo *pi);
+
+ /**
+ * This method adds the specified proxy to the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to disable.
+ */
+ void DisableProxy(nsProxyInfo *pi);
+
+ /**
+ * This method tests to see if the given proxy is disabled.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to test.
+ *
+ * @return True if the specified proxy is disabled.
+ */
+ bool IsProxyDisabled(nsProxyInfo *pi);
+
+ /**
+ * This method queries the protocol handler for the given scheme to check
+ * for the protocol flags and default port.
+ *
+ * @param uri
+ * The URI to query.
+ * @param info
+ * Holds information about the protocol upon return. Pass address
+ * of structure when you call this method. This parameter must not
+ * be null.
+ */
+ nsresult GetProtocolInfo(nsIURI *uri, nsProtocolInfo *result);
+
+ /**
+ * This method is an internal version nsIProtocolProxyService::newProxyInfo
+ * that expects a string literal for the type.
+ *
+ * @param type
+ * The proxy type.
+ * @param host
+ * The proxy host name (UTF-8 ok).
+ * @param port
+ * The proxy port number.
+ * @param username
+ * The username for the proxy (ASCII). May be "", but not null.
+ * @param password
+ * The password for the proxy (ASCII). May be "", but not null.
+ * @param flags
+ * The proxy flags (nsIProxyInfo::flags).
+ * @param timeout
+ * The failover timeout for this proxy.
+ * @param next
+ * The next proxy to try if this one fails.
+ * @param aResolveFlags
+ * The flags passed to resolve (from nsIProtocolProxyService).
+ * @param result
+ * The resulting nsIProxyInfo object.
+ */
+ nsresult NewProxyInfo_Internal(const char *type,
+ const nsACString &host,
+ int32_t port,
+ const nsACString &username,
+ const nsACString &password,
+ uint32_t flags,
+ uint32_t timeout,
+ nsIProxyInfo *next,
+ uint32_t aResolveFlags,
+ nsIProxyInfo **result);
+
+ /**
+ * This method is an internal version of Resolve that does not query PAC.
+ * It performs all of the built-in processing, and reports back to the
+ * caller with either the proxy info result or a flag to instruct the
+ * caller to use PAC instead.
+ *
+ * @param channel
+ * The channel to test.
+ * @param info
+ * Information about the URI's protocol.
+ * @param flags
+ * The flags passed to either the resolve or the asyncResolve method.
+ * @param usePAC
+ * If this flag is set upon return, then PAC should be queried to
+ * resolve the proxy info.
+ * @param result
+ * The resulting proxy info or null.
+ */
+ nsresult Resolve_Internal(nsIChannel *channel,
+ const nsProtocolInfo &info,
+ uint32_t flags,
+ bool *usePAC,
+ nsIProxyInfo **result);
+
+ /**
+ * This method applies the registered filters to the given proxy info
+ * list, and returns a possibly modified list.
+ *
+ * @param channel
+ * The channel corresponding to this proxy info list.
+ * @param info
+ * Information about the URI's protocol.
+ * @param proxyInfo
+ * The proxy info list to be modified. This is an inout param.
+ */
+ void ApplyFilters(nsIChannel *channel, const nsProtocolInfo &info,
+ nsIProxyInfo **proxyInfo);
+
+ /**
+ * This method is a simple wrapper around ApplyFilters that takes the
+ * proxy info list inout param as a nsCOMPtr.
+ */
+ inline void ApplyFilters(nsIChannel *channel, const nsProtocolInfo &info,
+ nsCOMPtr<nsIProxyInfo> &proxyInfo)
+ {
+ nsIProxyInfo *pi = nullptr;
+ proxyInfo.swap(pi);
+ ApplyFilters(channel, info, &pi);
+ proxyInfo.swap(pi);
+ }
+
+ /**
+ * This method prunes out disabled and disallowed proxies from a given
+ * proxy info list.
+ *
+ * @param info
+ * Information about the URI's protocol.
+ * @param proxyInfo
+ * The proxy info list to be modified. This is an inout param.
+ */
+ void PruneProxyInfo(const nsProtocolInfo &info,
+ nsIProxyInfo **proxyInfo);
+
+ /**
+ * This method populates mHostFiltersArray from the given string.
+ *
+ * @param hostFilters
+ * A "no-proxy-for" exclusion list.
+ */
+ void LoadHostFilters(const nsACString& hostFilters);
+
+ /**
+ * This method checks the given URI against mHostFiltersArray.
+ *
+ * @param uri
+ * The URI to test.
+ * @param defaultPort
+ * The default port for the given URI.
+ *
+ * @return True if the URI can use the specified proxy.
+ */
+ bool CanUseProxy(nsIURI *uri, int32_t defaultPort);
+
+ /**
+ * Disable Prefetch in the DNS service if a proxy is in use.
+ *
+ * @param aProxy
+ * The proxy information
+ */
+ void MaybeDisableDNSPrefetch(nsIProxyInfo *aProxy);
+
+private:
+ nsresult SetupPACThread();
+ nsresult ResetPACThread();
+ nsresult ReloadNetworkPAC();
+
+public:
+ // The Sun Forte compiler and others implement older versions of the
+ // C++ standard's rules on access and nested classes. These structs
+ // need to be public in order to deal with those compilers.
+
+ struct HostInfoIP {
+ uint16_t family;
+ uint16_t mask_len;
+ PRIPv6Addr addr; // possibly IPv4-mapped address
+ };
+
+ struct HostInfoName {
+ char *host;
+ uint32_t host_len;
+ };
+
+protected:
+
+ // simplified array of filters defined by this struct
+ struct HostInfo {
+ bool is_ipaddr;
+ int32_t port;
+ union {
+ HostInfoIP ip;
+ HostInfoName name;
+ };
+
+ HostInfo()
+ : is_ipaddr(false)
+ , port(0)
+ { /* other members intentionally uninitialized */ }
+ ~HostInfo() {
+ if (!is_ipaddr && name.host)
+ free(name.host);
+ }
+ };
+
+ // An instance of this struct is allocated for each registered
+ // nsIProtocolProxyFilter and each nsIProtocolProxyChannelFilter.
+ struct FilterLink {
+ struct FilterLink *next;
+ uint32_t position;
+ nsCOMPtr<nsIProtocolProxyFilter> filter;
+ nsCOMPtr<nsIProtocolProxyChannelFilter> channelFilter;
+ FilterLink(uint32_t p, nsIProtocolProxyFilter *f)
+ : next(nullptr), position(p), filter(f), channelFilter(nullptr) {}
+ FilterLink(uint32_t p, nsIProtocolProxyChannelFilter *cf)
+ : next(nullptr), position(p), filter(nullptr), channelFilter(cf) {}
+ // Chain deletion to simplify cleaning up the filter links
+ ~FilterLink() { if (next) delete next; }
+ };
+
+private:
+ // Private methods to insert and remove FilterLinks from the FilterLink chain.
+ nsresult InsertFilterLink(FilterLink *link, uint32_t position);
+ nsresult RemoveFilterLink(nsISupports *givenObject);
+
+protected:
+ // Indicates if local hosts (plain hostnames, no dots) should use the proxy
+ bool mFilterLocalHosts;
+
+ // Holds an array of HostInfo objects
+ nsTArray<nsAutoPtr<HostInfo> > mHostFiltersArray;
+
+ // Points to the start of a sorted by position, singly linked list
+ // of FilterLink objects.
+ FilterLink *mFilters;
+
+ uint32_t mProxyConfig;
+
+ nsCString mHTTPProxyHost;
+ int32_t mHTTPProxyPort;
+
+ nsCString mFTPProxyHost;
+ int32_t mFTPProxyPort;
+
+ nsCString mHTTPSProxyHost;
+ int32_t mHTTPSProxyPort;
+
+ // mSOCKSProxyTarget could be a host, a domain socket path,
+ // or a named-pipe name.
+ nsCString mSOCKSProxyTarget;
+ int32_t mSOCKSProxyPort;
+ int32_t mSOCKSProxyVersion;
+ bool mSOCKSProxyRemoteDNS;
+ bool mProxyOverTLS;
+
+ RefPtr<nsPACMan> mPACMan; // non-null if we are using PAC
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ PRTime mSessionStart;
+ nsFailedProxyTable mFailedProxies;
+ int32_t mFailedProxyTimeout;
+
+private:
+ nsresult AsyncResolveInternal(nsIChannel *channel, uint32_t flags,
+ nsIProtocolProxyCallback *callback,
+ nsICancelable **result,
+ bool isSyncOK);
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProtocolProxyService, NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsProtocolProxyService_h__
diff --git a/netwerk/base/nsProxyInfo.cpp b/netwerk/base/nsProxyInfo.cpp
new file mode 100644
index 0000000000..8291711ab9
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsProxyInfo.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+// Yes, we support QI to nsProxyInfo
+NS_IMPL_ISUPPORTS(nsProxyInfo, nsProxyInfo, nsIProxyInfo)
+
+NS_IMETHODIMP
+nsProxyInfo::GetHost(nsACString &result)
+{
+ result = mHost;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetPort(int32_t *result)
+{
+ *result = mPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetType(nsACString &result)
+{
+ result = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFlags(uint32_t *result)
+{
+ *result = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetResolveFlags(uint32_t *result)
+{
+ *result = mResolveFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetUsername(nsACString &result)
+{
+ result = mUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetPassword(nsACString &result)
+{
+ result = mPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFailoverTimeout(uint32_t *result)
+{
+ *result = mTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFailoverProxy(nsIProxyInfo **result)
+{
+ NS_IF_ADDREF(*result = mNext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::SetFailoverProxy(nsIProxyInfo *proxy)
+{
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(proxy);
+ NS_ENSURE_ARG(pi);
+
+ pi.swap(mNext);
+ return NS_OK;
+}
+
+// These pointers are declared in nsProtocolProxyService.cpp and
+// comparison of mType by string pointer is valid within necko
+ extern const char kProxyType_HTTP[];
+ extern const char kProxyType_HTTPS[];
+ extern const char kProxyType_SOCKS[];
+ extern const char kProxyType_SOCKS4[];
+ extern const char kProxyType_SOCKS5[];
+ extern const char kProxyType_DIRECT[];
+
+bool
+nsProxyInfo::IsDirect()
+{
+ if (!mType)
+ return true;
+ return mType == kProxyType_DIRECT;
+}
+
+bool
+nsProxyInfo::IsHTTP()
+{
+ return mType == kProxyType_HTTP;
+}
+
+bool
+nsProxyInfo::IsHTTPS()
+{
+ return mType == kProxyType_HTTPS;
+}
+
+bool
+nsProxyInfo::IsSOCKS()
+{
+ return mType == kProxyType_SOCKS ||
+ mType == kProxyType_SOCKS4 || mType == kProxyType_SOCKS5;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProxyInfo.h b/netwerk/base/nsProxyInfo.h
new file mode 100644
index 0000000000..007387b770
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProxyInfo_h__
+#define nsProxyInfo_h__
+
+#include "nsIProxyInfo.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+// Use to support QI nsIProxyInfo to nsProxyInfo
+#define NS_PROXYINFO_IID \
+{ /* ed42f751-825e-4cc2-abeb-3670711a8b85 */ \
+ 0xed42f751, \
+ 0x825e, \
+ 0x4cc2, \
+ {0xab, 0xeb, 0x36, 0x70, 0x71, 0x1a, 0x8b, 0x85} \
+}
+
+namespace mozilla {
+namespace net {
+
+// This class is exposed to other classes inside Necko for fast access
+// to the nsIProxyInfo attributes.
+class nsProxyInfo final : public nsIProxyInfo
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROXYINFO_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROXYINFO
+
+ // Cheap accessors for use within Necko
+ const nsCString &Host() { return mHost; }
+ int32_t Port() { return mPort; }
+ const char *Type() { return mType; }
+ uint32_t Flags() { return mFlags; }
+ const nsCString &Username() { return mUsername; }
+ const nsCString &Password() { return mPassword; }
+
+ bool IsDirect();
+ bool IsHTTP();
+ bool IsHTTPS();
+ bool IsSOCKS();
+
+private:
+ friend class nsProtocolProxyService;
+
+ explicit nsProxyInfo(const char *type = nullptr)
+ : mType(type)
+ , mPort(-1)
+ , mFlags(0)
+ , mResolveFlags(0)
+ , mTimeout(UINT32_MAX)
+ , mNext(nullptr)
+ {}
+
+ ~nsProxyInfo()
+ {
+ NS_IF_RELEASE(mNext);
+ }
+
+ const char *mType; // pointer to statically allocated value
+ nsCString mHost;
+ nsCString mUsername;
+ nsCString mPassword;
+ int32_t mPort;
+ uint32_t mFlags;
+ uint32_t mResolveFlags;
+ uint32_t mTimeout;
+ nsProxyInfo *mNext;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProxyInfo, NS_PROXYINFO_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsProxyInfo_h__
diff --git a/netwerk/base/nsReadLine.h b/netwerk/base/nsReadLine.h
new file mode 100644
index 0000000000..3482330e66
--- /dev/null
+++ b/netwerk/base/nsReadLine.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsReadLine_h__
+#define nsReadLine_h__
+
+#include "nsIInputStream.h"
+#include "mozilla/Likely.h"
+
+/**
+ * @file
+ * Functions to read complete lines from an input stream.
+ *
+ * To properly use the helper function in here (NS_ReadLine) the caller should
+ * create a nsLineBuffer<T> with new, and pass it to NS_ReadLine every time it
+ * wants a line out.
+ *
+ * When done, the object should be deleted.
+ */
+
+/**
+ * @internal
+ * Buffer size. This many bytes will be buffered. If a line is longer than this,
+ * the partial line will be appended to the out parameter of NS_ReadLine and the
+ * buffer will be emptied.
+ * Note: if you change this constant, please update the regression test in
+ * netwerk/test/unit/test_readline.js accordingly (bug 397850).
+ */
+#define kLineBufferSize 4096
+
+/**
+ * @internal
+ * Line buffer structure, buffers data from an input stream.
+ * The buffer is empty when |start| == |end|.
+ * Invariant: |start| <= |end|
+ */
+template<typename CharT>
+class nsLineBuffer {
+ public:
+ nsLineBuffer() : start(buf), end(buf) { }
+
+ CharT buf[kLineBufferSize+1];
+ CharT* start;
+ CharT* end;
+};
+
+/**
+ * Read a line from an input stream. Lines are separated by '\r' (0x0D) or '\n'
+ * (0x0A), or "\r\n" or "\n\r".
+ *
+ * @param aStream
+ * The stream to read from
+ * @param aBuffer
+ * The line buffer to use. A single line buffer must not be used with
+ * different input streams.
+ * @param aLine [out]
+ * The string where the line will be stored.
+ * @param more [out]
+ * Whether more data is available in the buffer. If true, NS_ReadLine may
+ * be called again to read further lines. Otherwise, further calls to
+ * NS_ReadLine will return an error.
+ *
+ * @retval NS_OK
+ * Read successful
+ * @retval error
+ * Input stream returned an error upon read. See
+ * nsIInputStream::read.
+ */
+template<typename CharT, class StreamType, class StringType>
+nsresult
+NS_ReadLine (StreamType* aStream, nsLineBuffer<CharT> * aBuffer,
+ StringType & aLine, bool *more)
+{
+ CharT eolchar = 0; // the first eol char or 1 after \r\n or \n\r is found
+
+ aLine.Truncate();
+
+ while (1) { // will be returning out of this loop on eol or eof
+ if (aBuffer->start == aBuffer->end) { // buffer is empty. Read into it.
+ uint32_t bytesRead;
+ nsresult rv = aStream->Read(aBuffer->buf, kLineBufferSize, &bytesRead);
+ if (NS_FAILED(rv) || MOZ_UNLIKELY(bytesRead == 0)) {
+ *more = false;
+ return rv;
+ }
+ aBuffer->start = aBuffer->buf;
+ aBuffer->end = aBuffer->buf + bytesRead;
+ *(aBuffer->end) = '\0';
+ }
+
+ /*
+ * Walk the buffer looking for an end-of-line.
+ * There are 3 cases to consider:
+ * 1. the eol char is the last char in the buffer
+ * 2. the eol char + one more char at the end of the buffer
+ * 3. the eol char + two or more chars at the end of the buffer
+ * we need at least one char after the first eol char to determine if
+ * it's a \r\n or \n\r sequence (and skip over it), and we need one
+ * more char after the end-of-line to set |more| correctly.
+ */
+ CharT* current = aBuffer->start;
+ if (MOZ_LIKELY(eolchar == 0)) {
+ for ( ; current < aBuffer->end; ++current) {
+ if (*current == '\n' || *current == '\r') {
+ eolchar = *current;
+ *current++ = '\0';
+ aLine.Append(aBuffer->start);
+ break;
+ }
+ }
+ }
+ if (MOZ_LIKELY(eolchar != 0)) {
+ for ( ; current < aBuffer->end; ++current) {
+ if ((eolchar == '\r' && *current == '\n') ||
+ (eolchar == '\n' && *current == '\r')) {
+ eolchar = 1;
+ continue;
+ }
+ aBuffer->start = current;
+ *more = true;
+ return NS_OK;
+ }
+ }
+
+ if (eolchar == 0)
+ aLine.Append(aBuffer->start);
+ aBuffer->start = aBuffer->end; // mark the buffer empty
+ }
+}
+
+#endif // nsReadLine_h__
diff --git a/netwerk/base/nsRequestObserverProxy.cpp b/netwerk/base/nsRequestObserverProxy.cpp
new file mode 100644
index 0000000000..4c3c718baf
--- /dev/null
+++ b/netwerk/base/nsRequestObserverProxy.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nscore.h"
+#include "nsRequestObserverProxy.h"
+#include "nsIRequest.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gRequestObserverProxyLog("nsRequestObserverProxy");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gRequestObserverProxyLog, LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsARequestObserverEvent internal class...
+//-----------------------------------------------------------------------------
+
+nsARequestObserverEvent::nsARequestObserverEvent(nsIRequest *request)
+ : mRequest(request)
+{
+ NS_PRECONDITION(mRequest, "null pointer");
+}
+
+//-----------------------------------------------------------------------------
+// nsOnStartRequestEvent internal class...
+//-----------------------------------------------------------------------------
+
+class nsOnStartRequestEvent : public nsARequestObserverEvent
+{
+ RefPtr<nsRequestObserverProxy> mProxy;
+public:
+ nsOnStartRequestEvent(nsRequestObserverProxy *proxy,
+ nsIRequest *request)
+ : nsARequestObserverEvent(request)
+ , mProxy(proxy)
+ {
+ NS_PRECONDITION(mProxy, "null pointer");
+ }
+
+ virtual ~nsOnStartRequestEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ LOG(("nsOnStartRequestEvent::HandleEvent [req=%x]\n", mRequest.get()));
+
+ if (!mProxy->mObserver) {
+ NS_NOTREACHED("already handled onStopRequest event (observer is null)");
+ return NS_OK;
+ }
+
+ LOG(("handle startevent=%p\n", this));
+ nsresult rv = mProxy->mObserver->OnStartRequest(mRequest, mProxy->mContext);
+ if (NS_FAILED(rv)) {
+ LOG(("OnStartRequest failed [rv=%x] canceling request!\n", rv));
+ rv = mRequest->Cancel(rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed for request!");
+ }
+
+ return NS_OK;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// nsOnStopRequestEvent internal class...
+//-----------------------------------------------------------------------------
+
+class nsOnStopRequestEvent : public nsARequestObserverEvent
+{
+ RefPtr<nsRequestObserverProxy> mProxy;
+public:
+ nsOnStopRequestEvent(nsRequestObserverProxy *proxy,
+ nsIRequest *request)
+ : nsARequestObserverEvent(request)
+ , mProxy(proxy)
+ {
+ NS_PRECONDITION(mProxy, "null pointer");
+ }
+
+ virtual ~nsOnStopRequestEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ LOG(("nsOnStopRequestEvent::HandleEvent [req=%x]\n", mRequest.get()));
+
+ nsMainThreadPtrHandle<nsIRequestObserver> observer = mProxy->mObserver;
+ if (!observer) {
+ NS_NOTREACHED("already handled onStopRequest event (observer is null)");
+ return NS_OK;
+ }
+ // Do not allow any more events to be handled after OnStopRequest
+ mProxy->mObserver = 0;
+
+ nsresult status = NS_OK;
+ DebugOnly<nsresult> rv = mRequest->GetStatus(&status);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetStatus failed for request!");
+
+ LOG(("handle stopevent=%p\n", this));
+ (void) observer->OnStopRequest(mRequest, mProxy->mContext, status);
+
+ return NS_OK;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsISupports implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsRequestObserverProxy,
+ nsIRequestObserver,
+ nsIRequestObserverProxy)
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsIRequestObserver implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRequestObserverProxy::OnStartRequest(nsIRequest *request,
+ nsISupports *context)
+{
+ MOZ_ASSERT(!context || context == mContext);
+ LOG(("nsRequestObserverProxy::OnStartRequest [this=%p req=%x]\n", this, request));
+
+ nsOnStartRequestEvent *ev =
+ new nsOnStartRequestEvent(this, request);
+ if (!ev)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ LOG(("post startevent=%p\n", ev));
+ nsresult rv = FireEvent(ev);
+ if (NS_FAILED(rv))
+ delete ev;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsRequestObserverProxy::OnStopRequest(nsIRequest *request,
+ nsISupports *context,
+ nsresult status)
+{
+ MOZ_ASSERT(!context || context == mContext);
+ LOG(("nsRequestObserverProxy: OnStopRequest [this=%p req=%x status=%x]\n",
+ this, request, status));
+
+ // The status argument is ignored because, by the time the OnStopRequestEvent
+ // is actually processed, the status of the request may have changed :-(
+ // To make sure that an accurate status code is always used, GetStatus() is
+ // called when the OnStopRequestEvent is actually processed (see above).
+
+ nsOnStopRequestEvent *ev =
+ new nsOnStopRequestEvent(this, request);
+ if (!ev)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ LOG(("post stopevent=%p\n", ev));
+ nsresult rv = FireEvent(ev);
+ if (NS_FAILED(rv))
+ delete ev;
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsIRequestObserverProxy implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRequestObserverProxy::Init(nsIRequestObserver *observer, nsISupports *context)
+{
+ NS_ENSURE_ARG_POINTER(observer);
+ mObserver = new nsMainThreadPtrHolder<nsIRequestObserver>(observer);
+ mContext = new nsMainThreadPtrHolder<nsISupports>(context);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy implementation...
+//-----------------------------------------------------------------------------
+
+nsresult
+nsRequestObserverProxy::FireEvent(nsARequestObserverEvent *event)
+{
+ nsCOMPtr<nsIEventTarget> mainThread(do_GetMainThread());
+ return mainThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsRequestObserverProxy.h b/netwerk/base/nsRequestObserverProxy.h
new file mode 100644
index 0000000000..0ad07186b7
--- /dev/null
+++ b/netwerk/base/nsRequestObserverProxy.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRequestObserverProxy_h__
+#define nsRequestObserverProxy_h__
+
+#include "nsIRequestObserver.h"
+#include "nsIRequestObserverProxy.h"
+#include "nsIRequest.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+class nsARequestObserverEvent;
+
+class nsRequestObserverProxy final : public nsIRequestObserverProxy
+{
+ ~nsRequestObserverProxy() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIREQUESTOBSERVERPROXY
+
+ nsRequestObserverProxy() {}
+
+ nsIRequestObserver *Observer() { return mObserver; }
+
+ nsresult FireEvent(nsARequestObserverEvent *);
+
+protected:
+ nsMainThreadPtrHandle<nsIRequestObserver> mObserver;
+ nsMainThreadPtrHandle<nsISupports> mContext;
+
+ friend class nsOnStartRequestEvent;
+ friend class nsOnStopRequestEvent;
+};
+
+class nsARequestObserverEvent : public Runnable
+{
+public:
+ explicit nsARequestObserverEvent(nsIRequest *);
+
+protected:
+ virtual ~nsARequestObserverEvent() {}
+
+ nsCOMPtr<nsIRequest> mRequest;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsRequestObserverProxy_h__
diff --git a/netwerk/base/nsSecCheckWrapChannel.cpp b/netwerk/base/nsSecCheckWrapChannel.cpp
new file mode 100644
index 0000000000..330079a0f0
--- /dev/null
+++ b/netwerk/base/nsSecCheckWrapChannel.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentSecurityManager.h"
+#include "nsSecCheckWrapChannel.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIStreamListener.h"
+#include "mozilla/Logging.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gChannelWrapperLog("ChannelWrapper");
+#define CHANNELWRAPPERLOG(args) MOZ_LOG(gChannelWrapperLog, LogLevel::Debug, args)
+
+NS_IMPL_ADDREF(nsSecCheckWrapChannelBase)
+NS_IMPL_RELEASE(nsSecCheckWrapChannelBase)
+
+NS_INTERFACE_MAP_BEGIN(nsSecCheckWrapChannelBase)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannel, mHttpChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannelInternal, mHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel, mUploadChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel2, mUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsISecCheckWrapChannel)
+NS_INTERFACE_MAP_END
+
+//---------------------------------------------------------
+// nsSecCheckWrapChannelBase implementation
+//---------------------------------------------------------
+
+nsSecCheckWrapChannelBase::nsSecCheckWrapChannelBase(nsIChannel* aChannel)
+ : mChannel(aChannel)
+ , mHttpChannel(do_QueryInterface(aChannel))
+ , mHttpChannelInternal(do_QueryInterface(aChannel))
+ , mRequest(do_QueryInterface(aChannel))
+ , mUploadChannel(do_QueryInterface(aChannel))
+ , mUploadChannel2(do_QueryInterface(aChannel))
+{
+ MOZ_ASSERT(mChannel, "can not create a channel wrapper without a channel");
+}
+
+nsSecCheckWrapChannelBase::~nsSecCheckWrapChannelBase()
+{
+}
+
+//---------------------------------------------------------
+// nsISecCheckWrapChannel implementation
+//---------------------------------------------------------
+
+NS_IMETHODIMP
+nsSecCheckWrapChannelBase::GetInnerChannel(nsIChannel **aInnerChannel)
+{
+ NS_IF_ADDREF(*aInnerChannel = mChannel);
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+// nsSecCheckWrapChannel implementation
+//---------------------------------------------------------
+
+nsSecCheckWrapChannel::nsSecCheckWrapChannel(nsIChannel* aChannel,
+ nsILoadInfo* aLoadInfo)
+ : nsSecCheckWrapChannelBase(aChannel)
+ , mLoadInfo(aLoadInfo)
+{
+ {
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::nsSecCheckWrapChannel [%p] (%s)",
+ this, uri ? uri->GetSpecOrDefault().get() : ""));
+ }
+}
+
+// static
+already_AddRefed<nsIChannel>
+nsSecCheckWrapChannel::MaybeWrap(nsIChannel* aChannel, nsILoadInfo* aLoadInfo)
+{
+ // Maybe a custom protocol handler actually returns a gecko
+ // http/ftpChannel - To check this we will check whether the channel
+ // implements a gecko non-scriptable interface e.g. nsIForcePendingChannel.
+ nsCOMPtr<nsIForcePendingChannel> isGeckoChannel = do_QueryInterface(aChannel);
+
+ nsCOMPtr<nsIChannel> channel;
+ if (isGeckoChannel) {
+ // If it is a gecko channel (ftp or http) we do not need to wrap it.
+ channel = aChannel;
+ channel->SetLoadInfo(aLoadInfo);
+ } else {
+ channel = new nsSecCheckWrapChannel(aChannel, aLoadInfo);
+ }
+ return channel.forget();
+}
+
+nsSecCheckWrapChannel::~nsSecCheckWrapChannel()
+{
+}
+
+//---------------------------------------------------------
+// SecWrapChannelStreamListener helper
+//---------------------------------------------------------
+
+class SecWrapChannelStreamListener final : public nsIStreamListener
+{
+ public:
+ SecWrapChannelStreamListener(nsIRequest *aRequest,
+ nsIStreamListener *aStreamListener)
+ : mRequest(aRequest)
+ , mListener(aStreamListener) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ private:
+ ~SecWrapChannelStreamListener() {}
+
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+NS_IMPL_ISUPPORTS(SecWrapChannelStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+SecWrapChannelStreamListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ return mListener->OnStartRequest(mRequest, aContext);
+}
+
+NS_IMETHODIMP
+SecWrapChannelStreamListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ return mListener->OnStopRequest(mRequest, aContext, aStatus);
+}
+
+NS_IMETHODIMP
+SecWrapChannelStreamListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ return mListener->OnDataAvailable(mRequest, aContext, aInStream, aOffset, aCount);
+}
+
+//---------------------------------------------------------
+// nsIChannel implementation
+//---------------------------------------------------------
+
+NS_IMETHODIMP
+nsSecCheckWrapChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
+{
+ CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::GetLoadInfo() [%p]",this));
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSecCheckWrapChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::SetLoadInfo() [%p]", this));
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSecCheckWrapChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> secWrapChannelListener =
+ new SecWrapChannelStreamListener(this, aListener);
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, secWrapChannelListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(secWrapChannelListener, nullptr);
+}
+
+NS_IMETHODIMP
+nsSecCheckWrapChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSecCheckWrapChannel.h b/netwerk/base/nsSecCheckWrapChannel.h
new file mode 100644
index 0000000000..35300e0ea2
--- /dev/null
+++ b/netwerk/base/nsSecCheckWrapChannel.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSecCheckWrapChannel_h__
+#define nsSecCheckWrapChannel_h__
+
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIUploadChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsISecCheckWrapChannel.h"
+#include "nsIWyciwygChannel.h"
+#include "mozilla/LoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+/*
+ * The nsSecCheckWrapChannelBase wraps channels that do *not*
+ * * provide a newChannel2() implementation
+ * * provide get/setLoadInfo functions
+ *
+ * In order to perform security checks for channels
+ * a) before opening the channel, and
+ * b) after redirects
+ * we are attaching a loadinfo object to every channel which
+ * provides information about the content-type of the channel,
+ * who initiated the load, etc.
+ *
+ * Addon created channels might *not* provide that loadInfo object for
+ * some transition time before we mark the NewChannel-API as deprecated.
+ * We do not want to break those addons hence we wrap such channels
+ * using the provided wrapper in this class.
+ *
+ * Please note that the wrapper only forwards calls for
+ * * nsIRequest
+ * * nsIChannel
+ * * nsIHttpChannel
+ * * nsIHttpChannelInternal
+ * * nsIUploadChannel
+ * * nsIUploadChannel2
+ *
+ * In case any addon needs to query the inner channel this class
+ * provides a readonly function to query the wrapped channel.
+ *
+ */
+
+class nsSecCheckWrapChannelBase : public nsIHttpChannel
+ , public nsIHttpChannelInternal
+ , public nsISecCheckWrapChannel
+ , public nsIUploadChannel
+ , public nsIUploadChannel2
+{
+public:
+ NS_FORWARD_NSIHTTPCHANNEL(mHttpChannel->)
+ NS_FORWARD_NSIHTTPCHANNELINTERNAL(mHttpChannelInternal->)
+ NS_FORWARD_NSICHANNEL(mChannel->)
+ NS_FORWARD_NSIREQUEST(mRequest->)
+ NS_FORWARD_NSIUPLOADCHANNEL(mUploadChannel->)
+ NS_FORWARD_NSIUPLOADCHANNEL2(mUploadChannel2->)
+ NS_DECL_NSISECCHECKWRAPCHANNEL
+ NS_DECL_ISUPPORTS
+
+ explicit nsSecCheckWrapChannelBase(nsIChannel* aChannel);
+
+protected:
+ virtual ~nsSecCheckWrapChannelBase();
+
+ nsCOMPtr<nsIChannel> mChannel;
+ // We do a QI in the constructor to set the following pointers.
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+ nsCOMPtr<nsIHttpChannelInternal> mHttpChannelInternal;
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIUploadChannel> mUploadChannel;
+ nsCOMPtr<nsIUploadChannel2> mUploadChannel2;
+};
+
+/* We define a separate class here to make it clear that we're overriding
+ * Get/SetLoadInfo as well as AsyncOpen2() and Open2(), rather that using
+ * the forwarded implementations provided by NS_FORWARD_NSICHANNEL"
+ */
+class nsSecCheckWrapChannel : public nsSecCheckWrapChannelBase
+{
+public:
+ NS_IMETHOD GetLoadInfo(nsILoadInfo **aLoadInfo);
+ NS_IMETHOD SetLoadInfo(nsILoadInfo *aLoadInfo);
+
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener);
+ NS_IMETHOD Open2(nsIInputStream** aStream);
+
+ nsSecCheckWrapChannel(nsIChannel* aChannel, nsILoadInfo* aLoadInfo);
+ static already_AddRefed<nsIChannel> MaybeWrap(nsIChannel* aChannel,
+ nsILoadInfo* aLoadInfo);
+
+protected:
+ virtual ~nsSecCheckWrapChannel();
+
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsSecCheckWrapChannel_h__
diff --git a/netwerk/base/nsSerializationHelper.cpp b/netwerk/base/nsSerializationHelper.cpp
new file mode 100644
index 0000000000..68f971eae4
--- /dev/null
+++ b/netwerk/base/nsSerializationHelper.cpp
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSerializationHelper.h"
+
+
+#include "mozilla/Base64.h"
+#include "nsISerializable.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsString.h"
+#include "nsBase64Encoder.h"
+#include "nsAutoPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStringStream.h"
+
+using namespace mozilla;
+
+nsresult
+NS_SerializeToString(nsISerializable* obj, nsCSubstring& str)
+{
+ RefPtr<nsBase64Encoder> stream(new nsBase64Encoder());
+ if (!stream)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIObjectOutputStream> objstream =
+ do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+ if (!objstream)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ objstream->SetOutputStream(stream);
+ nsresult rv =
+ objstream->WriteCompoundObject(obj, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return stream->Finish(str);
+}
+
+nsresult
+NS_DeserializeObject(const nsCSubstring& str, nsISupports** obj)
+{
+ nsCString decodedData;
+ nsresult rv = Base64Decode(str, decodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(stream), decodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObjectInputStream> objstream =
+ do_CreateInstance("@mozilla.org/binaryinputstream;1");
+ if (!objstream)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ objstream->SetInputStream(stream);
+ return objstream->ReadObject(true, obj);
+}
+
+NS_IMPL_ISUPPORTS(nsSerializationHelper, nsISerializationHelper)
+
+NS_IMETHODIMP
+nsSerializationHelper::SerializeToString(nsISerializable *serializable,
+ nsACString & _retval)
+{
+ return NS_SerializeToString(serializable, _retval);
+}
+
+NS_IMETHODIMP
+nsSerializationHelper::DeserializeObject(const nsACString & input,
+ nsISupports **_retval)
+{
+ return NS_DeserializeObject(input, _retval);
+}
diff --git a/netwerk/base/nsSerializationHelper.h b/netwerk/base/nsSerializationHelper.h
new file mode 100644
index 0000000000..4a0b7339fe
--- /dev/null
+++ b/netwerk/base/nsSerializationHelper.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/** @file
+ * Helper functions for (de)serializing objects to/from ASCII strings.
+ */
+
+#ifndef NSSERIALIZATIONHELPER_H_
+#define NSSERIALIZATIONHELPER_H_
+
+#include "nsStringFwd.h"
+#include "nsISerializationHelper.h"
+#include "mozilla/Attributes.h"
+
+class nsISerializable;
+
+/**
+ * Serialize an object to an ASCII string.
+ */
+nsresult NS_SerializeToString(nsISerializable* obj,
+ nsCSubstring& str);
+
+/**
+ * Deserialize an object.
+ */
+nsresult NS_DeserializeObject(const nsCSubstring& str,
+ nsISupports** obj);
+
+class nsSerializationHelper final : public nsISerializationHelper
+{
+ ~nsSerializationHelper() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERIALIZATIONHELPER
+};
+
+#endif
diff --git a/netwerk/base/nsServerSocket.cpp b/netwerk/base/nsServerSocket.cpp
new file mode 100644
index 0000000000..8f5d3b029d
--- /dev/null
+++ b/netwerk/base/nsServerSocket.cpp
@@ -0,0 +1,560 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransport2.h"
+#include "nsServerSocket.h"
+#include "nsProxyRelease.h"
+#include "nsAutoPtr.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "prnetdb.h"
+#include "prio.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/net/DNS.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+
+namespace mozilla { namespace net {
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+typedef void (nsServerSocket:: *nsServerSocketFunc)(void);
+
+static nsresult
+PostEvent(nsServerSocket *s, nsServerSocketFunc func)
+{
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(s, func);
+ if (!gSocketTransportService)
+ return NS_ERROR_FAILURE;
+
+ return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket
+//-----------------------------------------------------------------------------
+
+nsServerSocket::nsServerSocket()
+ : mFD(nullptr)
+ , mLock("nsServerSocket.mLock")
+ , mAttached(false)
+ , mKeepWhenOffline(false)
+{
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService)
+ {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID);
+ }
+ // make sure the STS sticks around as long as we do
+ NS_IF_ADDREF(gSocketTransportService);
+}
+
+nsServerSocket::~nsServerSocket()
+{
+ Close(); // just in case :)
+
+ // release our reference to the STS
+ nsSocketTransportService *serv = gSocketTransportService;
+ NS_IF_RELEASE(serv);
+}
+
+void
+nsServerSocket::OnMsgClose()
+{
+ SOCKET_LOG(("nsServerSocket::OnMsgClose [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition))
+ return;
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then we'll close the socket in our OnSocketDetached.
+ // otherwise, call OnSocketDetached from here.
+ if (!mAttached)
+ OnSocketDetached(mFD);
+}
+
+void
+nsServerSocket::OnMsgAttach()
+{
+ SOCKET_LOG(("nsServerSocket::OnMsgAttach [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition))
+ return;
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition))
+ {
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+}
+
+nsresult
+nsServerSocket::TryAttach()
+{
+ nsresult rv;
+
+ if (!gSocketTransportService)
+ return NS_ERROR_FAILURE;
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket())
+ {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &nsServerSocket::OnMsgAttach);
+ if (!event)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+ return NS_OK;
+}
+
+void
+nsServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+ const NetAddr& aClientAddr)
+{
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport;
+ if (NS_WARN_IF(!trans)) {
+ mCondition = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ nsresult rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ mListener->OnSocketAccepted(this, trans);
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void
+nsServerSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
+{
+ NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
+
+ if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL))
+ {
+ NS_WARNING("error polling on listening socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ PRFileDesc *clientFD;
+ PRNetAddr prClientAddr;
+ NetAddr clientAddr;
+
+ // NSPR doesn't tell us the peer address's length (as provided by the
+ // 'accept' system call), so we can't distinguish between named,
+ // unnamed, and abstract peer addresses. Clear prClientAddr first, so
+ // that the path will at least be reliably empty for unnamed and
+ // abstract addresses, and not garbage when the peer is unnamed.
+ memset(&prClientAddr, 0, sizeof(prClientAddr));
+
+ clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT);
+ PRNetAddrToNetAddr(&prClientAddr, &clientAddr);
+ if (!clientFD) {
+ NS_WARNING("PR_Accept failed");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ // Accept succeeded, create socket transport and notify consumer
+ CreateClientTransport(clientFD, clientAddr);
+}
+
+void
+nsServerSocket::OnSocketDetached(PRFileDesc *fd)
+{
+ // force a failure condition if none set; maybe the STS is shutting down :-/
+ if (NS_SUCCEEDED(mCondition))
+ mCondition = NS_ERROR_ABORT;
+
+ if (mFD)
+ {
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+
+ if (mListener)
+ {
+ mListener->OnStopListening(this, mCondition);
+
+ // need to atomically clear mListener. see our Close() method.
+ RefPtr<nsIServerSocketListener> listener = nullptr;
+ {
+ MutexAutoLock lock(mLock);
+ listener = mListener.forget();
+ }
+
+ // XXX we need to proxy the release to the listener's target thread to work
+ // around bug 337492.
+ if (listener) {
+ NS_ProxyRelease(mListenerTarget, listener.forget());
+ }
+ }
+}
+
+void
+nsServerSocket::IsLocal(bool *aIsLocal)
+{
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ if (mAddr.raw.family == PR_AF_LOCAL)
+ {
+ *aIsLocal = true;
+ return;
+ }
+#endif
+
+ // If bound to loopback, this server socket only accepts local connections.
+ *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback);
+}
+
+void
+nsServerSocket::KeepWhenOffline(bool *aKeepWhenOffline)
+{
+ *aKeepWhenOffline = mKeepWhenOffline;
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsServerSocket, nsIServerSocket)
+
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsIServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog)
+{
+ return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0, aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithFilename(nsIFile *aPath, uint32_t aPermissions, int32_t aBacklog)
+{
+#if defined(XP_UNIX)
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = aPath->GetNativePath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Create a Unix domain PRNetAddr referring to the given path.
+ PRNetAddr addr;
+ if (path.Length() > sizeof(addr.local.path) - 1)
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ addr.local.family = PR_AF_LOCAL;
+ memcpy(addr.local.path, path.get(), path.Length());
+ addr.local.path[path.Length()] = '\0';
+
+ rv = InitWithAddress(&addr, aBacklog);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return aPath->SetPermissions(aPermissions);
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags,
+ int32_t aBackLog)
+{
+ PRNetAddrValue val;
+ PRNetAddr addr;
+
+ if (aPort < 0)
+ aPort = 0;
+ if (aFlags & nsIServerSocket::LoopbackOnly)
+ val = PR_IpAddrLoopback;
+ else
+ val = PR_IpAddrAny;
+ PR_SetNetAddr(val, PR_AF_INET, aPort, &addr);
+
+ mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0);
+ return InitWithAddress(&addr, aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog)
+{
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ nsresult rv;
+
+ //
+ // configure listening socket...
+ //
+
+ mFD = PR_OpenTCPSocket(aAddr->raw.family);
+ if (!mFD)
+ {
+ NS_WARNING("unable to create server socket");
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_Reuseaddr;
+ opt.value.reuse_addr = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ if (PR_Bind(mFD, aAddr) != PR_SUCCESS)
+ {
+ NS_WARNING("failed to bind socket");
+ goto fail;
+ }
+
+ if (aBackLog < 0)
+ aBackLog = 5; // seems like a reasonable default
+
+ if (PR_Listen(mFD, aBackLog) != PR_SUCCESS)
+ {
+ NS_WARNING("cannot listen on socket");
+ goto fail;
+ }
+
+ // get the resulting socket address, which may be different than what
+ // we passed to bind.
+ if (PR_GetSockName(mFD, &mAddr) != PR_SUCCESS)
+ {
+ NS_WARNING("cannot get socket name");
+ goto fail;
+ }
+
+ // Set any additional socket defaults needed by child classes
+ rv = SetSocketDefaults();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ goto fail;
+ }
+
+ // wait until AsyncListen is called before polling the socket for
+ // client connections.
+ return NS_OK;
+
+fail:
+ rv = ErrorAccordingToNSPR(PR_GetError());
+ Close();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsServerSocket::Close()
+{
+ {
+ MutexAutoLock lock(mLock);
+ // we want to proxy the close operation to the socket thread if a listener
+ // has been set. otherwise, we should just close the socket here...
+ if (!mListener)
+ {
+ if (mFD)
+ {
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+ return NS_OK;
+ }
+ }
+ return PostEvent(this, &nsServerSocket::OnMsgClose);
+}
+
+namespace {
+
+class ServerSocketListenerProxy final : public nsIServerSocketListener
+{
+ ~ServerSocketListenerProxy() {}
+
+public:
+ explicit ServerSocketListenerProxy(nsIServerSocketListener* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsIServerSocketListener>(aListener))
+ , mTargetThread(do_GetCurrentThread())
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ class OnSocketAcceptedRunnable : public Runnable
+ {
+ public:
+ OnSocketAcceptedRunnable(const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
+ nsIServerSocket* aServ,
+ nsISocketTransport* aTransport)
+ : mListener(aListener)
+ , mServ(aServ)
+ , mTransport(aTransport)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServ;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ };
+
+ class OnStopListeningRunnable : public Runnable
+ {
+ public:
+ OnStopListeningRunnable(const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
+ nsIServerSocket* aServ,
+ nsresult aStatus)
+ : mListener(aListener)
+ , mServ(aServ)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServ;
+ nsresult mStatus;
+ };
+
+private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+};
+
+NS_IMPL_ISUPPORTS(ServerSocketListenerProxy,
+ nsIServerSocketListener)
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnSocketAccepted(nsIServerSocket* aServ,
+ nsISocketTransport* aTransport)
+{
+ RefPtr<OnSocketAcceptedRunnable> r =
+ new OnSocketAcceptedRunnable(mListener, aServ, aTransport);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnStopListening(nsIServerSocket* aServ,
+ nsresult aStatus)
+{
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aServ, aStatus);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnSocketAcceptedRunnable::Run()
+{
+ mListener->OnSocketAccepted(mServ, mTransport);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnStopListeningRunnable::Run()
+{
+ mListener->OnStopListening(mServ, mStatus);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsServerSocket::AsyncListen(nsIServerSocketListener *aListener)
+{
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListener = new ServerSocketListenerProxy(aListener);
+ mListenerTarget = NS_GetCurrentThread();
+ }
+
+ // Child classes may need to do additional setup just before listening begins
+ nsresult rv = OnSocketListen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return PostEvent(this, &nsServerSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsServerSocket::GetPort(int32_t *aResult)
+{
+ // no need to enter the lock here
+ uint16_t port;
+ if (mAddr.raw.family == PR_AF_INET)
+ port = mAddr.inet.port;
+ else if (mAddr.raw.family == PR_AF_INET6)
+ port = mAddr.ipv6.port;
+ else
+ return NS_ERROR_FAILURE;
+
+ *aResult = static_cast<int32_t>(NetworkEndian::readUint16(&port));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerSocket::GetAddress(PRNetAddr *aResult)
+{
+ // no need to enter the lock here
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsServerSocket.h b/netwerk/base/nsServerSocket.h
new file mode 100644
index 0000000000..ef5f242313
--- /dev/null
+++ b/netwerk/base/nsServerSocket.h
@@ -0,0 +1,67 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsServerSocket_h__
+#define nsServerSocket_h__
+
+#include "prio.h"
+#include "nsASocketHandler.h"
+#include "nsIServerSocket.h"
+#include "mozilla/Mutex.h"
+
+//-----------------------------------------------------------------------------
+
+class nsIEventTarget;
+namespace mozilla { namespace net {
+union NetAddr;
+
+class nsServerSocket : public nsASocketHandler
+ , public nsIServerSocket
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc *fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc *fd) override;
+ virtual void IsLocal(bool *aIsLocal) override;
+ virtual void KeepWhenOffline(bool *aKeepWhenOffline) override;
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+ nsServerSocket();
+
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const mozilla::net::NetAddr& clientAddr);
+ virtual nsresult SetSocketDefaults() { return NS_OK; }
+ virtual nsresult OnSocketListen() { return NS_OK; }
+
+protected:
+ virtual ~nsServerSocket();
+ PRFileDesc* mFD;
+ nsCOMPtr<nsIServerSocketListener> mListener;
+
+private:
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ // lock protects access to mListener; so it is not cleared while being used.
+ mozilla::Mutex mLock;
+ PRNetAddr mAddr;
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached;
+ bool mKeepWhenOffline;
+};
+
+} // namespace net
+} // namespace mozilla
+
+//-----------------------------------------------------------------------------
+
+#endif // nsServerSocket_h__
diff --git a/netwerk/base/nsSimpleNestedURI.cpp b/netwerk/base/nsSimpleNestedURI.cpp
new file mode 100644
index 0000000000..fd3f87a9c2
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.cpp
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsSimpleNestedURI.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+
+#include "mozilla/ipc/URIUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(nsSimpleNestedURI, nsSimpleURI, nsINestedURI)
+
+nsSimpleNestedURI::nsSimpleNestedURI(nsIURI* innerURI)
+ : mInnerURI(innerURI)
+{
+ NS_ASSERTION(innerURI, "Must have inner URI");
+ NS_TryToSetImmutable(innerURI);
+}
+
+// nsISerializable
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv = nsSimpleURI::Read(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(!mMutable, "How did that happen?");
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) return rv;
+
+ mInnerURI = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_TryToSetImmutable(mInnerURI);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Write(nsIObjectOutputStream* aStream)
+{
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(mInnerURI);
+ if (!serializable) {
+ // We can't serialize ourselves
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = nsSimpleURI::Write(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteCompoundObject(mInnerURI, NS_GET_IID(nsIURI),
+ true);
+ return rv;
+}
+
+// nsIIPCSerializableURI
+void
+nsSimpleNestedURI::Serialize(mozilla::ipc::URIParams& aParams)
+{
+ using namespace mozilla::ipc;
+
+ SimpleNestedURIParams params;
+ URIParams simpleParams;
+
+ nsSimpleURI::Serialize(simpleParams);
+ params.simpleParams() = simpleParams;
+
+ SerializeURI(mInnerURI, params.innerURI());
+
+ aParams = params;
+}
+
+bool
+nsSimpleNestedURI::Deserialize(const mozilla::ipc::URIParams& aParams)
+{
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::TSimpleNestedURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SimpleNestedURIParams& params = aParams.get_SimpleNestedURIParams();
+ if (!nsSimpleURI::Deserialize(params.simpleParams()))
+ return false;
+
+ mInnerURI = DeserializeURI(params.innerURI());
+
+ NS_TryToSetImmutable(mInnerURI);
+ return true;
+}
+
+// nsINestedURI
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetInnerURI(nsIURI** uri)
+{
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ return NS_EnsureSafeToReturn(mInnerURI, uri);
+}
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetInnermostURI(nsIURI** uri)
+{
+ return NS_ImplGetInnermostURI(this, uri);
+}
+
+// nsSimpleURI overrides
+/* virtual */ nsresult
+nsSimpleNestedURI::EqualsInternal(nsIURI* other,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ bool* result)
+{
+ *result = false;
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ if (other) {
+ bool correctScheme;
+ nsresult rv = other->SchemeIs(mScheme.get(), &correctScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (correctScheme) {
+ nsCOMPtr<nsINestedURI> nest = do_QueryInterface(other);
+ if (nest) {
+ nsCOMPtr<nsIURI> otherInner;
+ rv = nest->GetInnerURI(getter_AddRefs(otherInner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return (refHandlingMode == eHonorRef) ?
+ otherInner->Equals(mInnerURI, result) :
+ otherInner->EqualsExceptRef(mInnerURI, result);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsSimpleURI*
+nsSimpleNestedURI::StartClone(nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef)
+{
+ NS_ENSURE_TRUE(mInnerURI, nullptr);
+
+ nsCOMPtr<nsIURI> innerClone;
+ nsresult rv;
+ if (refHandlingMode == eHonorRef) {
+ rv = mInnerURI->Clone(getter_AddRefs(innerClone));
+ } else if (refHandlingMode == eReplaceRef) {
+ rv = mInnerURI->CloneWithNewRef(newRef, getter_AddRefs(innerClone));
+ } else {
+ rv = mInnerURI->CloneIgnoringRef(getter_AddRefs(innerClone));
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsSimpleNestedURI* url = new nsSimpleNestedURI(innerClone);
+ SetRefOnClone(url, refHandlingMode, newRef);
+ url->SetMutable(false);
+
+ return url;
+}
+
+// nsIClassInfo overrides
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ static NS_DEFINE_CID(kSimpleNestedURICID, NS_SIMPLENESTEDURI_CID);
+
+ *aClassIDNoAlloc = kSimpleNestedURICID;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleNestedURI.h b/netwerk/base/nsSimpleNestedURI.h
new file mode 100644
index 0000000000..2402ba4d32
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * URI class to be used for cases when a simple URI actually resolves to some
+ * other sort of URI, with the latter being what's loaded when the load
+ * happens. All objects of this class should always be immutable, so that the
+ * inner URI and this URI don't get out of sync. The Clone() implementation
+ * will guarantee this for the clone, but it's up to the protocol handlers
+ * creating these URIs to ensure that in the first place. The innerURI passed
+ * to this URI will be set immutable if possible.
+ */
+
+#ifndef nsSimpleNestedURI_h__
+#define nsSimpleNestedURI_h__
+
+#include "nsCOMPtr.h"
+#include "nsSimpleURI.h"
+#include "nsINestedURI.h"
+
+#include "nsIIPCSerializableURI.h"
+
+namespace mozilla {
+namespace net {
+
+class nsSimpleNestedURI : public nsSimpleURI,
+ public nsINestedURI
+{
+protected:
+ ~nsSimpleNestedURI() {}
+
+public:
+ // To be used by deserialization only. Leaves this object in an
+ // uninitialized state that will throw on most accesses.
+ nsSimpleNestedURI()
+ {
+ }
+
+ // Constructor that should generally be used when constructing an object of
+ // this class with |operator new|.
+ explicit nsSimpleNestedURI(nsIURI* innerURI);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINESTEDURI
+
+ // Overrides for various methods nsSimpleURI implements follow.
+
+ // nsSimpleURI overrides
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result) override;
+ virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) override;
+
+ // nsISerializable overrides
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream) override;
+
+ // nsIIPCSerializableURI overrides
+ NS_DECL_NSIIPCSERIALIZABLEURI
+
+ // Override the nsIClassInfo method GetClassIDNoAlloc to make sure our
+ // nsISerializable impl works right.
+ NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) override;
+
+protected:
+ nsCOMPtr<nsIURI> mInnerURI;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* nsSimpleNestedURI_h__ */
diff --git a/netwerk/base/nsSimpleStreamListener.cpp b/netwerk/base/nsSimpleStreamListener.cpp
new file mode 100644
index 0000000000..da00de2819
--- /dev/null
+++ b/netwerk/base/nsSimpleStreamListener.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSimpleStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+//
+//----------------------------------------------------------------------------
+// nsISupports implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMPL_ISUPPORTS(nsSimpleStreamListener,
+ nsISimpleStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+//
+//----------------------------------------------------------------------------
+// nsIRequestObserver implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ return mObserver ?
+ mObserver->OnStartRequest(aRequest, aContext) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleStreamListener::OnStopRequest(nsIRequest* request,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ return mObserver ?
+ mObserver->OnStopRequest(request, aContext, aStatus) : NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// nsIStreamListener implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::OnDataAvailable(nsIRequest* request,
+ nsISupports *aContext,
+ nsIInputStream *aSource,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t writeCount;
+ nsresult rv = mSink->WriteFrom(aSource, aCount, &writeCount);
+ //
+ // Equate zero bytes read and NS_SUCCEEDED to stopping the read.
+ //
+ if (NS_SUCCEEDED(rv) && (writeCount == 0))
+ return NS_BASE_STREAM_CLOSED;
+ return rv;
+}
+
+//
+//----------------------------------------------------------------------------
+// nsISimpleStreamListener implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::Init(nsIOutputStream *aSink,
+ nsIRequestObserver *aObserver)
+{
+ NS_PRECONDITION(aSink, "null output stream");
+
+ mSink = aSink;
+ mObserver = aObserver;
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleStreamListener.h b/netwerk/base/nsSimpleStreamListener.h
new file mode 100644
index 0000000000..05f78a5bb9
--- /dev/null
+++ b/netwerk/base/nsSimpleStreamListener.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleStreamListener_h__
+#define nsSimpleStreamListener_h__
+
+#include "nsISimpleStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class nsSimpleStreamListener : public nsISimpleStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISIMPLESTREAMLISTENER
+
+ nsSimpleStreamListener() { }
+
+protected:
+ virtual ~nsSimpleStreamListener() {}
+
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsSimpleURI.cpp b/netwerk/base/nsSimpleURI.cpp
new file mode 100644
index 0000000000..ae5c51a1e5
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.cpp
@@ -0,0 +1,847 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#undef LOG
+#include "IPCMessageUtils.h"
+
+#include "nsSimpleURI.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "plstr.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsEscape.h"
+#include "nsError.h"
+#include "nsIIPCSerializableURI.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisSimpleURIImplementationCID,
+ NS_THIS_SIMPLEURI_IMPLEMENTATION_CID);
+static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// nsSimpleURI methods:
+
+nsSimpleURI::nsSimpleURI()
+ : mMutable(true)
+ , mIsRefValid(false)
+ , mIsQueryValid(false)
+{
+}
+
+nsSimpleURI::~nsSimpleURI()
+{
+}
+
+NS_IMPL_ADDREF(nsSimpleURI)
+NS_IMPL_RELEASE(nsSimpleURI)
+NS_INTERFACE_TABLE_HEAD(nsSimpleURI)
+NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsIURIWithQuery, nsISerializable,
+ nsIClassInfo, nsIMutable, nsIIPCSerializableURI)
+NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ if (aIID.Equals(kThisSimpleURIImplementationCID))
+ foundInterface = static_cast<nsIURI*>(this);
+ else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISerializable methods:
+
+NS_IMETHODIMP
+nsSimpleURI::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv;
+
+ bool isMutable;
+ rv = aStream->ReadBoolean(&isMutable);
+ if (NS_FAILED(rv)) return rv;
+ mMutable = isMutable;
+
+ rv = aStream->ReadCString(mScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->ReadCString(mPath);
+ if (NS_FAILED(rv)) return rv;
+
+ bool isRefValid;
+ rv = aStream->ReadBoolean(&isRefValid);
+ if (NS_FAILED(rv)) return rv;
+ mIsRefValid = isRefValid;
+
+ if (isRefValid) {
+ rv = aStream->ReadCString(mRef);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+ }
+
+ bool isQueryValid;
+ rv = aStream->ReadBoolean(&isQueryValid);
+ if (NS_FAILED(rv)) return rv;
+ mIsQueryValid = isQueryValid;
+
+ if (isQueryValid) {
+ rv = aStream->ReadCString(mQuery);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Write(nsIObjectOutputStream* aStream)
+{
+ nsresult rv;
+
+ rv = aStream->WriteBoolean(mMutable);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteStringZ(mScheme.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteStringZ(mPath.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteBoolean(mIsRefValid);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mIsRefValid) {
+ rv = aStream->WriteStringZ(mRef.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = aStream->WriteBoolean(mIsQueryValid);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mIsQueryValid) {
+ rv = aStream->WriteStringZ(mQuery.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIIPCSerializableURI methods:
+
+void
+nsSimpleURI::Serialize(URIParams& aParams)
+{
+ SimpleURIParams params;
+
+ params.scheme() = mScheme;
+ params.path() = mPath;
+
+ if (mIsRefValid) {
+ params.ref() = mRef;
+ } else {
+ params.ref().SetIsVoid(true);
+ }
+
+ if (mIsQueryValid) {
+ params.query() = mQuery;
+ } else {
+ params.query().SetIsVoid(true);
+ }
+
+ params.isMutable() = mMutable;
+
+ aParams = params;
+}
+
+bool
+nsSimpleURI::Deserialize(const URIParams& aParams)
+{
+ if (aParams.type() != URIParams::TSimpleURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SimpleURIParams& params = aParams.get_SimpleURIParams();
+
+ mScheme = params.scheme();
+ mPath = params.path();
+
+ if (params.ref().IsVoid()) {
+ mRef.Truncate();
+ mIsRefValid = false;
+ } else {
+ mRef = params.ref();
+ mIsRefValid = true;
+ }
+
+ if (params.query().IsVoid()) {
+ mQuery.Truncate();
+ mIsQueryValid = false;
+ } else {
+ mQuery = params.query();
+ mIsQueryValid = true;
+ }
+
+ mMutable = params.isMutable();
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURI methods:
+
+NS_IMETHODIMP
+nsSimpleURI::GetSpec(nsACString &result)
+{
+ if (!result.Assign(mScheme, fallible) ||
+ !result.Append(NS_LITERAL_CSTRING(":"), fallible) ||
+ !result.Append(mPath, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mIsQueryValid) {
+ if (!result.Append(NS_LITERAL_CSTRING("?"), fallible) ||
+ !result.Append(mQuery, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+ }
+
+ if (mIsRefValid) {
+ if (!result.Append(NS_LITERAL_CSTRING("#"), fallible) ||
+ !result.Append(mRef, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ }
+
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsSimpleURI::GetSpecIgnoringRef(nsACString &result)
+{
+ result = mScheme + NS_LITERAL_CSTRING(":") + mPath;
+ if (mIsQueryValid) {
+ result += NS_LITERAL_CSTRING("?") + mQuery;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHasRef(bool *result)
+{
+ *result = mIsRefValid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetSpec(const nsACString &aSpec)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredSpec;
+ net_FilterURIString(aSpec, filteredSpec);
+
+ // nsSimpleURI currently restricts the charset to US-ASCII
+ nsAutoCString spec;
+ nsresult rv = NS_EscapeURL(filteredSpec, esc_OnlyNonASCII, spec, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t colonPos = spec.FindChar(':');
+ if (colonPos < 0 || !net_IsValidScheme(spec.get(), colonPos))
+ return NS_ERROR_MALFORMED_URI;
+
+ mScheme.Truncate();
+ DebugOnly<int32_t> n = spec.Left(mScheme, colonPos);
+ NS_ASSERTION(n == colonPos, "Left failed");
+ ToLowerCase(mScheme);
+
+ // This sets mPath, mQuery and mRef.
+ return SetPath(Substring(spec, colonPos + 1));
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetScheme(nsACString &result)
+{
+ result = mScheme;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetScheme(const nsACString &scheme)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(scheme);
+ if (!net_IsValidScheme(flat)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ mScheme = scheme;
+ ToLowerCase(mScheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPrePath(nsACString &result)
+{
+ result = mScheme + NS_LITERAL_CSTRING(":");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetUserPass(nsACString &result)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetUserPass(const nsACString &userPass)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetUsername(nsACString &result)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetUsername(const nsACString &userName)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPassword(nsACString &result)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetPassword(const nsACString &password)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHostPort(nsACString &result)
+{
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code may depend on this throwing.
+ // Note: If this is changed, change GetAsciiHostPort as well.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetHostPort(const nsACString &result)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetHostAndPort(const nsACString &result)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHost(nsACString &result)
+{
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code depend on this throwing.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetHost(const nsACString &host)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPort(int32_t *result)
+{
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code may depend on this throwing.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetPort(int32_t port)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPath(nsACString &result)
+{
+ result = mPath;
+ if (mIsQueryValid) {
+ result += NS_LITERAL_CSTRING("?") + mQuery;
+ }
+ if (mIsRefValid) {
+ result += NS_LITERAL_CSTRING("#") + mRef;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetPath(const nsACString &aPath)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ nsAutoCString path;
+ if (!path.Assign(aPath, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ int32_t queryPos = path.FindChar('?');
+ int32_t hashPos = path.FindChar('#');
+
+ if (queryPos != kNotFound && hashPos != kNotFound && hashPos < queryPos) {
+ queryPos = kNotFound;
+ }
+
+ nsAutoCString query;
+ if (queryPos != kNotFound) {
+ query.Assign(Substring(path, queryPos));
+ path.Truncate(queryPos);
+ }
+
+ nsAutoCString hash;
+ if (hashPos != kNotFound) {
+ if (query.IsEmpty()) {
+ hash.Assign(Substring(path, hashPos));
+ path.Truncate(hashPos);
+ } else {
+ // We have to search the hash character in the query
+ hashPos = query.FindChar('#');
+ hash.Assign(Substring(query, hashPos));
+ query.Truncate(hashPos);
+ }
+ }
+
+ mIsQueryValid = false;
+ mQuery.Truncate();
+
+ mIsRefValid = false;
+ mRef.Truncate();
+
+ // The path
+ if (!mPath.Assign(path, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = SetQuery(query);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return SetRef(hash);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetRef(nsACString &result)
+{
+ if (!mIsRefValid) {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ result.Truncate();
+ } else {
+ result = mRef;
+ }
+
+ return NS_OK;
+}
+
+// NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty
+// string (and will result in .spec and .path having a terminal #).
+NS_IMETHODIMP
+nsSimpleURI::SetRef(const nsACString &aRef)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ nsAutoCString ref;
+ nsresult rv = NS_EscapeURL(aRef, esc_OnlyNonASCII, ref, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (ref.IsEmpty()) {
+ // Empty string means to remove ref completely.
+ mIsRefValid = false;
+ mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+ return NS_OK;
+ }
+
+ mIsRefValid = true;
+
+ // Gracefully skip initial hash
+ if (ref[0] == '#') {
+ mRef = Substring(ref, 1);
+ } else {
+ mRef = ref;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Equals(nsIURI* other, bool *result)
+{
+ return EqualsInternal(other, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::EqualsExceptRef(nsIURI* other, bool *result)
+{
+ return EqualsInternal(other, eIgnoreRef, result);
+}
+
+/* virtual */ nsresult
+nsSimpleURI::EqualsInternal(nsIURI* other,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ bool* result)
+{
+ NS_ENSURE_ARG_POINTER(other);
+ NS_PRECONDITION(result, "null pointer");
+
+ RefPtr<nsSimpleURI> otherUri;
+ nsresult rv = other->QueryInterface(kThisSimpleURIImplementationCID,
+ getter_AddRefs(otherUri));
+ if (NS_FAILED(rv)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ *result = EqualsInternal(otherUri, refHandlingMode);
+ return NS_OK;
+}
+
+bool
+nsSimpleURI::EqualsInternal(nsSimpleURI* otherUri, RefHandlingEnum refHandlingMode)
+{
+ bool result = (mScheme == otherUri->mScheme &&
+ mPath == otherUri->mPath);
+
+ if (result) {
+ result = (mIsQueryValid == otherUri->mIsQueryValid &&
+ (!mIsQueryValid || mQuery == otherUri->mQuery));
+ }
+
+ if (result && refHandlingMode == eHonorRef) {
+ result = (mIsRefValid == otherUri->mIsRefValid &&
+ (!mIsRefValid || mRef == otherUri->mRef));
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SchemeIs(const char *i_Scheme, bool *o_Equals)
+{
+ NS_ENSURE_ARG_POINTER(o_Equals);
+ if (!i_Scheme) return NS_ERROR_NULL_POINTER;
+
+ const char *this_scheme = mScheme.get();
+
+ // mScheme is guaranteed to be lower case.
+ if (*i_Scheme == *this_scheme || *i_Scheme == (*this_scheme - ('a' - 'A')) ) {
+ *o_Equals = PL_strcasecmp(this_scheme, i_Scheme) ? false : true;
+ } else {
+ *o_Equals = false;
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsSimpleURI*
+nsSimpleURI::StartClone(nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef)
+{
+ nsSimpleURI* url = new nsSimpleURI();
+ SetRefOnClone(url, refHandlingMode, newRef);
+ return url;
+}
+
+/* virtual */ void
+nsSimpleURI::SetRefOnClone(nsSimpleURI* url,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef)
+{
+ if (refHandlingMode == eHonorRef) {
+ url->mRef = mRef;
+ url->mIsRefValid = mIsRefValid;
+ } else if (refHandlingMode == eReplaceRef) {
+ url->SetRef(newRef);
+ }
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Clone(nsIURI** result)
+{
+ return CloneInternal(eHonorRef, EmptyCString(), result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::CloneIgnoringRef(nsIURI** result)
+{
+ return CloneInternal(eIgnoreRef, EmptyCString(), result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::CloneWithNewRef(const nsACString &newRef, nsIURI** result)
+{
+ return CloneInternal(eReplaceRef, newRef, result);
+}
+
+nsresult
+nsSimpleURI::CloneInternal(nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString &newRef,
+ nsIURI** result)
+{
+ RefPtr<nsSimpleURI> url = StartClone(refHandlingMode, newRef);
+ if (!url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Note: |url| may well have mMutable false at this point, so
+ // don't call any setter methods.
+ url->mScheme = mScheme;
+ url->mPath = mPath;
+
+ url->mIsQueryValid = mIsQueryValid;
+ if (url->mIsQueryValid) {
+ url->mQuery = mQuery;
+ }
+
+ url.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Resolve(const nsACString &relativePath, nsACString &result)
+{
+ result = relativePath;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiSpec(nsACString &result)
+{
+ nsAutoCString buf;
+ nsresult rv = GetSpec(buf);
+ if (NS_FAILED(rv)) return rv;
+ return NS_EscapeURL(buf, esc_OnlyNonASCII|esc_AlwaysCopy, result, fallible);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiHostPort(nsACString &result)
+{
+ // XXX This behavior mimics GetHostPort.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiHost(nsACString &result)
+{
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetOriginCharset(nsACString &result)
+{
+ result.Truncate();
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSimpleURI::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ *count = 0;
+ *array = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetContractID(char * *aContractID)
+{
+ // Make sure to modify any subclasses as needed if this ever
+ // changes.
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetClassID(nsCID * *aClassID)
+{
+ // Make sure to modify any subclasses as needed if this ever
+ // changes to not call the virtual GetClassIDNoAlloc.
+ *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
+ if (!*aClassID)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return GetClassIDNoAlloc(*aClassID);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetFlags(uint32_t *aFlags)
+{
+ *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kSimpleURICID;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsISimpleURI
+//----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsSimpleURI::GetMutable(bool *value)
+{
+ *value = mMutable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetMutable(bool value)
+{
+ NS_ENSURE_ARG(mMutable || !value);
+
+ mMutable = value;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t
+nsSimpleURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mQuery.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mRef.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+size_t
+nsSimpleURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsIURIWithQuery
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSimpleURI::GetFilePath(nsACString& aFilePath)
+{
+ aFilePath = mPath;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetFilePath(const nsACString& aFilePath)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetQuery(nsACString& aQuery)
+{
+ if (!mIsQueryValid) {
+ MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+ aQuery.Truncate();
+ } else {
+ aQuery = mQuery;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetQuery(const nsACString& aQuery)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ nsAutoCString query;
+ nsresult rv = NS_EscapeURL(aQuery, esc_OnlyNonASCII, query, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (query.IsEmpty()) {
+ // Empty string means to remove query completely.
+ mIsQueryValid = false;
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+ return NS_OK;
+ }
+
+ mIsQueryValid = true;
+
+ // Gracefully skip initial question mark
+ if (query[0] == '?') {
+ mQuery = Substring(query, 1);
+ } else {
+ mQuery = query;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleURI.h b/netwerk/base/nsSimpleURI.h
new file mode 100644
index 0000000000..29bc9b3139
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleURI_h__
+#define nsSimpleURI_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "nsIURI.h"
+#include "nsIURIWithQuery.h"
+#include "nsISerializable.h"
+#include "nsString.h"
+#include "nsIClassInfo.h"
+#include "nsIMutable.h"
+#include "nsISizeOf.h"
+#include "nsIIPCSerializableURI.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_THIS_SIMPLEURI_IMPLEMENTATION_CID \
+{ /* 0b9bb0c2-fee6-470b-b9b9-9fd9462b5e19 */ \
+ 0x0b9bb0c2, \
+ 0xfee6, \
+ 0x470b, \
+ {0xb9, 0xb9, 0x9f, 0xd9, 0x46, 0x2b, 0x5e, 0x19} \
+}
+
+class nsSimpleURI
+ : public nsIURIWithQuery
+ , public nsISerializable
+ , public nsIClassInfo
+ , public nsIMutable
+ , public nsISizeOf
+ , public nsIIPCSerializableURI
+{
+protected:
+ virtual ~nsSimpleURI();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIURIWITHQUERY
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIMUTABLE
+ NS_DECL_NSIIPCSERIALIZABLEURI
+
+ // nsSimpleURI methods:
+
+ nsSimpleURI();
+
+ // nsISizeOf
+ // Among the sub-classes that inherit (directly or indirectly) from
+ // nsSimpleURI, measurement of the following members may be added later if
+ // DMD finds it is worthwhile:
+ // - nsJSURI: mBaseURI
+ // - nsSimpleNestedURI: mInnerURI
+ // - nsBlobURI: mPrincipal
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+protected:
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ // Helper to be used by inherited classes who want to test
+ // equality given an assumed nsSimpleURI. This must NOT check
+ // the passed-in other for QI to our CID.
+ bool EqualsInternal(nsSimpleURI* otherUri, RefHandlingEnum refHandlingMode);
+
+ // Used by StartClone (and versions of StartClone in subclasses) to
+ // handle the ref in the right way for clones.
+ void SetRefOnClone(nsSimpleURI* url, RefHandlingEnum refHandlingMode,
+ const nsACString& newRef);
+
+ // NOTE: This takes the refHandlingMode as an argument because
+ // nsSimpleNestedURI's specialized version needs to know how to clone
+ // its inner URI.
+ virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef);
+
+ // Helper to share code between Clone methods.
+ virtual nsresult CloneInternal(RefHandlingEnum refHandlingMode,
+ const nsACString &newRef,
+ nsIURI** clone);
+
+ nsCString mScheme;
+ nsCString mPath; // NOTE: mPath does not include ref, as an optimization
+ nsCString mRef; // so that URIs with different refs can share string data.
+ nsCString mQuery; // so that URLs with different querys can share string data.
+ bool mMutable;
+ bool mIsRefValid; // To distinguish between empty-ref and no-ref.
+ bool mIsQueryValid; // To distinguish between empty-query and no-query.
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsSimpleURI_h__
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
new file mode 100644
index 0000000000..1bfd1fc915
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -0,0 +1,3245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransport2.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Telemetry.h"
+#include "nsIOService.h"
+#include "nsStreamUtils.h"
+#include "nsNetSegmentUtils.h"
+#include "nsNetAddr.h"
+#include "nsTransportUtils.h"
+#include "nsProxyInfo.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "plstr.h"
+#include "prerr.h"
+#include "NetworkActivityMonitor.h"
+#include "NSSErrorsService.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsThreadUtils.h"
+#include "nsISocketProviderService.h"
+#include "nsISocketProvider.h"
+#include "nsISSLSocketControl.h"
+#include "nsIPipe.h"
+#include "nsIClassInfoImpl.h"
+#include "nsURLHelper.h"
+#include "nsIDNSService.h"
+#include "nsIDNSRecord.h"
+#include "nsICancelable.h"
+#include <algorithm>
+
+#include "nsPrintfCString.h"
+#include "xpcpublic.h"
+
+#if defined(XP_WIN)
+#include "mozilla/WindowsVersion.h"
+#include "ShutdownLayer.h"
+#endif
+
+/* Following inclusions required for keepalive config not supported by NSPR. */
+#include "private/pprio.h"
+#if defined(XP_WIN)
+#include <winsock2.h>
+#include <mstcpip.h>
+#elif defined(XP_UNIX)
+#include <errno.h>
+#include <netinet/tcp.h>
+#endif
+/* End keepalive config inclusions. */
+
+#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0
+#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1
+#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2
+#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3
+
+//-----------------------------------------------------------------------------
+
+static NS_DEFINE_CID(kSocketProviderServiceCID, NS_SOCKETPROVIDERSERVICE_CID);
+static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsSocketEvent : public Runnable
+{
+public:
+ nsSocketEvent(nsSocketTransport *transport, uint32_t type,
+ nsresult status = NS_OK, nsISupports *param = nullptr)
+ : mTransport(transport)
+ , mType(type)
+ , mStatus(status)
+ , mParam(param)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mTransport->OnSocketEvent(mType, mStatus, mParam);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsSocketTransport> mTransport;
+
+ uint32_t mType;
+ nsresult mStatus;
+ nsCOMPtr<nsISupports> mParam;
+};
+
+//-----------------------------------------------------------------------------
+
+//#define TEST_CONNECT_ERRORS
+#ifdef TEST_CONNECT_ERRORS
+#include <stdlib.h>
+static PRErrorCode RandomizeConnectError(PRErrorCode code)
+{
+ //
+ // To test out these errors, load http://www.yahoo.com/. It should load
+ // correctly despite the random occurrence of these errors.
+ //
+ int n = rand();
+ if (n > RAND_MAX/2) {
+ struct {
+ PRErrorCode err_code;
+ const char *err_name;
+ }
+ errors[] = {
+ //
+ // These errors should be recoverable provided there is another
+ // IP address in mDNSRecord.
+ //
+ { PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR" },
+ { PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR" },
+ //
+ // This error will cause this socket transport to error out;
+ // however, if the consumer is HTTP, then the HTTP transaction
+ // should be restarted when this error occurs.
+ //
+ { PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR" },
+ };
+ n = n % (sizeof(errors)/sizeof(errors[0]));
+ code = errors[n].err_code;
+ SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
+ }
+ return code;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult
+ErrorAccordingToNSPR(PRErrorCode errorCode)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ switch (errorCode) {
+ case PR_WOULD_BLOCK_ERROR:
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ break;
+ case PR_CONNECT_ABORTED_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ rv = NS_ERROR_NET_RESET;
+ break;
+ case PR_END_OF_FILE_ERROR: // XXX document this correlation
+ rv = NS_ERROR_NET_INTERRUPT;
+ break;
+ case PR_CONNECT_REFUSED_ERROR:
+ // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
+ // could get better diagnostics by adding distinct XPCOM error codes for
+ // each of these, but there are a lot of places in Gecko that check
+ // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
+ // be checked.
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_HOST_UNREACHABLE_ERROR:
+ case PR_ADDRESS_NOT_AVAILABLE_ERROR:
+ // Treat EACCES as a soft error since (at least on Linux) connect() returns
+ // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case PR_ADDRESS_NOT_SUPPORTED_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+ break;
+ case PR_IO_TIMEOUT_ERROR:
+ case PR_CONNECT_TIMEOUT_ERROR:
+ rv = NS_ERROR_NET_TIMEOUT;
+ break;
+ case PR_OUT_OF_MEMORY_ERROR:
+ // These really indicate that the descriptor table filled up, or that the
+ // kernel ran out of network buffers - but nobody really cares which part of
+ // the system ran out of memory.
+ case PR_PROC_DESC_TABLE_FULL_ERROR:
+ case PR_SYS_DESC_TABLE_FULL_ERROR:
+ case PR_INSUFFICIENT_RESOURCES_ERROR:
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ case PR_ADDRESS_IN_USE_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
+ break;
+ // These filename-related errors can arise when using Unix-domain sockets.
+ case PR_FILE_NOT_FOUND_ERROR:
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ break;
+ case PR_IS_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_IS_DIRECTORY;
+ break;
+ case PR_LOOP_ERROR:
+ rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ break;
+ case PR_NAME_TOO_LONG_ERROR:
+ rv = NS_ERROR_FILE_NAME_TOO_LONG;
+ break;
+ case PR_NO_DEVICE_SPACE_ERROR:
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ case PR_NOT_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_NOT_DIRECTORY;
+ break;
+ case PR_READ_ONLY_FILESYSTEM_ERROR:
+ rv = NS_ERROR_FILE_READ_ONLY;
+ break;
+ default:
+ if (psm::IsNSSErrorCode(errorCode)) {
+ rv = psm::GetXPCOMFromNSSError(errorCode);
+ }
+ break;
+
+ // NSPR's socket code can return these, but they're not worth breaking out
+ // into their own error codes, distinct from NS_ERROR_FAILURE:
+ //
+ // PR_BAD_DESCRIPTOR_ERROR
+ // PR_INVALID_ARGUMENT_ERROR
+ // PR_NOT_SOCKET_ERROR
+ // PR_NOT_TCP_SOCKET_ERROR
+ // These would indicate a bug internal to the component.
+ //
+ // PR_PROTOCOL_NOT_SUPPORTED_ERROR
+ // This means that we can't use the given "protocol" (like
+ // IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
+ // above, this indicates an internal bug.
+ //
+ // PR_IS_CONNECTED_ERROR
+ // This indicates that we've applied a system call like 'bind' or
+ // 'connect' to a socket that is already connected. The socket
+ // components manage each file descriptor's state, and in some cases
+ // handle this error result internally. We shouldn't be returning
+ // this to our callers.
+ //
+ // PR_IO_ERROR
+ // This is so vague that NS_ERROR_FAILURE is just as good.
+ }
+ SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%x]\n", errorCode, rv));
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// socket input stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketInputStream::nsSocketInputStream(nsSocketTransport *trans)
+ : mTransport(trans)
+ , mReaderRefCnt(0)
+ , mCondition(NS_OK)
+ , mCallbackFlags(0)
+ , mByteCount(0)
+{
+}
+
+nsSocketInputStream::~nsSocketInputStream()
+{
+}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void
+nsSocketInputStream::OnSocketReady(nsresult condition)
+{
+ SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%x]\n",
+ this, condition));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition))
+ mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = mCallback;
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback)
+ callback->OnInputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketInputStream,
+ nsIInputStream,
+ nsIAsyncInputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::AddRef()
+{
+ ++mReaderRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::Release()
+{
+ if (--mReaderRefCnt == 0)
+ Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Close()
+{
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Available(uint64_t *avail)
+{
+ SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this));
+
+ *avail = 0;
+
+ PRFileDesc *fd;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition))
+ return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd)
+ return NS_OK;
+ }
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Available(fd);
+
+ // PSM does not implement PR_Available() so do a best approximation of it
+ // with MSG_PEEK
+ if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) {
+ char c;
+
+ n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+ SOCKET_LOG(("nsSocketInputStream::Available [this=%p] "
+ "using PEEK backup n=%d]\n", this, n));
+ }
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n >= 0)
+ *avail = n;
+ else {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR)
+ return NS_OK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnInputClosed(rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Read(char *buf, uint32_t count, uint32_t *countRead)
+{
+ SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition))
+ return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Read [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Read(fd, buf, count);
+
+ SOCKET_LOG((" PR_Read returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0)
+ mTransport->TraceInBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0)
+ mByteCount += (*countRead = n);
+ else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnInputClosed(rv);
+
+ // only send this notification if we have indeed read some data.
+ // see bug 196827 for an example of why this is important.
+ if (n > 0)
+ mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *countRead)
+{
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::IsNonBlocking(bool *nonblocking)
+{
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::CloseWithStatus(nsresult reason)
+{
+ SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%x]\n", this, reason));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition))
+ rv = mCondition = reason;
+ else
+ rv = NS_OK;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnInputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::AsyncWait(nsIInputStreamCallback *callback,
+ uint32_t flags,
+ uint32_t amount,
+ nsIEventTarget *target)
+{
+ SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this));
+
+ bool hasError = false;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewInputStreamReadyEvent(callback, target);
+ }
+ else
+ mCallback = callback;
+ mCallbackFlags = flags;
+
+ hasError = NS_FAILED(mCondition);
+ } // unlock mTransport->mLock
+
+ if (hasError) {
+ // OnSocketEvent will call OnInputStreamReady with an error code after
+ // going through the event loop. We do this because most socket callers
+ // do not expect AsyncWait() to synchronously execute the OnInputStreamReady
+ // callback.
+ mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING);
+ } else {
+ mTransport->OnInputPending();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket output stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport *trans)
+ : mTransport(trans)
+ , mWriterRefCnt(0)
+ , mCondition(NS_OK)
+ , mCallbackFlags(0)
+ , mByteCount(0)
+{
+}
+
+nsSocketOutputStream::~nsSocketOutputStream()
+{
+}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void
+nsSocketOutputStream::OnSocketReady(nsresult condition)
+{
+ SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%x]\n",
+ this, condition));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ nsCOMPtr<nsIOutputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition))
+ mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = mCallback;
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback)
+ callback->OnOutputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream,
+ nsIOutputStream,
+ nsIAsyncOutputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::AddRef()
+{
+ ++mWriterRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::Release()
+{
+ if (--mWriterRefCnt == 0)
+ Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Close()
+{
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Write(const char *buf, uint32_t count, uint32_t *countWritten)
+{
+ SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count));
+
+ *countWritten = 0;
+
+ // A write of 0 bytes can be used to force the initial SSL handshake, so do
+ // not reject that.
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition))
+ return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Write [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Write(fd, buf, count);
+
+ SOCKET_LOG((" PR_Write returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0)
+ mTransport->TraceOutBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0)
+ mByteCount += (*countWritten = n);
+ else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnOutputClosed(rv);
+
+ // only send this notification if we have indeed written some data.
+ // see bug 196827 for an example of why this is important.
+ if (n > 0)
+ mTransport->SendStatus(NS_NET_STATUS_SENDING_TO);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::WriteSegments(nsReadSegmentFun reader, void *closure,
+ uint32_t count, uint32_t *countRead)
+{
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsSocketOutputStream::WriteFromSegments(nsIInputStream *input,
+ void *closure,
+ const char *fromSegment,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ nsSocketOutputStream *self = (nsSocketOutputStream *) closure;
+ return self->Write(fromSegment, count, countRead);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::WriteFrom(nsIInputStream *stream, uint32_t count, uint32_t *countRead)
+{
+ return stream->ReadSegments(WriteFromSegments, this, count, countRead);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::IsNonBlocking(bool *nonblocking)
+{
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::CloseWithStatus(nsresult reason)
+{
+ SOCKET_LOG(("nsSocketOutputStream::CloseWithStatus [this=%p reason=%x]\n", this, reason));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition))
+ rv = mCondition = reason;
+ else
+ rv = NS_OK;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnOutputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::AsyncWait(nsIOutputStreamCallback *callback,
+ uint32_t flags,
+ uint32_t amount,
+ nsIEventTarget *target)
+{
+ SOCKET_LOG(("nsSocketOutputStream::AsyncWait [this=%p]\n", this));
+
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewOutputStreamReadyEvent(callback, target);
+ }
+ else
+ mCallback = callback;
+
+ mCallbackFlags = flags;
+ }
+ mTransport->OnOutputPending();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket transport impl
+//-----------------------------------------------------------------------------
+
+nsSocketTransport::nsSocketTransport()
+ : mTypes(nullptr)
+ , mTypeCount(0)
+ , mPort(0)
+ , mProxyPort(0)
+ , mOriginPort(0)
+ , mProxyTransparent(false)
+ , mProxyTransparentResolvesHost(false)
+ , mHttpsProxy(false)
+ , mConnectionFlags(0)
+ , mState(STATE_CLOSED)
+ , mAttached(false)
+ , mInputClosed(true)
+ , mOutputClosed(true)
+ , mResolving(false)
+ , mNetAddrIsSet(false)
+ , mSelfAddrIsSet(false)
+ , mNetAddrPreResolved(false)
+ , mLock("nsSocketTransport.mLock")
+ , mFD(this)
+ , mFDref(0)
+ , mFDconnected(false)
+ , mSocketTransportService(gSocketTransportService)
+ , mInput(this)
+ , mOutput(this)
+ , mQoSBits(0x00)
+ , mKeepaliveEnabled(false)
+ , mKeepaliveIdleTimeS(-1)
+ , mKeepaliveRetryIntervalS(-1)
+ , mKeepaliveProbeCount(-1)
+ , mDoNotRetryToConnect(false)
+{
+ SOCKET_LOG(("creating nsSocketTransport @%p\n", this));
+
+ mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout
+ mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout
+}
+
+nsSocketTransport::~nsSocketTransport()
+{
+ SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
+
+ CleanupTypes();
+}
+
+void
+nsSocketTransport::CleanupTypes()
+{
+ // cleanup socket type info
+ if (mTypes) {
+ for (uint32_t i = 0; i < mTypeCount; ++i) {
+ PL_strfree(mTypes[i]);
+ }
+ free(mTypes);
+ mTypes = nullptr;
+ }
+ mTypeCount = 0;
+}
+
+nsresult
+nsSocketTransport::Init(const char **types, uint32_t typeCount,
+ const nsACString &host, uint16_t port,
+ const nsACString &hostRoute, uint16_t portRoute,
+ nsIProxyInfo *givenProxyInfo)
+{
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ // init socket type info
+
+ mOriginHost = host;
+ mOriginPort = port;
+ if (!hostRoute.IsEmpty()) {
+ mHost = hostRoute;
+ mPort = portRoute;
+ } else {
+ mHost = host;
+ mPort = port;
+ }
+
+ if (proxyInfo) {
+ mHttpsProxy = proxyInfo->IsHTTPS();
+ }
+
+ const char *proxyType = nullptr;
+ mProxyInfo = proxyInfo;
+ if (proxyInfo) {
+ mProxyPort = proxyInfo->Port();
+ mProxyHost = proxyInfo->Host();
+ // grab proxy type (looking for "socks" for example)
+ proxyType = proxyInfo->Type();
+ if (proxyType && (proxyInfo->IsHTTP() ||
+ proxyInfo->IsHTTPS() ||
+ proxyInfo->IsDirect() ||
+ !strcmp(proxyType, "unknown"))) {
+ proxyType = nullptr;
+ }
+ }
+
+ SOCKET_LOG(("nsSocketTransport::Init [this=%p host=%s:%hu origin=%s:%d proxy=%s:%hu]\n",
+ this, mHost.get(), mPort, mOriginHost.get(), mOriginPort,
+ mProxyHost.get(), mProxyPort));
+
+ // include proxy type as a socket type if proxy type is not "http"
+ mTypeCount = typeCount + (proxyType != nullptr);
+ if (!mTypeCount)
+ return NS_OK;
+
+ // if we have socket types, then the socket provider service had
+ // better exist!
+ nsresult rv;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ do_GetService(kSocketProviderServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ mTypes = (char **) malloc(mTypeCount * sizeof(char *));
+ if (!mTypes)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // now verify that each socket type has a registered socket provider.
+ for (uint32_t i = 0, type = 0; i < mTypeCount; ++i) {
+ // store socket types
+ if (i == 0 && proxyType)
+ mTypes[i] = PL_strdup(proxyType);
+ else
+ mTypes[i] = PL_strdup(types[type++]);
+
+ if (!mTypes[i]) {
+ mTypeCount = i;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsCOMPtr<nsISocketProvider> provider;
+ rv = spserv->GetSocketProvider(mTypes[i], getter_AddRefs(provider));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("no registered socket provider");
+ return rv;
+ }
+
+ // note if socket type corresponds to a transparent proxy
+ // XXX don't hardcode SOCKS here (use proxy info's flags instead).
+ if ((strcmp(mTypes[i], "socks") == 0) ||
+ (strcmp(mTypes[i], "socks4") == 0)) {
+ mProxyTransparent = true;
+
+ if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ // we want the SOCKS layer to send the hostname
+ // and port to the proxy and let it do the DNS.
+ mProxyTransparentResolvesHost = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::InitPreResolved(const char **socketTypes, uint32_t typeCount,
+ const nsACString &host, uint16_t port,
+ const nsACString &hostRoute, uint16_t portRoute,
+ nsIProxyInfo *proxyInfo,
+ const mozilla::net::NetAddr* addr)
+{
+ nsresult rv = Init(socketTypes, typeCount, host, port, hostRoute, portRoute, proxyInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mNetAddr = *addr;
+ mNetAddrPreResolved = true;
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::InitWithFilename(const char *filename)
+{
+#if defined(XP_UNIX)
+ size_t filenameLength = strlen(filename);
+
+ if (filenameLength > sizeof(mNetAddr.local.path) - 1)
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+
+ mHost.Assign(filename);
+ mPort = 0;
+ mTypeCount = 0;
+
+ mNetAddr.local.family = AF_LOCAL;
+ memcpy(mNetAddr.local.path, filename, filenameLength);
+ mNetAddr.local.path[filenameLength] = '\0';
+ mNetAddrIsSet = true;
+
+ return NS_OK;
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+nsresult
+nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
+
+ char buf[kNetAddrMaxCStrBufSize];
+ NetAddrToString(addr, buf, sizeof(buf));
+ mHost.Assign(buf);
+
+ uint16_t port;
+ if (addr->raw.family == AF_INET)
+ port = addr->inet.port;
+ else if (addr->raw.family == AF_INET6)
+ port = addr->inet6.port;
+ else
+ port = 0;
+ mPort = ntohs(port);
+
+ memcpy(&mNetAddr, addr, sizeof(NetAddr));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ mState = STATE_TRANSFERRING;
+ SetSocketName(fd);
+ mNetAddrIsSet = true;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = 1;
+ }
+
+ // make sure new socket is non-blocking
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(fd, &opt);
+
+ SOCKET_LOG(("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n",
+ this, mHost.get(), mPort));
+
+ // jump to InitiateSocket to get ourselves attached to the STS poll list.
+ return PostEvent(MSG_RETRY_INIT_SOCKET);
+}
+
+nsresult
+nsSocketTransport::InitWithConnectedSocket(PRFileDesc* aFD,
+ const NetAddr* aAddr,
+ nsISupports* aSecInfo)
+{
+ mSecInfo = aSecInfo;
+ return InitWithConnectedSocket(aFD, aAddr);
+}
+
+nsresult
+nsSocketTransport::PostEvent(uint32_t type, nsresult status, nsISupports *param)
+{
+ SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%x param=%p]\n",
+ this, type, status, param));
+
+ nsCOMPtr<nsIRunnable> event = new nsSocketEvent(this, type, status, param);
+ if (!event)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void
+nsSocketTransport::SendStatus(nsresult status)
+{
+ SOCKET_LOG(("nsSocketTransport::SendStatus [this=%p status=%x]\n", this, status));
+
+ nsCOMPtr<nsITransportEventSink> sink;
+ uint64_t progress;
+ {
+ MutexAutoLock lock(mLock);
+ sink = mEventSink;
+ switch (status) {
+ case NS_NET_STATUS_SENDING_TO:
+ progress = mOutput.ByteCount();
+ break;
+ case NS_NET_STATUS_RECEIVING_FROM:
+ progress = mInput.ByteCount();
+ break;
+ default:
+ progress = 0;
+ break;
+ }
+ }
+ if (sink) {
+ sink->OnTransportStatus(this, status, progress, -1);
+ }
+}
+
+nsresult
+nsSocketTransport::ResolveHost()
+{
+ SOCKET_LOG(("nsSocketTransport::ResolveHost [this=%p %s:%d%s]\n",
+ this, SocketHost().get(), SocketPort(),
+ mConnectionFlags & nsSocketTransport::BYPASS_CACHE ?
+ " bypass cache" : ""));
+
+ nsresult rv;
+
+ if (mNetAddrPreResolved) {
+ mState = STATE_RESOLVING;
+ return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+ }
+
+ if (!mProxyHost.IsEmpty()) {
+ if (!mProxyTransparent || mProxyTransparentResolvesHost) {
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with proxies");
+#endif
+ // When not resolving mHost locally, we still want to ensure that
+ // it only contains valid characters. See bug 304904 for details.
+ // Sometimes the end host is not yet known and mHost is *
+ if (!net_IsValidHostName(mHost) &&
+ !mHost.Equals(NS_LITERAL_CSTRING("*"))) {
+ SOCKET_LOG((" invalid hostname %s\n", mHost.get()));
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ }
+ if (mProxyTransparentResolvesHost) {
+ // Name resolution is done on the server side. Just pretend
+ // client resolution is complete, this will get picked up later.
+ // since we don't need to do DNS now, we bypass the resolving
+ // step by initializing mNetAddr to an empty address, but we
+ // must keep the port. The SOCKS IO layer will use the hostname
+ // we send it when it's created, rather than the empty address
+ // we send with the connect call.
+ mState = STATE_RESOLVING;
+ mNetAddr.raw.family = AF_INET;
+ mNetAddr.inet.port = htons(SocketPort());
+ mNetAddr.inet.ip = htonl(INADDR_ANY);
+ return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+ }
+ }
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(kDNSServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ mResolving = true;
+
+ uint32_t dnsFlags = 0;
+ if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE)
+ dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6)
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4)
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+
+ NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+
+ SendStatus(NS_NET_STATUS_RESOLVING_HOST);
+
+ if (!SocketHost().Equals(mOriginHost)) {
+ SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n",
+ this, mOriginHost.get(), SocketHost().get()));
+ }
+ rv = dns->AsyncResolveExtended(SocketHost(), dnsFlags, mNetworkInterfaceId, this,
+ nullptr, getter_AddRefs(mDNSRequest));
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
+ mState = STATE_RESOLVING;
+ }
+ return rv;
+}
+
+nsresult
+nsSocketTransport::BuildSocket(PRFileDesc *&fd, bool &proxyTransparent, bool &usingSSL)
+{
+ SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this));
+
+ nsresult rv;
+
+ proxyTransparent = false;
+ usingSSL = false;
+
+ if (mTypeCount == 0) {
+ fd = PR_OpenTCPSocket(mNetAddr.raw.family);
+ rv = fd ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+ else {
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with socket types");
+#endif
+
+ fd = nullptr;
+
+ nsCOMPtr<nsISocketProviderService> spserv =
+ do_GetService(kSocketProviderServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // by setting host to mOriginHost, instead of mHost we send the
+ // SocketProvider (e.g. PSM) the origin hostname but can still do DNS
+ // on an explicit alternate service host name
+ const char *host = mOriginHost.get();
+ int32_t port = (int32_t) mOriginPort;
+ nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;
+ uint32_t controlFlags = 0;
+
+ uint32_t i;
+ for (i=0; i<mTypeCount; ++i) {
+ nsCOMPtr<nsISocketProvider> provider;
+
+ SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i]));
+
+ rv = spserv->GetSocketProvider(mTypes[i], getter_AddRefs(provider));
+ if (NS_FAILED(rv))
+ break;
+
+ if (mProxyTransparentResolvesHost)
+ controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST;
+
+ if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT)
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
+
+ if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE)
+ controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+
+ if (mConnectionFlags & nsISocketTransport::MITM_OK)
+ controlFlags |= nsISocketProvider::MITM_OK;
+
+ if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE)
+ controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
+
+ nsCOMPtr<nsISupports> secinfo;
+ if (i == 0) {
+ // if this is the first type, we'll want the
+ // service to allocate a new socket
+
+ // when https proxying we want to just connect to the proxy as if
+ // it were the end host (i.e. expect the proxy's cert)
+
+ rv = provider->NewSocket(mNetAddr.raw.family,
+ mHttpsProxy ? mProxyHost.get() : host,
+ mHttpsProxy ? mProxyPort : port,
+ proxyInfo, mOriginAttributes,
+ controlFlags, &fd,
+ getter_AddRefs(secinfo));
+
+ if (NS_SUCCEEDED(rv) && !fd) {
+ NS_NOTREACHED("NewSocket succeeded but failed to create a PRFileDesc");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ else {
+ // the socket has already been allocated,
+ // so we just want the service to add itself
+ // to the stack (such as pushing an io layer)
+ rv = provider->AddToSocket(mNetAddr.raw.family,
+ host, port, proxyInfo,
+ mOriginAttributes, controlFlags, fd,
+ getter_AddRefs(secinfo));
+ }
+
+ // controlFlags = 0; not used below this point...
+ if (NS_FAILED(rv))
+ break;
+
+ // if the service was ssl or starttls, we want to hold onto the socket info
+ bool isSSL = (strcmp(mTypes[i], "ssl") == 0);
+ if (isSSL || (strcmp(mTypes[i], "starttls") == 0)) {
+ // remember security info and give notification callbacks to PSM...
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mLock);
+ mSecInfo = secinfo;
+ callbacks = mCallbacks;
+ SOCKET_LOG((" [secinfo=%x callbacks=%x]\n", mSecInfo.get(), mCallbacks.get()));
+ }
+ // don't call into PSM while holding mLock!!
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
+ if (secCtrl)
+ secCtrl->SetNotificationCallbacks(callbacks);
+ // remember if socket type is SSL so we can ProxyStartSSL if need be.
+ usingSSL = isSSL;
+ }
+ else if ((strcmp(mTypes[i], "socks") == 0) ||
+ (strcmp(mTypes[i], "socks4") == 0)) {
+ // since socks is transparent, any layers above
+ // it do not have to worry about proxy stuff
+ proxyInfo = nullptr;
+ proxyTransparent = true;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG((" error pushing io layer [%u:%s rv=%x]\n", i, mTypes[i], rv));
+ if (fd) {
+ CloseSocket(fd,
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsSocketTransport::InitiateSocket()
+{
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
+
+ nsresult rv;
+ bool isLocal;
+ IsLocal(&isLocal);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_ABORT;
+ }
+ if (gIOService->IsOffline()) {
+ if (!isLocal)
+ return NS_ERROR_OFFLINE;
+ } else if (!isLocal) {
+
+#ifdef DEBUG
+ // all IP networking has to be done from the parent
+ if (NS_SUCCEEDED(mCondition) &&
+ ((mNetAddr.raw.family == AF_INET) || (mNetAddr.raw.family == AF_INET6))) {
+ MOZ_ASSERT(!IsNeckoChild());
+ }
+#endif
+
+ if (NS_SUCCEEDED(mCondition) &&
+ xpc::AreNonLocalConnectionsDisabled() &&
+ !(IsIPAddrAny(&mNetAddr) || IsIPAddrLocal(&mNetAddr))) {
+ nsAutoCString ipaddr;
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr);
+ netaddr->GetAddress(ipaddr);
+ fprintf_stderr(stderr,
+ "FATAL ERROR: Non-local network connections are disabled and a connection "
+ "attempt to %s (%s) was made.\nYou should only access hostnames "
+ "available via the test networking proxy (if running mochitests) "
+ "or from a test-specific httpd.js server (if running xpcshell tests). "
+ "Browser services should be disabled or redirected to a local server.\n",
+ mHost.get(), ipaddr.get());
+ MOZ_CRASH("Attempting to connect to non-local address!");
+ }
+ }
+
+ // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 &&
+ IsIPAddrLocal(&mNetAddr)) {
+ if (SOCKET_LOG_ENABLED()) {
+ nsAutoCString netAddrCString;
+ netAddrCString.SetCapacity(kIPv6CStrBufSize);
+ if (!NetAddrToString(&mNetAddr,
+ netAddrCString.BeginWriting(),
+ kIPv6CStrBufSize))
+ netAddrCString = NS_LITERAL_CSTRING("<IP-to-string failed>");
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket skipping "
+ "speculative connection for host [%s:%d] proxy "
+ "[%s:%d] with Local IP address [%s]",
+ mHost.get(), mPort, mProxyHost.get(), mProxyPort,
+ netAddrCString.get()));
+ }
+ mCondition = NS_ERROR_CONNECTION_REFUSED;
+ OnSocketDetached(nullptr);
+ return mCondition;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!mSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET);
+ if (!event)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return mSocketTransportService->NotifyWhenCanAttachSocket(event);
+ }
+
+ //
+ // if we already have a connected socket, then just attach and return.
+ //
+ if (mFD.IsInitialized()) {
+ rv = mSocketTransportService->AttachSocket(mFD, this);
+ if (NS_SUCCEEDED(rv))
+ mAttached = true;
+ return rv;
+ }
+
+ //
+ // create new socket fd, push io layers, etc.
+ //
+ PRFileDesc *fd;
+ bool proxyTransparent;
+ bool usingSSL;
+
+ rv = BuildSocket(fd, proxyTransparent, usingSSL);
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG((" BuildSocket failed [rv=%x]\n", rv));
+ return rv;
+ }
+
+ // Attach network activity monitor
+ NetworkActivityMonitor::AttachIOLayer(fd);
+
+ PRStatus status;
+
+ // Make the socket non-blocking...
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ status = PR_SetSocketOption(fd, &opt);
+ NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking");
+
+ // disable the nagle algorithm - if we rely on it to coalesce writes into
+ // full packets the final packet of a multi segment POST/PUT or pipeline
+ // sequence is delayed a full rtt
+ opt.option = PR_SockOpt_NoDelay;
+ opt.value.no_delay = true;
+ PR_SetSocketOption(fd, &opt);
+
+ // if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF
+ // The Windows default of 8KB is too small and as of vista sp1, autotuning
+ // only applies to receive window
+ int32_t sndBufferSize;
+ mSocketTransportService->GetSendBufferSize(&sndBufferSize);
+ if (sndBufferSize > 0) {
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = sndBufferSize;
+ PR_SetSocketOption(fd, &opt);
+ }
+
+ if (mQoSBits) {
+ opt.option = PR_SockOpt_IpTypeOfService;
+ opt.value.tos = mQoSBits;
+ PR_SetSocketOption(fd, &opt);
+ }
+
+#if defined(XP_WIN)
+ // The linger is turned off by default. This is not a hard close, but
+ // closesocket should return immediately and operating system tries to send
+ // remaining data for certain, implementation specific, amount of time.
+ // https://msdn.microsoft.com/en-us/library/ms739165.aspx
+ //
+ // Turn the linger option on an set the interval to 0. This will cause hard
+ // close of the socket.
+ opt.option = PR_SockOpt_Linger;
+ opt.value.linger.polarity = 1;
+ opt.value.linger.linger = 0;
+ PR_SetSocketOption(fd, &opt);
+#endif
+
+ // inform socket transport about this newly created socket...
+ rv = mSocketTransportService->AttachSocket(fd, this);
+ if (NS_FAILED(rv)) {
+ CloseSocket(fd,
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return rv;
+ }
+ mAttached = true;
+
+ // assign mFD so that we can properly handle OnSocketDetached before we've
+ // established a connection.
+ {
+ MutexAutoLock lock(mLock);
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = false;
+ }
+
+ SOCKET_LOG((" advancing to STATE_CONNECTING\n"));
+ mState = STATE_CONNECTING;
+ mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
+ SendStatus(NS_NET_STATUS_CONNECTING_TO);
+
+ if (SOCKET_LOG_ENABLED()) {
+ char buf[kNetAddrMaxCStrBufSize];
+ NetAddrToString(&mNetAddr, buf, sizeof(buf));
+ SOCKET_LOG((" trying address: %s\n", buf));
+ }
+
+ //
+ // Initiate the connect() to the host...
+ //
+ PRNetAddr prAddr;
+ {
+ if (mBindAddr) {
+ MutexAutoLock lock(mLock);
+ NetAddrToPRNetAddr(mBindAddr.get(), &prAddr);
+ status = PR_Bind(fd, &prAddr);
+ if (status != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ mBindAddr = nullptr;
+ }
+ }
+
+ NetAddrToPRNetAddr(&mNetAddr, &prAddr);
+
+#ifdef XP_WIN
+ // Find the real tcp socket and set non-blocking once again!
+ // Bug 1158189.
+ PRFileDesc *bottom = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
+ if (bottom) {
+ PROsfd osfd = PR_FileDesc2NativeHandle(bottom);
+ u_long nonblocking = 1;
+ if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) {
+ NS_WARNING("Socket could not be set non-blocking!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+#endif
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime connectStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ connectStarted = PR_IntervalNow();
+ }
+
+ status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted) {
+ SendPRBlockingTelemetry(connectStarted,
+ Telemetry::PRCONNECT_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECT_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECT_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECT_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECT_BLOCKING_TIME_OFFLINE);
+ }
+
+ if (status == PR_SUCCESS) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+ }
+ else {
+ PRErrorCode code = PR_GetError();
+#if defined(TEST_CONNECT_ERRORS)
+ code = RandomizeConnectError(code);
+#endif
+ //
+ // If the PR_Connect(...) would block, then poll for a connection.
+ //
+ if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code))
+ mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
+ //
+ // If the socket is already connected, then return success...
+ //
+ else if (PR_IS_CONNECTED_ERROR == code) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+
+ if (mSecInfo && !mProxyHost.IsEmpty() && proxyTransparent && usingSSL) {
+ // if the connection phase is finished, and the ssl layer has
+ // been pushed, and we were proxying (transparently; ie. nothing
+ // has to happen in the protocol layer above us), it's time for
+ // the ssl to start doing it's thing.
+ nsCOMPtr<nsISSLSocketControl> secCtrl =
+ do_QueryInterface(mSecInfo);
+ if (secCtrl) {
+ SOCKET_LOG((" calling ProxyStartSSL()\n"));
+ secCtrl->ProxyStartSSL();
+ }
+ // XXX what if we were forced to poll on the socket for a successful
+ // connection... wouldn't we need to call ProxyStartSSL after a call
+ // to PR_ConnectContinue indicates that we are connected?
+ //
+ // XXX this appears to be what the old socket transport did. why
+ // isn't this broken?
+ }
+ }
+ //
+ // A SOCKS request was rejected; get the actual error code from
+ // the OS error
+ //
+ else if (PR_UNKNOWN_ERROR == code &&
+ mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ rv = ErrorAccordingToNSPR(code);
+ }
+ //
+ // The connection was refused...
+ //
+ else {
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted) {
+ SendPRBlockingTelemetry(connectStarted,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_OFFLINE);
+ }
+
+ rv = ErrorAccordingToNSPR(code);
+ if ((rv == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty())
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ }
+ }
+ return rv;
+}
+
+bool
+nsSocketTransport::RecoverFromError()
+{
+ NS_ASSERTION(NS_FAILED(mCondition), "there should be something wrong");
+
+ SOCKET_LOG(("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%x]\n",
+ this, mState, mCondition));
+
+ if (mDoNotRetryToConnect) {
+ SOCKET_LOG(("nsSocketTransport::RecoverFromError do not retry because "
+ "mDoNotRetryToConnect is set [this=%p]\n",
+ this));
+ return false;
+ }
+
+#if defined(XP_UNIX)
+ // Unix domain connections don't have multiple addresses to try,
+ // so the recovery techniques here don't apply.
+ if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL)
+ return false;
+#endif
+
+ // can only recover from errors in these states
+ if (mState != STATE_RESOLVING && mState != STATE_CONNECTING)
+ return false;
+
+ nsresult rv;
+
+ // OK to check this outside mLock
+ NS_ASSERTION(!mFDconnected, "socket should not be connected");
+
+ // all connection failures need to be reported to DNS so that the next
+ // time we will use a different address if available.
+ if (mState == STATE_CONNECTING && mDNSRecord) {
+ mDNSRecord->ReportUnusable(SocketPort());
+ }
+
+ // can only recover from these errors
+ if (mCondition != NS_ERROR_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_NET_TIMEOUT &&
+ mCondition != NS_ERROR_UNKNOWN_HOST &&
+ mCondition != NS_ERROR_UNKNOWN_PROXY_HOST)
+ return false;
+
+ bool tryAgain = false;
+
+ if ((mState == STATE_CONNECTING) && mDNSRecord &&
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ if (mNetAddr.raw.family == AF_INET) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
+ } else if (mNetAddr.raw.family == AF_INET6) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
+ }
+ }
+
+ if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4) &&
+ mCondition == NS_ERROR_UNKNOWN_HOST &&
+ mState == STATE_RESOLVING &&
+ !mProxyTransparentResolvesHost) {
+ SOCKET_LOG((" trying lookup again with both ipv4/ipv6 enabled\n"));
+ mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4);
+ tryAgain = true;
+ }
+
+ // try next ip address only if past the resolver stage...
+ if (mState == STATE_CONNECTING && mDNSRecord) {
+ nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" trying again with next ip address\n"));
+ tryAgain = true;
+ }
+ else if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4)) {
+ // Drop state to closed. This will trigger new round of DNS
+ // resolving bellow.
+ // XXX Could be optimized to only switch the flags to save duplicate
+ // connection attempts.
+ SOCKET_LOG((" failed to connect all ipv4-only or ipv6-only hosts,"
+ " trying lookup/connect again with both ipv4/ipv6\n"));
+ mState = STATE_CLOSED;
+ mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4);
+ tryAgain = true;
+ }
+ }
+
+ // prepare to try again.
+ if (tryAgain) {
+ uint32_t msg;
+
+ if (mState == STATE_CONNECTING) {
+ mState = STATE_RESOLVING;
+ msg = MSG_DNS_LOOKUP_COMPLETE;
+ }
+ else {
+ mState = STATE_CLOSED;
+ msg = MSG_ENSURE_CONNECT;
+ }
+
+ rv = PostEvent(msg, NS_OK);
+ if (NS_FAILED(rv))
+ tryAgain = false;
+ }
+
+ return tryAgain;
+}
+
+// called on the socket thread only
+void
+nsSocketTransport::OnMsgInputClosed(nsresult reason)
+{
+ SOCKET_LOG(("nsSocketTransport::OnMsgInputClosed [this=%p reason=%x]\n",
+ this, reason));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ mInputClosed = true;
+ // check if event should affect entire transport
+ if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED))
+ mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
+ else if (mOutputClosed)
+ mCondition = NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
+ else {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags &= ~PR_POLL_READ;
+ mInput.OnSocketReady(reason);
+ }
+}
+
+// called on the socket thread only
+void
+nsSocketTransport::OnMsgOutputClosed(nsresult reason)
+{
+ SOCKET_LOG(("nsSocketTransport::OnMsgOutputClosed [this=%p reason=%x]\n",
+ this, reason));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ mOutputClosed = true;
+ // check if event should affect entire transport
+ if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED))
+ mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
+ else if (mInputClosed)
+ mCondition = NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
+ else {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags &= ~PR_POLL_WRITE;
+ mOutput.OnSocketReady(reason);
+ }
+}
+
+void
+nsSocketTransport::OnSocketConnected()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ SOCKET_LOG((" advancing to STATE_TRANSFERRING\n"));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ mState = STATE_TRANSFERRING;
+
+ // Set the m*AddrIsSet flags only when state has reached TRANSFERRING
+ // because we need to make sure its value does not change due to failover
+ mNetAddrIsSet = true;
+
+ // assign mFD (must do this within the transport lock), but take care not
+ // to trample over mFDref if mFD is already set.
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(mFD.IsInitialized(), "no socket");
+ NS_ASSERTION(mFDref == 1, "wrong socket ref count");
+ SetSocketName(mFD);
+ mFDconnected = true;
+
+#ifdef XP_WIN
+ if (!IsWin2003OrLater()) { // windows xp
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ if (PR_GetSocketOption(mFD, &opt) == PR_SUCCESS) {
+ SOCKET_LOG(("%p checking rwin on xp originally=%u\n",
+ this, opt.value.recv_buffer_size));
+ if (opt.value.recv_buffer_size < 65535) {
+ opt.value.recv_buffer_size = 65535;
+ PR_SetSocketOption(mFD, &opt);
+ }
+ }
+ }
+#endif
+ }
+
+ // Ensure keepalive is configured correctly if previously enabled.
+ if (mKeepaliveEnabled) {
+ nsresult rv = SetKeepaliveEnabledInternal(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%x]", rv));
+ }
+ }
+
+ SendStatus(NS_NET_STATUS_CONNECTED_TO);
+}
+
+void
+nsSocketTransport::SetSocketName(PRFileDesc *fd)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ if (mSelfAddrIsSet) {
+ return;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ if (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) {
+ PRNetAddrToNetAddr(&prAddr, &mSelfAddr);
+ mSelfAddrIsSet = true;
+ }
+}
+
+PRFileDesc *
+nsSocketTransport::GetFD_Locked()
+{
+ mLock.AssertCurrentThreadOwns();
+
+ // mFD is not available to the streams while disconnected.
+ if (!mFDconnected)
+ return nullptr;
+
+ if (mFD.IsInitialized())
+ mFDref++;
+
+ return mFD;
+}
+
+class ThunkPRClose : public Runnable
+{
+public:
+ explicit ThunkPRClose(PRFileDesc *fd) : mFD(fd) {}
+
+ NS_IMETHOD Run() override
+ {
+ nsSocketTransport::CloseSocket(mFD,
+ gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return NS_OK;
+ }
+private:
+ PRFileDesc *mFD;
+};
+
+void
+STS_PRCloseOnSocketTransport(PRFileDesc *fd)
+{
+ if (gSocketTransportService) {
+ // Can't PR_Close() a socket off STS thread. Thunk it to STS to die
+ // FIX - Should use RUN_ON_THREAD once it's generally available
+ // RUN_ON_THREAD(gSocketThread,WrapRunnableNM(&PR_Close, mFD);
+ gSocketTransportService->Dispatch(new ThunkPRClose(fd), NS_DISPATCH_NORMAL);
+ } else {
+ // something horrible has happened
+ NS_ASSERTION(gSocketTransportService, "No STS service");
+ }
+}
+
+void
+nsSocketTransport::ReleaseFD_Locked(PRFileDesc *fd)
+{
+ mLock.AssertCurrentThreadOwns();
+
+ NS_ASSERTION(mFD == fd, "wrong fd");
+ SOCKET_LOG(("JIMB: ReleaseFD_Locked: mFDref = %d\n", mFDref));
+
+ if (--mFDref == 0) {
+ if (gIOService->IsNetTearingDown() &&
+ ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
+ gSocketTransportService->MaxTimeForPrClosePref())) {
+ // If shutdown last to long, let the socket leak and do not close it.
+ SOCKET_LOG(("Intentional leak"));
+ } else if (PR_GetCurrentThread() == gSocketThread) {
+ SOCKET_LOG(("nsSocketTransport: calling PR_Close [this=%p]\n", this));
+ CloseSocket(mFD,
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ } else {
+ // Can't PR_Close() a socket off STS thread. Thunk it to STS to die
+ STS_PRCloseOnSocketTransport(mFD);
+ }
+ mFD = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// socket event handler impl
+
+void
+nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status, nsISupports *param)
+{
+ SOCKET_LOG(("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%x param=%p]\n",
+ this, type, status, param));
+
+ if (NS_FAILED(mCondition)) {
+ // block event since we're apparently already dead.
+ SOCKET_LOG((" blocking event [condition=%x]\n", mCondition));
+ //
+ // notify input/output streams in case either has a pending notify.
+ //
+ mInput.OnSocketReady(mCondition);
+ mOutput.OnSocketReady(mCondition);
+ return;
+ }
+
+ switch (type) {
+ case MSG_ENSURE_CONNECT:
+ SOCKET_LOG((" MSG_ENSURE_CONNECT\n"));
+ //
+ // ensure that we have created a socket, attached it, and have a
+ // connection.
+ //
+ if (mState == STATE_CLOSED) {
+ // Unix domain sockets are ready to connect; mNetAddr is all we
+ // need. Internet address families require a DNS lookup (or possibly
+ // several) before we can connect.
+#if defined(XP_UNIX)
+ if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL)
+ mCondition = InitiateSocket();
+ else
+#endif
+ mCondition = ResolveHost();
+
+ } else {
+ SOCKET_LOG((" ignoring redundant event\n"));
+ }
+ break;
+
+ case MSG_DNS_LOOKUP_COMPLETE:
+ if (mDNSRequest) // only send this if we actually resolved anything
+ SendStatus(NS_NET_STATUS_RESOLVED_HOST);
+
+ SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n"));
+ mDNSRequest = nullptr;
+ if (param) {
+ mDNSRecord = static_cast<nsIDNSRecord *>(param);
+ mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
+ }
+ // status contains DNS lookup status
+ if (NS_FAILED(status)) {
+ // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
+ // proxy host is not found, so we fixup the error code.
+ // For SOCKS proxies (mProxyTransparent == true), the socket
+ // transport resolves the real host here, so there's no fixup
+ // (see bug 226943).
+ if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent &&
+ !mProxyHost.IsEmpty())
+ mCondition = NS_ERROR_UNKNOWN_PROXY_HOST;
+ else
+ mCondition = status;
+ }
+ else if (mState == STATE_RESOLVING) {
+ mCondition = InitiateSocket();
+ }
+ break;
+
+ case MSG_RETRY_INIT_SOCKET:
+ mCondition = InitiateSocket();
+ break;
+
+ case MSG_INPUT_CLOSED:
+ SOCKET_LOG((" MSG_INPUT_CLOSED\n"));
+ OnMsgInputClosed(status);
+ break;
+
+ case MSG_INPUT_PENDING:
+ SOCKET_LOG((" MSG_INPUT_PENDING\n"));
+ OnMsgInputPending();
+ break;
+
+ case MSG_OUTPUT_CLOSED:
+ SOCKET_LOG((" MSG_OUTPUT_CLOSED\n"));
+ OnMsgOutputClosed(status);
+ break;
+
+ case MSG_OUTPUT_PENDING:
+ SOCKET_LOG((" MSG_OUTPUT_PENDING\n"));
+ OnMsgOutputPending();
+ break;
+ case MSG_TIMEOUT_CHANGED:
+ SOCKET_LOG((" MSG_TIMEOUT_CHANGED\n"));
+ mPollTimeout = mTimeouts[(mState == STATE_TRANSFERRING)
+ ? TIMEOUT_READ_WRITE : TIMEOUT_CONNECT];
+ break;
+ default:
+ SOCKET_LOG((" unhandled event!\n"));
+ }
+
+ if (NS_FAILED(mCondition)) {
+ SOCKET_LOG((" after event [this=%p cond=%x]\n", this, mCondition));
+ if (!mAttached) // need to process this error ourselves...
+ OnSocketDetached(nullptr);
+ }
+ else if (mPollFlags == PR_POLL_EXCEPT)
+ mPollFlags = 0; // make idle
+}
+
+//-----------------------------------------------------------------------------
+// socket handler impl
+
+void
+nsSocketTransport::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
+{
+ SOCKET_LOG(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n",
+ this, outFlags));
+
+ if (outFlags == -1) {
+ SOCKET_LOG(("socket timeout expired\n"));
+ mCondition = NS_ERROR_NET_TIMEOUT;
+ return;
+ }
+
+ if (mState == STATE_TRANSFERRING) {
+ // if waiting to write and socket is writable or hit an exception.
+ if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) {
+ // assume that we won't need to poll any longer (the stream will
+ // request that we poll again if it is still pending).
+ mPollFlags &= ~PR_POLL_WRITE;
+ mOutput.OnSocketReady(NS_OK);
+ }
+ // if waiting to read and socket is readable or hit an exception.
+ if ((mPollFlags & PR_POLL_READ) && (outFlags & ~PR_POLL_WRITE)) {
+ // assume that we won't need to poll any longer (the stream will
+ // request that we poll again if it is still pending).
+ mPollFlags &= ~PR_POLL_READ;
+ mInput.OnSocketReady(NS_OK);
+ }
+ // Update poll timeout in case it was changed
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+ else if ((mState == STATE_CONNECTING) && !gIOService->IsNetTearingDown()) {
+ // We do not need to do PR_ConnectContinue when we are already
+ // shutting down.
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime connectStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ connectStarted = PR_IntervalNow();
+ }
+
+ PRStatus status = PR_ConnectContinue(fd, outFlags);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted) {
+ SendPRBlockingTelemetry(connectStarted,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_OFFLINE);
+ }
+
+ if (status == PR_SUCCESS) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ if (mNetAddr.raw.family == AF_INET) {
+ Telemetry::Accumulate(
+ Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
+ } else if (mNetAddr.raw.family == AF_INET6) {
+ Telemetry::Accumulate(
+ Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
+ }
+ }
+ }
+ else {
+ PRErrorCode code = PR_GetError();
+#if defined(TEST_CONNECT_ERRORS)
+ code = RandomizeConnectError(code);
+#endif
+ //
+ // If the connect is still not ready, then continue polling...
+ //
+ if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
+ // Set up the select flags for connect...
+ mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
+ // Update poll timeout in case it was changed
+ mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
+ }
+ //
+ // The SOCKS proxy rejected our request. Find out why.
+ //
+ else if (PR_UNKNOWN_ERROR == code &&
+ mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ else {
+ //
+ // else, the connection failed...
+ //
+ mCondition = ErrorAccordingToNSPR(code);
+ if ((mCondition == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty())
+ mCondition = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ SOCKET_LOG((" connection failed! [reason=%x]\n", mCondition));
+ }
+ }
+ }
+ else if ((mState == STATE_CONNECTING) && gIOService->IsNetTearingDown()) {
+ // We do not need to do PR_ConnectContinue when we are already
+ // shutting down.
+ SOCKET_LOG(("We are in shutdown so skip PR_ConnectContinue and set "
+ "and error.\n"));
+ mCondition = NS_ERROR_ABORT;
+ }
+ else {
+ NS_ERROR("unexpected socket state");
+ mCondition = NS_ERROR_UNEXPECTED;
+ }
+
+ if (mPollFlags == PR_POLL_EXCEPT)
+ mPollFlags = 0; // make idle
+}
+
+// called on the socket thread only
+void
+nsSocketTransport::OnSocketDetached(PRFileDesc *fd)
+{
+ SOCKET_LOG(("nsSocketTransport::OnSocketDetached [this=%p cond=%x]\n",
+ this, mCondition));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ // if we didn't initiate this detach, then be sure to pass an error
+ // condition up to our consumers. (e.g., STS is shutting down.)
+ if (NS_SUCCEEDED(mCondition)) {
+ if (gIOService->IsOffline()) {
+ mCondition = NS_ERROR_OFFLINE;
+ }
+ else {
+ mCondition = NS_ERROR_ABORT;
+ }
+ }
+
+ // If we are not shutting down try again.
+ if (!gIOService->IsNetTearingDown() && RecoverFromError())
+ mCondition = NS_OK;
+ else {
+ mState = STATE_CLOSED;
+
+ // make sure there isn't any pending DNS request
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+
+ //
+ // notify input/output streams
+ //
+ mInput.OnSocketReady(mCondition);
+ mOutput.OnSocketReady(mCondition);
+ }
+
+ // break any potential reference cycle between the security info object
+ // and ourselves by resetting its notification callbacks object. see
+ // bug 285991 for details.
+ nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
+ if (secCtrl)
+ secCtrl->SetNotificationCallbacks(nullptr);
+
+ // finally, release our reference to the socket (must do this within
+ // the transport lock) possibly closing the socket. Also release our
+ // listeners to break potential refcount cycles.
+
+ // We should be careful not to release mEventSink and mCallbacks while
+ // we're locked, because releasing it might require acquiring the lock
+ // again, so we just null out mEventSink and mCallbacks while we're
+ // holding the lock, and let the stack based objects' destuctors take
+ // care of destroying it if needed.
+ nsCOMPtr<nsIInterfaceRequestor> ourCallbacks;
+ nsCOMPtr<nsITransportEventSink> ourEventSink;
+ {
+ MutexAutoLock lock(mLock);
+ if (mFD.IsInitialized()) {
+ ReleaseFD_Locked(mFD);
+ // flag mFD as unusable; this prevents other consumers from
+ // acquiring a reference to mFD.
+ mFDconnected = false;
+ }
+
+ // We must release mCallbacks and mEventSink to avoid memory leak
+ // but only when RecoverFromError() above failed. Otherwise we lose
+ // link with UI and security callbacks on next connection attempt
+ // round. That would lead e.g. to a broken certificate exception page.
+ if (NS_FAILED(mCondition)) {
+ mCallbacks.swap(ourCallbacks);
+ mEventSink.swap(ourEventSink);
+ }
+ }
+}
+
+void
+nsSocketTransport::IsLocal(bool *aIsLocal)
+{
+ {
+ MutexAutoLock lock(mLock);
+
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ if (mNetAddr.raw.family == PR_AF_LOCAL)
+ {
+ *aIsLocal = true;
+ return;
+ }
+#endif
+
+ *aIsLocal = IsLoopBackAddress(&mNetAddr);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransport,
+ nsISocketTransport,
+ nsITransport,
+ nsIDNSListener,
+ nsIClassInfo,
+ nsIInterfaceRequestor)
+NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport,
+ nsISocketTransport,
+ nsITransport,
+ nsIDNSListener,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+nsSocketTransport::OpenInputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream **result)
+{
+ SOCKET_LOG(("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n",
+ this, flags));
+
+ NS_ENSURE_TRUE(!mInput.IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ //bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
+ !openBlocking, true, segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(&mInput, pipeOut, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
+ if (NS_FAILED(rv)) return rv;
+
+ *result = pipeIn;
+ }
+ else
+ *result = &mInput;
+
+ // flag input stream as open
+ mInputClosed = false;
+
+ rv = PostEvent(MSG_ENSURE_CONNECT);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OpenOutputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream **result)
+{
+ SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n",
+ this, flags));
+
+ NS_ENSURE_TRUE(!mOutput.IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ //bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
+ true, !openBlocking, segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(pipeIn, &mOutput, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
+ if (NS_FAILED(rv)) return rv;
+
+ *result = pipeOut;
+ }
+ else
+ *result = &mOutput;
+
+ // flag output stream as open
+ mOutputClosed = false;
+
+ rv = PostEvent(MSG_ENSURE_CONNECT);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Close(nsresult reason)
+{
+ if (NS_SUCCEEDED(reason))
+ reason = NS_BASE_STREAM_CLOSED;
+
+ mDoNotRetryToConnect = true;
+
+ mInput.CloseWithStatus(reason);
+ mOutput.CloseWithStatus(reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityInfo(nsISupports **secinfo)
+{
+ MutexAutoLock lock(mLock);
+ NS_IF_ADDREF(*secinfo = mSecInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor **callbacks)
+{
+ MutexAutoLock lock(mLock);
+ NS_IF_ADDREF(*callbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor *callbacks)
+{
+ nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
+ NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
+ NS_GetCurrentThread(),
+ getter_AddRefs(threadsafeCallbacks));
+
+ nsCOMPtr<nsISupports> secinfo;
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = threadsafeCallbacks;
+ SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n",
+ mSecInfo.get(), mCallbacks.get()));
+
+ secinfo = mSecInfo;
+ }
+
+ // don't call into PSM while holding mLock!!
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
+ if (secCtrl)
+ secCtrl->SetNotificationCallbacks(threadsafeCallbacks);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetEventSink(nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ nsCOMPtr<nsITransportEventSink> temp;
+ if (target) {
+ nsresult rv = net_NewTransportEventSinkProxy(getter_AddRefs(temp),
+ sink, target);
+ if (NS_FAILED(rv))
+ return rv;
+ sink = temp.get();
+ }
+
+ MutexAutoLock lock(mLock);
+ mEventSink = sink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::IsAlive(bool *result)
+{
+ *result = false;
+
+ nsresult conditionWhileLocked = NS_OK;
+ PRFileDescAutoLock fd(this, &conditionWhileLocked);
+ if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) {
+ return NS_OK;
+ }
+
+ // XXX do some idle-time based checks??
+
+ char c;
+ int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+
+ if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR))
+ *result = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetHost(nsACString &host)
+{
+ host = SocketHost();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetPort(int32_t *port)
+{
+ *port = (int32_t) SocketPort();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetNetworkInterfaceId(nsACString_internal &aNetworkInterfaceId)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ aNetworkInterfaceId = mNetworkInterfaceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetNetworkInterfaceId(const nsACString_internal &aNetworkInterfaceId)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ mNetworkInterfaceId = aNetworkInterfaceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOriginAttributes)
+{
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetScriptableOriginAttributes(JSContext* aCx,
+ JS::Handle<JS::Value> aOriginAttributes)
+{
+ MutexAutoLock lock(mLock);
+ NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+ NeckoOriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOriginAttributes = attrs;
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::GetOriginAttributes(NeckoOriginAttributes* aOriginAttributes)
+{
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::SetOriginAttributes(const NeckoOriginAttributes& aOriginAttributes)
+{
+ MutexAutoLock lock(mLock);
+ NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+ mOriginAttributes = aOriginAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetPeerAddr(NetAddr *addr)
+{
+ // once we are in the connected state, mNetAddr will not change.
+ // so if we can verify that we are in the connected state, then
+ // we can freely access mNetAddr from any thread without being
+ // inside a critical section.
+
+ if (!mNetAddrIsSet) {
+ SOCKET_LOG(("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
+ "NOT_AVAILABLE because not yet connected.", this, mState));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mNetAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSelfAddr(NetAddr *addr)
+{
+ // once we are in the connected state, mSelfAddr will not change.
+ // so if we can verify that we are in the connected state, then
+ // we can freely access mSelfAddr from any thread without being
+ // inside a critical section.
+
+ if (!mSelfAddrIsSet) {
+ SOCKET_LOG(("nsSocketTransport::GetSelfAddr [this=%p state=%d] "
+ "NOT_AVAILABLE because not yet connected.", this, mState));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mSelfAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Bind(NetAddr *aLocalAddr)
+{
+ NS_ENSURE_ARG(aLocalAddr);
+
+ MutexAutoLock lock(mLock);
+ if (mAttached) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBindAddr = new NetAddr();
+ memcpy(mBindAddr.get(), aLocalAddr, sizeof(NetAddr));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptablePeerAddr(nsINetAddr * *addr)
+{
+ NetAddr rawAddr;
+
+ nsresult rv;
+ rv = GetPeerAddr(&rawAddr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ADDREF(*addr = new nsNetAddr(&rawAddr));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableSelfAddr(nsINetAddr * *addr)
+{
+ NetAddr rawAddr;
+
+ nsresult rv;
+ rv = GetSelfAddr(&rawAddr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ADDREF(*addr = new nsNetAddr(&rawAddr));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetTimeout(uint32_t type, uint32_t *value)
+{
+ NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
+ *value = (uint32_t) mTimeouts[type];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetTimeout(uint32_t type, uint32_t value)
+{
+ NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
+ // truncate overly large timeout values.
+ mTimeouts[type] = (uint16_t) std::min<uint32_t>(value, UINT16_MAX);
+ PostEvent(MSG_TIMEOUT_CHANGED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetQoSBits(uint8_t aQoSBits)
+{
+ // Don't do any checking here of bits. Why? Because as of RFC-4594
+ // several different Class Selector and Assured Forwarding values
+ // have been defined, but that isn't to say more won't be added later.
+ // In that case, any checking would be an impediment to interoperating
+ // with newer QoS definitions.
+
+ mQoSBits = aQoSBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetQoSBits(uint8_t *aQoSBits)
+{
+ *aQoSBits = mQoSBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetRecvBufferSize(uint32_t *aSize)
+{
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized())
+ return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS)
+ *aSize = opt.value.recv_buffer_size;
+ else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSendBufferSize(uint32_t *aSize)
+{
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized())
+ return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_SendBufferSize;
+ if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS)
+ *aSize = opt.value.send_buffer_size;
+ else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetRecvBufferSize(uint32_t aSize)
+{
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized())
+ return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = aSize;
+ if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS)
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSendBufferSize(uint32_t aSize)
+{
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized())
+ return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = aSize;
+ if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS)
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ // flag host lookup complete for the benefit of the ResolveHost method.
+ mResolving = false;
+
+ nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, rec);
+
+ // if posting a message fails, then we should assume that the socket
+ // transport has been shutdown. this should never happen! if it does
+ // it means that the socket transport service was shutdown before the
+ // DNS service.
+ if (NS_FAILED(rv))
+ NS_WARNING("unable to post DNS lookup complete message");
+
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+NS_IMETHODIMP
+nsSocketTransport::GetInterface(const nsIID &iid, void **result)
+{
+ if (iid.Equals(NS_GET_IID(nsIDNSRecord))) {
+ return mDNSRecord ?
+ mDNSRecord->QueryInterface(iid, result) : NS_ERROR_NO_INTERFACE;
+ }
+ return this->QueryInterface(iid, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(count, array);
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetContractID(char * *aContractID)
+{
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassID(nsCID * *aClassID)
+{
+ *aClassID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetFlags(uint32_t *aFlags)
+{
+ *aFlags = nsIClassInfo::THREADSAFE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+
+NS_IMETHODIMP
+nsSocketTransport::GetConnectionFlags(uint32_t *value)
+{
+ *value = mConnectionFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetConnectionFlags(uint32_t value)
+{
+ mConnectionFlags = value;
+ mIsPrivate = value & nsISocketTransport::NO_PERMANENT_STORAGE;
+ return NS_OK;
+}
+
+void
+nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ // The global pref toggles keepalive as a system feature; it only affects
+ // an individual socket if keepalive has been specifically enabled for it.
+ // So, ensure keepalive is configured correctly if previously enabled.
+ if (mKeepaliveEnabled) {
+ nsresult rv = SetKeepaliveEnabledInternal(aEnabled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal [%s] failed rv[0x%x]",
+ aEnabled ? "enable" : "disable", rv));
+ }
+ }
+}
+
+nsresult
+nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable)
+{
+ MOZ_ASSERT(mKeepaliveIdleTimeS > 0 &&
+ mKeepaliveIdleTimeS <= kMaxTCPKeepIdle);
+ MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 &&
+ mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl);
+ MOZ_ASSERT(mKeepaliveProbeCount > 0 &&
+ mKeepaliveProbeCount <= kMaxTCPKeepCount);
+
+ PRFileDescAutoLock fd(this);
+ if (NS_WARN_IF(!fd.IsInitialized())) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Only enable if keepalives are globally enabled, but ensure other
+ // options are set correctly on the fd.
+ bool enable = aEnable && mSocketTransportService->IsKeepaliveEnabled();
+ nsresult rv = fd.SetKeepaliveVals(enable,
+ mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveVals failed rv[0x%x]", rv));
+ return rv;
+ }
+ rv = fd.SetKeepaliveEnabled(enable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%x]", rv));
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetKeepaliveEnabled(bool *aResult)
+{
+ MOZ_ASSERT(aResult);
+
+ *aResult = mKeepaliveEnabled;
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::EnsureKeepaliveValsAreInitialized()
+{
+ nsresult rv = NS_OK;
+ int32_t val = -1;
+ if (mKeepaliveIdleTimeS == -1) {
+ rv = mSocketTransportService->GetKeepaliveIdleTime(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveIdleTimeS = val;
+ }
+ if (mKeepaliveRetryIntervalS == -1) {
+ rv = mSocketTransportService->GetKeepaliveRetryInterval(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveRetryIntervalS = val;
+ }
+ if (mKeepaliveProbeCount == -1) {
+ rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveProbeCount = val;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetKeepaliveEnabled(bool aEnable)
+{
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (aEnable == mKeepaliveEnabled) {
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.",
+ this, aEnable ? "enabled" : "disabled"));
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ if (aEnable) {
+ rv = EnsureKeepaliveValsAreInitialized();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabled [%p] "
+ "error [0x%x] initializing keepalive vals",
+ this, rv));
+ return rv;
+ }
+ }
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] "
+ "%s, idle time[%ds] retry interval[%ds] packet count[%d]: "
+ "globally %s.",
+ this, aEnable ? "enabled" : "disabled",
+ mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount,
+ mSocketTransportService->IsKeepaliveEnabled() ?
+ "enabled" : "disabled"));
+
+ // Set mKeepaliveEnabled here so that state is maintained; it is possible
+ // that we're in between fds, e.g. the 1st IP address failed, so we're about
+ // to retry on a 2nd from the DNS record.
+ mKeepaliveEnabled = aEnable;
+
+ rv = SetKeepaliveEnabledInternal(aEnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%x]", rv));
+ return rv;
+ }
+
+ return NS_OK;
+#else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime,
+ int32_t aRetryInterval)
+{
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aRetryInterval <= 0 ||
+ kMaxTCPKeepIntvl < aRetryInterval)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aIdleTime == mKeepaliveIdleTimeS &&
+ aRetryInterval == mKeepaliveRetryIntervalS) {
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals [%p] idle time "
+ "already %ds and retry interval already %ds.",
+ this, mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS));
+ return NS_OK;
+ }
+ mKeepaliveIdleTimeS = aIdleTime;
+ mKeepaliveRetryIntervalS = aRetryInterval;
+
+ nsresult rv = NS_OK;
+ if (mKeepaliveProbeCount == -1) {
+ int32_t val = -1;
+ nsresult rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveProbeCount = val;
+ }
+
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals [%p] "
+ "keepalive %s, idle time[%ds] retry interval[%ds] "
+ "packet count[%d]",
+ this, mKeepaliveEnabled ? "enabled" : "disabled",
+ mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount));
+
+ PRFileDescAutoLock fd(this);
+ if (NS_WARN_IF(!fd.IsInitialized())) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ rv = fd.SetKeepaliveVals(mKeepaliveEnabled,
+ mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+#else
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+#ifdef ENABLE_SOCKET_TRACING
+
+#include <stdio.h>
+#include <ctype.h>
+#include "prenv.h"
+
+static void
+DumpBytesToFile(const char *path, const char *header, const char *buf, int32_t n)
+{
+ FILE *fp = fopen(path, "a");
+
+ fprintf(fp, "\n%s [%d bytes]\n", header, n);
+
+ const unsigned char *p;
+ while (n) {
+ p = (const unsigned char *) buf;
+
+ int32_t i, row_max = std::min(16, n);
+
+ for (i = 0; i < row_max; ++i)
+ fprintf(fp, "%02x ", *p++);
+ for (i = row_max; i < 16; ++i)
+ fprintf(fp, " ");
+
+ p = (const unsigned char *) buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ if (isprint(*p))
+ fprintf(fp, "%c", *p);
+ else
+ fprintf(fp, ".");
+ }
+
+ fprintf(fp, "\n");
+ buf += row_max;
+ n -= row_max;
+ }
+
+ fprintf(fp, "\n");
+ fclose(fp);
+}
+
+void
+nsSocketTransport::TraceInBuf(const char *buf, int32_t n)
+{
+ char *val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
+ if (!val || !*val)
+ return;
+
+ nsAutoCString header;
+ header.AssignLiteral("Reading from: ");
+ header.Append(mHost);
+ header.Append(':');
+ header.AppendInt(mPort);
+
+ DumpBytesToFile(val, header.get(), buf, n);
+}
+
+void
+nsSocketTransport::TraceOutBuf(const char *buf, int32_t n)
+{
+ char *val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
+ if (!val || !*val)
+ return;
+
+ nsAutoCString header;
+ header.AssignLiteral("Writing to: ");
+ header.Append(mHost);
+ header.Append(':');
+ header.AppendInt(mPort);
+
+ DumpBytesToFile(val, header.get(), buf, n);
+}
+
+#endif
+
+static void LogNSPRError(const char* aPrefix, const void *aObjPtr)
+{
+#if defined(DEBUG)
+ PRErrorCode errCode = PR_GetError();
+ int errLen = PR_GetErrorTextLength();
+ nsAutoCString errStr;
+ if (errLen > 0) {
+ errStr.SetLength(errLen);
+ PR_GetErrorText(errStr.BeginWriting());
+ }
+ NS_WARNING(nsPrintfCString(
+ "%s [%p] NSPR error[0x%x] %s.",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
+ errLen > 0 ? errStr.BeginReading() : "<no error text>").get());
+#endif
+}
+
+nsresult
+nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(bool aEnable)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()),
+ "Cannot enable keepalive if global pref is disabled!");
+ if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_Keepalive;
+ opt.value.keep_alive = aEnable;
+ PRStatus status = PR_SetSocketOption(mFd, &opt);
+ if (NS_WARN_IF(status != PR_SUCCESS)) {
+ LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled",
+ mSocketTransport);
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+ return NS_OK;
+}
+
+static void LogOSError(const char *aPrefix, const void *aObjPtr)
+{
+#if defined(DEBUG)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+#ifdef XP_WIN
+ DWORD errCode = WSAGetLastError();
+ LPVOID errMessage;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ errCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &errMessage,
+ 0, NULL);
+#else
+ int errCode = errno;
+ char *errMessage = strerror(errno);
+#endif
+ NS_WARNING(nsPrintfCString(
+ "%s [%p] OS error[0x%x] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
+ errMessage ? errMessage : "<no error text>").get());
+#ifdef XP_WIN
+ LocalFree(errMessage);
+#endif
+#endif
+}
+
+/* XXX PR_SetSockOpt does not support setting keepalive values, so native
+ * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this
+ * file. Requires inclusion of NSPR private/pprio.h, and platform headers.
+ */
+
+nsresult
+nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(bool aEnabled,
+ int aIdleTime,
+ int aRetryInterval,
+ int aProbeCount)
+{
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aRetryInterval <= 0 ||
+ kMaxTCPKeepIntvl < aRetryInterval)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROsfd sock = PR_FileDesc2NativeHandle(mFd);
+ if (NS_WARN_IF(sock == -1)) {
+ LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals",
+ mSocketTransport);
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+#endif
+
+#if defined(XP_WIN)
+ // Windows allows idle time and retry interval to be set; NOT ping count.
+ struct tcp_keepalive keepalive_vals = {
+ (u_long)aEnabled,
+ // Windows uses msec.
+ (u_long)(aIdleTime * 1000UL),
+ (u_long)(aRetryInterval * 1000UL)
+ };
+ DWORD bytes_returned;
+ int err = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals,
+ sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL,
+ NULL);
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
+#elif defined(XP_DARWIN)
+ // Darwin uses sec; only supports idle time being set.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE,
+ &aIdleTime, sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
+#elif defined(XP_UNIX)
+ // Not all *nix OSes support the following setsockopt() options
+ // ... but we assume they are supported in the Android kernel;
+ // build errors will tell us if they are not.
+#if defined(ANDROID) || defined(TCP_KEEPIDLE)
+ // Idle time until first keepalive probe; interval between ack'd probes; seconds.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE,
+ &aIdleTime, sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+#endif
+#if defined(ANDROID) || defined(TCP_KEEPINTVL)
+ // Interval between unack'd keepalive probes; seconds.
+ err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL,
+ &aRetryInterval, sizeof(aRetryInterval));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+#endif
+#if defined(ANDROID) || defined(TCP_KEEPCNT)
+ // Number of unack'd keepalive probes before connection times out.
+ err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT,
+ &aProbeCount, sizeof(aProbeCount));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+#endif
+ return NS_OK;
+#else
+ MOZ_ASSERT(false, "nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals "
+ "called on unsupported platform!");
+ return NS_ERROR_UNEXPECTED;
+#endif
+}
+
+void
+nsSocketTransport::CloseSocket(PRFileDesc *aFd, bool aTelemetryEnabled)
+{
+#if defined(XP_WIN)
+ AttachShutdownLayer(aFd);
+#endif
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime closeStarted;
+ if (aTelemetryEnabled) {
+ closeStarted = PR_IntervalNow();
+ }
+
+ PR_Close(aFd);
+
+ if (aTelemetryEnabled) {
+ SendPRBlockingTelemetry(closeStarted,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE);
+ }
+}
+
+void
+nsSocketTransport::SendPRBlockingTelemetry(PRIntervalTime aStart,
+ Telemetry::ID aIDNormal,
+ Telemetry::ID aIDShutdown,
+ Telemetry::ID aIDConnectivityChange,
+ Telemetry::ID aIDLinkChange,
+ Telemetry::ID aIDOffline)
+{
+ PRIntervalTime now = PR_IntervalNow();
+ if (gIOService->IsNetTearingDown()) {
+ Telemetry::Accumulate(aIDShutdown,
+ PR_IntervalToMilliseconds(now - aStart));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange())
+ < 60) {
+ Telemetry::Accumulate(aIDConnectivityChange,
+ PR_IntervalToMilliseconds(now - aStart));
+ } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange())
+ < 60) {
+ Telemetry::Accumulate(aIDLinkChange,
+ PR_IntervalToMilliseconds(now - aStart));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange())
+ < 60) {
+ Telemetry::Accumulate(aIDOffline,
+ PR_IntervalToMilliseconds(now - aStart));
+ } else {
+ Telemetry::Accumulate(aIDNormal,
+ PR_IntervalToMilliseconds(now - aStart));
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransport2.h b/netwerk/base/nsSocketTransport2.h
new file mode 100644
index 0000000000..7c85ccdc40
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.h
@@ -0,0 +1,471 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransport2_h__
+#define nsSocketTransport2_h__
+
+#ifdef DEBUG_darinf
+#define ENABLE_SOCKET_TRACING
+#endif
+
+#include "mozilla/Mutex.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIDNSListener.h"
+#include "nsIClassInfo.h"
+#include "mozilla/net/DNS.h"
+#include "nsASocketHandler.h"
+#include "mozilla/Telemetry.h"
+
+#include "prerror.h"
+#include "nsAutoPtr.h"
+
+class nsICancelable;
+class nsIDNSRecord;
+class nsIInterfaceRequestor;
+
+//-----------------------------------------------------------------------------
+
+// after this short interval, we will return to PR_Poll
+#define NS_SOCKET_CONNECT_TIMEOUT PR_MillisecondsToInterval(20)
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+nsresult
+ErrorAccordingToNSPR(PRErrorCode errorCode);
+
+class nsSocketTransport;
+
+class nsSocketInputStream : public nsIAsyncInputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsSocketInputStream(nsSocketTransport *);
+ virtual ~nsSocketInputStream();
+
+ bool IsReferenced() { return mReaderRefCnt > 0; }
+ nsresult Condition() { return mCondition; }
+ uint64_t ByteCount() { return mByteCount; }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+private:
+ nsSocketTransport *mTransport;
+ ThreadSafeAutoRefCnt mReaderRefCnt;
+
+ // access to these is protected by mTransport->mLock
+ nsresult mCondition;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ uint64_t mByteCount;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketOutputStream : public nsIAsyncOutputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit nsSocketOutputStream(nsSocketTransport *);
+ virtual ~nsSocketOutputStream();
+
+ bool IsReferenced() { return mWriterRefCnt > 0; }
+ nsresult Condition() { return mCondition; }
+ uint64_t ByteCount() { return mByteCount; }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+private:
+ static nsresult WriteFromSegments(nsIInputStream *, void *,
+ const char *, uint32_t offset,
+ uint32_t count, uint32_t *countRead);
+
+ nsSocketTransport *mTransport;
+ ThreadSafeAutoRefCnt mWriterRefCnt;
+
+ // access to these is protected by mTransport->mLock
+ nsresult mCondition;
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ uint64_t mByteCount;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransport final : public nsASocketHandler
+ , public nsISocketTransport
+ , public nsIDNSListener
+ , public nsIClassInfo
+ , public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsSocketTransport();
+
+ // this method instructs the socket transport to open a socket of the
+ // given type(s) to the given host or proxy.
+ nsresult Init(const char **socketTypes, uint32_t typeCount,
+ const nsACString &host, uint16_t port,
+ const nsACString &hostRoute, uint16_t portRoute,
+ nsIProxyInfo *proxyInfo);
+
+ // Alternative Init method for when the IP-address of the host
+ // has been pre-resolved using a alternative means (e.g. FlyWeb service
+ // info).
+ nsresult InitPreResolved(const char **socketTypes, uint32_t typeCount,
+ const nsACString &host, uint16_t port,
+ const nsACString &hostRoute, uint16_t portRoute,
+ nsIProxyInfo *proxyInfo,
+ const mozilla::net::NetAddr* addr);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address.
+ nsresult InitWithConnectedSocket(PRFileDesc *socketFD,
+ const NetAddr *addr);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address, and additionally supplies security info.
+ nsresult InitWithConnectedSocket(PRFileDesc* aSocketFD,
+ const NetAddr* aAddr,
+ nsISupports* aSecInfo);
+
+ // This method instructs the socket transport to open a socket
+ // connected to the given Unix domain address. We can only create
+ // unlayered, simple, stream sockets.
+ nsresult InitWithFilename(const char *filename);
+
+ // nsASocketHandler methods:
+ void OnSocketReady(PRFileDesc *, int16_t outFlags) override;
+ void OnSocketDetached(PRFileDesc *) override;
+ void IsLocal(bool *aIsLocal) override;
+ void OnKeepaliveEnabledPrefChange(bool aEnabled) override final;
+
+ // called when a socket event is handled
+ void OnSocketEvent(uint32_t type, nsresult status, nsISupports *param);
+
+ uint64_t ByteCountReceived() override { return mInput.ByteCount(); }
+ uint64_t ByteCountSent() override { return mOutput.ByteCount(); }
+ static void CloseSocket(PRFileDesc *aFd, bool aTelemetryEnabled);
+ static void SendPRBlockingTelemetry(PRIntervalTime aStart,
+ Telemetry::ID aIDNormal,
+ Telemetry::ID aIDShutdown,
+ Telemetry::ID aIDConnectivityChange,
+ Telemetry::ID aIDLinkChange,
+ Telemetry::ID aIDOffline);
+protected:
+
+ virtual ~nsSocketTransport();
+ void CleanupTypes();
+
+private:
+
+ // event types
+ enum {
+ MSG_ENSURE_CONNECT,
+ MSG_DNS_LOOKUP_COMPLETE,
+ MSG_RETRY_INIT_SOCKET,
+ MSG_TIMEOUT_CHANGED,
+ MSG_INPUT_CLOSED,
+ MSG_INPUT_PENDING,
+ MSG_OUTPUT_CLOSED,
+ MSG_OUTPUT_PENDING
+ };
+ nsresult PostEvent(uint32_t type, nsresult status = NS_OK, nsISupports *param = nullptr);
+
+ enum {
+ STATE_CLOSED,
+ STATE_IDLE,
+ STATE_RESOLVING,
+ STATE_CONNECTING,
+ STATE_TRANSFERRING
+ };
+
+ // Safer way to get and automatically release PRFileDesc objects.
+ class MOZ_STACK_CLASS PRFileDescAutoLock
+ {
+ public:
+ explicit PRFileDescAutoLock(nsSocketTransport *aSocketTransport,
+ nsresult *aConditionWhileLocked = nullptr)
+ : mSocketTransport(aSocketTransport)
+ , mFd(nullptr)
+ {
+ MOZ_ASSERT(aSocketTransport);
+ MutexAutoLock lock(mSocketTransport->mLock);
+ if (aConditionWhileLocked) {
+ *aConditionWhileLocked = mSocketTransport->mCondition;
+ if (NS_FAILED(mSocketTransport->mCondition)) {
+ return;
+ }
+ }
+ mFd = mSocketTransport->GetFD_Locked();
+ }
+ ~PRFileDescAutoLock() {
+ MutexAutoLock lock(mSocketTransport->mLock);
+ if (mFd) {
+ mSocketTransport->ReleaseFD_Locked(mFd);
+ }
+ }
+ bool IsInitialized() {
+ return mFd;
+ }
+ operator PRFileDesc*() {
+ return mFd;
+ }
+ nsresult SetKeepaliveEnabled(bool aEnable);
+ nsresult SetKeepaliveVals(bool aEnabled, int aIdleTime,
+ int aRetryInterval, int aProbeCount);
+ private:
+ operator PRFileDescAutoLock*() { return nullptr; }
+
+ // Weak ptr to nsSocketTransport since this is a stack class only.
+ nsSocketTransport *mSocketTransport;
+ PRFileDesc *mFd;
+ };
+ friend class PRFileDescAutoLock;
+
+ class LockedPRFileDesc
+ {
+ public:
+ explicit LockedPRFileDesc(nsSocketTransport *aSocketTransport)
+ : mSocketTransport(aSocketTransport)
+ , mFd(nullptr)
+ {
+ MOZ_ASSERT(aSocketTransport);
+ }
+ ~LockedPRFileDesc() {}
+ bool IsInitialized() {
+ return mFd;
+ }
+ LockedPRFileDesc& operator=(PRFileDesc *aFd) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ mFd = aFd;
+ return *this;
+ }
+ operator PRFileDesc*() {
+ if (mSocketTransport->mAttached) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ }
+ return mFd;
+ }
+ bool operator==(PRFileDesc *aFd) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ return mFd == aFd;
+ }
+ private:
+ operator LockedPRFileDesc*() { return nullptr; }
+ // Weak ptr to nsSocketTransport since it owns this class.
+ nsSocketTransport *mSocketTransport;
+ PRFileDesc *mFd;
+ };
+ friend class LockedPRFileDesc;
+
+ //-------------------------------------------------------------------------
+ // these members are "set" at initialization time and are never modified
+ // afterwards. this allows them to be safely accessed from any thread.
+ //-------------------------------------------------------------------------
+
+ // socket type info:
+ char **mTypes;
+ uint32_t mTypeCount;
+ nsCString mHost;
+ nsCString mProxyHost;
+ nsCString mOriginHost;
+ uint16_t mPort;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ uint16_t mProxyPort;
+ uint16_t mOriginPort;
+ bool mProxyTransparent;
+ bool mProxyTransparentResolvesHost;
+ bool mHttpsProxy;
+ uint32_t mConnectionFlags;
+
+ // The origin attributes are used to create sockets. The first party domain
+ // will eventually be used to isolate OCSP cache and is only non-empty when
+ // "privacy.firstparty.isolate" is enabled. Setting this is the only way to
+ // carry origin attributes down to NSPR layers which are final consumers.
+ // It must be set before the socket transport is built.
+ NeckoOriginAttributes mOriginAttributes;
+
+ uint16_t SocketPort() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort; }
+ const nsCString &SocketHost() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost; }
+
+ //-------------------------------------------------------------------------
+ // members accessible only on the socket transport thread:
+ // (the exception being initialization/shutdown time)
+ //-------------------------------------------------------------------------
+
+ // socket state vars:
+ uint32_t mState; // STATE_??? flags
+ bool mAttached;
+ bool mInputClosed;
+ bool mOutputClosed;
+
+ // The platform-specific network interface id that this socket
+ // associated with.
+ nsCString mNetworkInterfaceId;
+
+ // this flag is used to determine if the results of a host lookup arrive
+ // recursively or not. this flag is not protected by any lock.
+ bool mResolving;
+
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ nsCOMPtr<nsIDNSRecord> mDNSRecord;
+
+ // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have
+ // reached STATE_TRANSFERRING. It must not change after that.
+ void SetSocketName(PRFileDesc *fd);
+ NetAddr mNetAddr;
+ NetAddr mSelfAddr; // getsockname()
+ Atomic<bool, Relaxed> mNetAddrIsSet;
+ Atomic<bool, Relaxed> mSelfAddrIsSet;
+ Atomic<bool, Relaxed> mNetAddrPreResolved;
+
+ nsAutoPtr<NetAddr> mBindAddr;
+
+ // socket methods (these can only be called on the socket thread):
+
+ void SendStatus(nsresult status);
+ nsresult ResolveHost();
+ nsresult BuildSocket(PRFileDesc *&, bool &, bool &);
+ nsresult InitiateSocket();
+ bool RecoverFromError();
+
+ void OnMsgInputPending()
+ {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags |= (PR_POLL_READ | PR_POLL_EXCEPT);
+ }
+ void OnMsgOutputPending()
+ {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags |= (PR_POLL_WRITE | PR_POLL_EXCEPT);
+ }
+ void OnMsgInputClosed(nsresult reason);
+ void OnMsgOutputClosed(nsresult reason);
+
+ // called when the socket is connected
+ void OnSocketConnected();
+
+ //-------------------------------------------------------------------------
+ // socket input/output objects. these may be accessed on any thread with
+ // the exception of some specific methods (XXX).
+
+ Mutex mLock; // protects members in this section.
+ LockedPRFileDesc mFD;
+ nsrefcnt mFDref; // mFD is closed when mFDref goes to zero.
+ bool mFDconnected; // mFD is available to consumer when TRUE.
+
+ // A delete protector reference to gSocketTransportService held for lifetime
+ // of 'this'. Sometimes used interchangably with gSocketTransportService due
+ // to scoping.
+ RefPtr<nsSocketTransportService> mSocketTransportService;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsISupports> mSecInfo;
+
+ nsSocketInputStream mInput;
+ nsSocketOutputStream mOutput;
+
+ friend class nsSocketInputStream;
+ friend class nsSocketOutputStream;
+
+ // socket timeouts are not protected by any lock.
+ uint16_t mTimeouts[2];
+
+ // QoS setting for socket
+ uint8_t mQoSBits;
+
+ //
+ // mFD access methods: called with mLock held.
+ //
+ PRFileDesc *GetFD_Locked();
+ void ReleaseFD_Locked(PRFileDesc *fd);
+
+ //
+ // stream state changes (called outside mLock):
+ //
+ void OnInputClosed(nsresult reason)
+ {
+ // no need to post an event if called on the socket thread
+ if (PR_GetCurrentThread() == gSocketThread)
+ OnMsgInputClosed(reason);
+ else
+ PostEvent(MSG_INPUT_CLOSED, reason);
+ }
+ void OnInputPending()
+ {
+ // no need to post an event if called on the socket thread
+ if (PR_GetCurrentThread() == gSocketThread)
+ OnMsgInputPending();
+ else
+ PostEvent(MSG_INPUT_PENDING);
+ }
+ void OnOutputClosed(nsresult reason)
+ {
+ // no need to post an event if called on the socket thread
+ if (PR_GetCurrentThread() == gSocketThread)
+ OnMsgOutputClosed(reason); // XXX need to not be inside lock!
+ else
+ PostEvent(MSG_OUTPUT_CLOSED, reason);
+ }
+ void OnOutputPending()
+ {
+ // no need to post an event if called on the socket thread
+ if (PR_GetCurrentThread() == gSocketThread)
+ OnMsgOutputPending();
+ else
+ PostEvent(MSG_OUTPUT_PENDING);
+ }
+
+#ifdef ENABLE_SOCKET_TRACING
+ void TraceInBuf(const char *buf, int32_t n);
+ void TraceOutBuf(const char *buf, int32_t n);
+#endif
+
+ // Reads prefs to get default keepalive config.
+ nsresult EnsureKeepaliveValsAreInitialized();
+
+ // Groups calls to fd.SetKeepaliveEnabled and fd.SetKeepaliveVals.
+ nsresult SetKeepaliveEnabledInternal(bool aEnable);
+
+ // True if keepalive has been enabled by the socket owner. Note: Keepalive
+ // must also be enabled globally for it to be enabled in TCP.
+ bool mKeepaliveEnabled;
+
+ // Keepalive config (support varies by platform).
+ int32_t mKeepaliveIdleTimeS;
+ int32_t mKeepaliveRetryIntervalS;
+ int32_t mKeepaliveProbeCount;
+
+ bool mDoNotRetryToConnect;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsSocketTransport_h__
diff --git a/netwerk/base/nsSocketTransportService2.cpp b/netwerk/base/nsSocketTransportService2.cpp
new file mode 100644
index 0000000000..d2f20651e7
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -0,0 +1,1627 @@
+// vim:set sw=4 sts=4 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+#include "nsSocketTransport2.h"
+#include "NetworkActivityMonitor.h"
+#include "mozilla/Preferences.h"
+#include "nsIOService.h"
+#include "nsASocketHandler.h"
+#include "nsError.h"
+#include "prnetdb.h"
+#include "prerror.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Services.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PublicSSL.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Telemetry.h"
+#include "nsThreadUtils.h"
+#include "nsIFile.h"
+#include "nsIWidget.h"
+#include "mozilla/dom/FlyWebService.h"
+
+#if defined(XP_WIN)
+#include "mozilla/WindowsVersion.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gSocketTransportLog("nsSocketTransport");
+LazyLogModule gUDPSocketLog("UDPSocket");
+LazyLogModule gTCPSocketLog("TCPSocket");
+
+nsSocketTransportService *gSocketTransportService = nullptr;
+Atomic<PRThread*, Relaxed> gSocketThread;
+
+#define SEND_BUFFER_PREF "network.tcp.sendbuffer"
+#define KEEPALIVE_ENABLED_PREF "network.tcp.keepalive.enabled"
+#define KEEPALIVE_IDLE_TIME_PREF "network.tcp.keepalive.idle_time"
+#define KEEPALIVE_RETRY_INTERVAL_PREF "network.tcp.keepalive.retry_interval"
+#define KEEPALIVE_PROBE_COUNT_PREF "network.tcp.keepalive.probe_count"
+#define SOCKET_LIMIT_TARGET 1000U
+#define SOCKET_LIMIT_MIN 50U
+#define BLIP_INTERVAL_PREF "network.activity.blipIntervalMilliseconds"
+#define MAX_TIME_BETWEEN_TWO_POLLS "network.sts.max_time_for_events_between_two_polls"
+#define TELEMETRY_PREF "toolkit.telemetry.enabled"
+#define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN "network.sts.max_time_for_pr_close_during_shutdown"
+
+#define REPAIR_POLLABLE_EVENT_TIME 10
+
+uint32_t nsSocketTransportService::gMaxCount;
+PRCallOnceType nsSocketTransportService::gMaxCountInitOnce;
+
+//-----------------------------------------------------------------------------
+// ctor/dtor (called on the main/UI thread by the service manager)
+
+nsSocketTransportService::nsSocketTransportService()
+ : mThread(nullptr)
+ , mLock("nsSocketTransportService::mLock")
+ , mInitialized(false)
+ , mShuttingDown(false)
+ , mOffline(false)
+ , mGoingOffline(false)
+ , mRawThread(nullptr)
+ , mActiveListSize(SOCKET_LIMIT_MIN)
+ , mIdleListSize(SOCKET_LIMIT_MIN)
+ , mActiveCount(0)
+ , mIdleCount(0)
+ , mSentBytesCount(0)
+ , mReceivedBytesCount(0)
+ , mSendBufferSize(0)
+ , mKeepaliveIdleTimeS(600)
+ , mKeepaliveRetryIntervalS(1)
+ , mKeepaliveProbeCount(kDefaultTCPKeepCount)
+ , mKeepaliveEnabledPref(false)
+ , mServingPendingQueue(false)
+ , mMaxTimePerPollIter(100)
+ , mTelemetryEnabledPref(false)
+ , mMaxTimeForPrClosePref(PR_SecondsToInterval(5))
+ , mSleepPhase(false)
+ , mProbedMaxCount(false)
+#if defined(XP_WIN)
+ , mPolling(false)
+#endif
+{
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount);
+ mActiveList = (SocketContext *)
+ moz_xmalloc(sizeof(SocketContext) * mActiveListSize);
+ mIdleList = (SocketContext *)
+ moz_xmalloc(sizeof(SocketContext) * mIdleListSize);
+ mPollList = (PRPollDesc *)
+ moz_xmalloc(sizeof(PRPollDesc) * (mActiveListSize + 1));
+
+ NS_ASSERTION(!gSocketTransportService, "must not instantiate twice");
+ gSocketTransportService = this;
+}
+
+nsSocketTransportService::~nsSocketTransportService()
+{
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+ NS_ASSERTION(!mInitialized, "not shutdown properly");
+
+ free(mActiveList);
+ free(mIdleList);
+ free(mPollList);
+ gSocketTransportService = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// event queue (any thread)
+
+already_AddRefed<nsIThread>
+nsSocketTransportService::GetThreadSafely()
+{
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIThread> result = mThread;
+ return result.forget();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DispatchFromScript(nsIRunnable *event, uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ return Dispatch(event_ref.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Dispatch(already_AddRefed<nsIRunnable> event, uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ SOCKET_LOG(("STS dispatch [%p]\n", event_ref.get()));
+
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ nsresult rv;
+ rv = thread ? thread->Dispatch(event_ref.forget(), flags) : NS_ERROR_NOT_INITIALIZED;
+ if (rv == NS_ERROR_UNEXPECTED) {
+ // Thread is no longer accepting events. We must have just shut it
+ // down on the main thread. Pretend we never saw it.
+ rv = NS_ERROR_NOT_INITIALIZED;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::IsOnCurrentThread(bool *result)
+{
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
+ return thread->IsOnCurrentThread(result);
+}
+
+//-----------------------------------------------------------------------------
+// socket api (socket thread only)
+
+NS_IMETHODIMP
+nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable *event)
+{
+ SOCKET_LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n"));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (CanAttachSocket()) {
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ auto *runnable = new LinkedRunnableEvent(event);
+ mPendingSocketQueue.insertBack(runnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AttachSocket(PRFileDesc *fd, nsASocketHandler *handler)
+{
+ SOCKET_LOG(("nsSocketTransportService::AttachSocket [handler=%p]\n", handler));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (!CanAttachSocket()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SocketContext sock;
+ sock.mFD = fd;
+ sock.mHandler = handler;
+ sock.mElapsedTime = 0;
+
+ nsresult rv = AddToIdleList(&sock);
+ if (NS_SUCCEEDED(rv))
+ NS_ADDREF(handler);
+ return rv;
+}
+
+// the number of sockets that can be attached at any given time is
+// limited. this is done because some operating systems (e.g., Win9x)
+// limit the number of sockets that can be created by an application.
+// AttachSocket will fail if the limit is exceeded. consumers should
+// call CanAttachSocket and check the result before creating a socket.
+
+bool
+nsSocketTransportService::CanAttachSocket()
+{
+ static bool reported900FDLimit = false;
+
+ uint32_t total = mActiveCount + mIdleCount;
+ bool rv = total < gMaxCount;
+
+ if (mTelemetryEnabledPref &&
+ (((total >= 900) || !rv) && !reported900FDLimit)) {
+ reported900FDLimit = true;
+ Telemetry::Accumulate(Telemetry::NETWORK_SESSION_AT_900FD, true);
+ }
+
+ return rv;
+}
+
+nsresult
+nsSocketTransportService::DetachSocket(SocketContext *listHead, SocketContext *sock)
+{
+ SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%p]\n", sock->mHandler));
+ MOZ_ASSERT((listHead == mActiveList) || (listHead == mIdleList),
+ "DetachSocket invalid head");
+
+ // inform the handler that this socket is going away
+ sock->mHandler->OnSocketDetached(sock->mFD);
+ mSentBytesCount += sock->mHandler->ByteCountSent();
+ mReceivedBytesCount += sock->mHandler->ByteCountReceived();
+
+ // cleanup
+ sock->mFD = nullptr;
+ NS_RELEASE(sock->mHandler);
+
+ if (listHead == mActiveList)
+ RemoveFromPollList(sock);
+ else
+ RemoveFromIdleList(sock);
+
+ // NOTE: sock is now an invalid pointer
+
+ //
+ // notify the first element on the pending socket queue...
+ //
+ nsCOMPtr<nsIRunnable> event;
+ LinkedRunnableEvent *runnable = mPendingSocketQueue.getFirst();
+ if (runnable) {
+ event = runnable->TakeEvent();
+ runnable->remove();
+ delete runnable;
+ }
+ if (event) {
+ // move event from pending queue to dispatch queue
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransportService::AddToPollList(SocketContext *sock)
+{
+ MOZ_ASSERT(!(static_cast<uint32_t>(sock - mActiveList) < mActiveListSize),
+ "AddToPollList Socket Already Active");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToPollList [handler=%p]\n", sock->mHandler));
+ if (mActiveCount == mActiveListSize) {
+ SOCKET_LOG((" Active List size of %d met\n", mActiveCount));
+ if (!GrowActiveList()) {
+ NS_ERROR("too many active sockets");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ uint32_t newSocketIndex = mActiveCount;
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
+ newSocketIndex = ChaosMode::randomUint32LessThan(mActiveCount + 1);
+ PodMove(mActiveList + newSocketIndex + 1, mActiveList + newSocketIndex,
+ mActiveCount - newSocketIndex);
+ PodMove(mPollList + newSocketIndex + 2, mPollList + newSocketIndex + 1,
+ mActiveCount - newSocketIndex);
+ }
+ mActiveList[newSocketIndex] = *sock;
+ mActiveCount++;
+
+ mPollList[newSocketIndex + 1].fd = sock->mFD;
+ mPollList[newSocketIndex + 1].in_flags = sock->mHandler->mPollFlags;
+ mPollList[newSocketIndex + 1].out_flags = 0;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::RemoveFromPollList(SocketContext *sock)
+{
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList [handler=%p]\n", sock->mHandler));
+
+ uint32_t index = sock - mActiveList;
+ MOZ_ASSERT(index < mActiveListSize, "invalid index");
+
+ SOCKET_LOG((" index=%u mActiveCount=%u\n", index, mActiveCount));
+
+ if (index != mActiveCount-1) {
+ mActiveList[index] = mActiveList[mActiveCount-1];
+ mPollList[index+1] = mPollList[mActiveCount];
+ }
+ mActiveCount--;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+}
+
+nsresult
+nsSocketTransportService::AddToIdleList(SocketContext *sock)
+{
+ MOZ_ASSERT(!(static_cast<uint32_t>(sock - mIdleList) < mIdleListSize),
+ "AddToIdlelList Socket Already Idle");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToIdleList [handler=%p]\n", sock->mHandler));
+ if (mIdleCount == mIdleListSize) {
+ SOCKET_LOG((" Idle List size of %d met\n", mIdleCount));
+ if (!GrowIdleList()) {
+ NS_ERROR("too many idle sockets");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mIdleList[mIdleCount] = *sock;
+ mIdleCount++;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::RemoveFromIdleList(SocketContext *sock)
+{
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%p]\n", sock->mHandler));
+
+ uint32_t index = sock - mIdleList;
+ NS_ASSERTION(index < mIdleListSize, "invalid index in idle list");
+
+ if (index != mIdleCount-1)
+ mIdleList[index] = mIdleList[mIdleCount-1];
+ mIdleCount--;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+}
+
+void
+nsSocketTransportService::MoveToIdleList(SocketContext *sock)
+{
+ nsresult rv = AddToIdleList(sock);
+ if (NS_FAILED(rv))
+ DetachSocket(mActiveList, sock);
+ else
+ RemoveFromPollList(sock);
+}
+
+void
+nsSocketTransportService::MoveToPollList(SocketContext *sock)
+{
+ nsresult rv = AddToPollList(sock);
+ if (NS_FAILED(rv))
+ DetachSocket(mIdleList, sock);
+ else
+ RemoveFromIdleList(sock);
+}
+
+bool
+nsSocketTransportService::GrowActiveList()
+{
+ int32_t toAdd = gMaxCount - mActiveListSize;
+ if (toAdd > 100) {
+ toAdd = 100;
+ } else if (toAdd < 1) {
+ MOZ_ASSERT(false, "CanAttachSocket() should prevent this");
+ return false;
+ }
+
+ mActiveListSize += toAdd;
+ mActiveList = (SocketContext *)
+ moz_xrealloc(mActiveList, sizeof(SocketContext) * mActiveListSize);
+ mPollList = (PRPollDesc *)
+ moz_xrealloc(mPollList, sizeof(PRPollDesc) * (mActiveListSize + 1));
+ return true;
+}
+
+bool
+nsSocketTransportService::GrowIdleList()
+{
+ int32_t toAdd = gMaxCount - mIdleListSize;
+ if (toAdd > 100) {
+ toAdd = 100;
+ } else if (toAdd < 1) {
+ MOZ_ASSERT(false, "CanAttachSocket() should prevent this");
+ return false;
+ }
+
+ mIdleListSize += toAdd;
+ mIdleList = (SocketContext *)
+ moz_xrealloc(mIdleList, sizeof(SocketContext) * mIdleListSize);
+ return true;
+}
+
+PRIntervalTime
+nsSocketTransportService::PollTimeout()
+{
+ if (mActiveCount == 0)
+ return NS_SOCKET_POLL_TIMEOUT;
+
+ // compute minimum time before any socket timeout expires.
+ uint32_t minR = UINT16_MAX;
+ for (uint32_t i=0; i<mActiveCount; ++i) {
+ const SocketContext &s = mActiveList[i];
+ // mPollTimeout could be less than mElapsedTime if setTimeout
+ // was called with a value smaller than mElapsedTime.
+ uint32_t r = (s.mElapsedTime < s.mHandler->mPollTimeout)
+ ? s.mHandler->mPollTimeout - s.mElapsedTime
+ : 0;
+ if (r < minR)
+ minR = r;
+ }
+ // nsASocketHandler defines UINT16_MAX as do not timeout
+ if (minR == UINT16_MAX) {
+ SOCKET_LOG(("poll timeout: none\n"));
+ return NS_SOCKET_POLL_TIMEOUT;
+ }
+ SOCKET_LOG(("poll timeout: %lu\n", minR));
+ return PR_SecondsToInterval(minR);
+}
+
+int32_t
+nsSocketTransportService::Poll(uint32_t *interval,
+ TimeDuration *pollDuration)
+{
+ PRPollDesc *pollList;
+ uint32_t pollCount;
+ PRIntervalTime pollTimeout;
+ *pollDuration = 0;
+
+ // If there are pending events for this thread then
+ // DoPollIteration() should service the network without blocking.
+ bool pendingEvents = false;
+ mRawThread->HasPendingEvents(&pendingEvents);
+
+ if (mPollList[0].fd) {
+ mPollList[0].out_flags = 0;
+ pollList = mPollList;
+ pollCount = mActiveCount + 1;
+ pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout();
+ }
+ else {
+ // no pollable event, so busy wait...
+ pollCount = mActiveCount;
+ if (pollCount)
+ pollList = &mPollList[1];
+ else
+ pollList = nullptr;
+ pollTimeout =
+ pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25);
+ }
+
+ PRIntervalTime ts = PR_IntervalNow();
+
+ TimeStamp pollStart;
+ if (mTelemetryEnabledPref) {
+ pollStart = TimeStamp::NowLoRes();
+ }
+
+ SOCKET_LOG((" timeout = %i milliseconds\n",
+ PR_IntervalToMilliseconds(pollTimeout)));
+ int32_t rv = PR_Poll(pollList, pollCount, pollTimeout);
+
+ PRIntervalTime passedInterval = PR_IntervalNow() - ts;
+
+ if (mTelemetryEnabledPref && !pollStart.IsNull()) {
+ *pollDuration = TimeStamp::NowLoRes() - pollStart;
+ }
+
+ SOCKET_LOG((" ...returned after %i milliseconds\n",
+ PR_IntervalToMilliseconds(passedInterval)));
+
+ *interval = PR_IntervalToSeconds(passedInterval);
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransportService,
+ nsISocketTransportService,
+ nsIRoutedSocketTransportService,
+ nsIEventTarget,
+ nsIThreadObserver,
+ nsIRunnable,
+ nsPISocketTransportService,
+ nsIObserver)
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Init()
+{
+ if (!NS_IsMainThread()) {
+ NS_ERROR("wrong thread");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mInitialized)
+ return NS_OK;
+
+ if (mShuttingDown)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_NewNamedThread("Socket Thread", getter_AddRefs(thread), this);
+ if (NS_FAILED(rv)) return rv;
+
+ {
+ MutexAutoLock lock(mLock);
+ // Install our mThread, protecting against concurrent readers
+ thread.swap(mThread);
+ }
+
+ nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (tmpPrefService) {
+ tmpPrefService->AddObserver(SEND_BUFFER_PREF, this, false);
+ tmpPrefService->AddObserver(KEEPALIVE_ENABLED_PREF, this, false);
+ tmpPrefService->AddObserver(KEEPALIVE_IDLE_TIME_PREF, this, false);
+ tmpPrefService->AddObserver(KEEPALIVE_RETRY_INTERVAL_PREF, this, false);
+ tmpPrefService->AddObserver(KEEPALIVE_PROBE_COUNT_PREF, this, false);
+ tmpPrefService->AddObserver(MAX_TIME_BETWEEN_TWO_POLLS, this, false);
+ tmpPrefService->AddObserver(TELEMETRY_PREF, this, false);
+ tmpPrefService->AddObserver(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN, this, false);
+ }
+ UpdatePrefs();
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "profile-initial-state", false);
+ obsSvc->AddObserver(this, "last-pb-context-exited", false);
+ obsSvc->AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true);
+ obsSvc->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+ obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ }
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Shutdown(bool aXpcomShutdown)
+{
+ SOCKET_LOG(("nsSocketTransportService::Shutdown\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized)
+ return NS_OK;
+
+ if (mShuttingDown)
+ return NS_ERROR_UNEXPECTED;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ // signal the socket thread to shutdown
+ mShuttingDown = true;
+
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ }
+
+ if (!aXpcomShutdown) {
+ return ShutdownThread();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransportService::ShutdownThread()
+{
+ SOCKET_LOG(("nsSocketTransportService::ShutdownThread\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized || !mShuttingDown)
+ return NS_OK;
+
+ // join with thread
+ mThread->Shutdown();
+ {
+ MutexAutoLock lock(mLock);
+ // Drop our reference to mThread and make sure that any concurrent
+ // readers are excluded
+ mThread = nullptr;
+ }
+
+ nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (tmpPrefService)
+ tmpPrefService->RemoveObserver(SEND_BUFFER_PREF, this);
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(this, "profile-initial-state");
+ obsSvc->RemoveObserver(this, "last-pb-context-exited");
+ obsSvc->RemoveObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC);
+ obsSvc->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ obsSvc->RemoveObserver(this, "xpcom-shutdown-threads");
+ }
+
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Cancel();
+ mAfterWakeUpTimer = nullptr;
+ }
+
+ NetworkActivityMonitor::Shutdown();
+
+ mInitialized = false;
+ mShuttingDown = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetOffline(bool *offline)
+{
+ *offline = mOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::SetOffline(bool offline)
+{
+ MutexAutoLock lock(mLock);
+ if (!mOffline && offline) {
+ // signal the socket thread to go offline, so it will detach sockets
+ mGoingOffline = true;
+ mOffline = true;
+ }
+ else if (mOffline && !offline) {
+ mOffline = false;
+ }
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveIdleTime(int32_t *aKeepaliveIdleTimeS)
+{
+ MOZ_ASSERT(aKeepaliveIdleTimeS);
+ if (NS_WARN_IF(!aKeepaliveIdleTimeS)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveIdleTimeS = mKeepaliveIdleTimeS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveRetryInterval(int32_t *aKeepaliveRetryIntervalS)
+{
+ MOZ_ASSERT(aKeepaliveRetryIntervalS);
+ if (NS_WARN_IF(!aKeepaliveRetryIntervalS)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveRetryIntervalS = mKeepaliveRetryIntervalS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveProbeCount(int32_t *aKeepaliveProbeCount)
+{
+ MOZ_ASSERT(aKeepaliveProbeCount);
+ if (NS_WARN_IF(!aKeepaliveProbeCount)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveProbeCount = mKeepaliveProbeCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateTransport(const char **types,
+ uint32_t typeCount,
+ const nsACString &host,
+ int32_t port,
+ nsIProxyInfo *proxyInfo,
+ nsISocketTransport **result)
+{
+ return CreateRoutedTransport(types, typeCount, host, port, NS_LITERAL_CSTRING(""), 0,
+ proxyInfo, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateRoutedTransport(const char **types,
+ uint32_t typeCount,
+ const nsACString &host,
+ int32_t port,
+ const nsACString &hostRoute,
+ int32_t portRoute,
+ nsIProxyInfo *proxyInfo,
+ nsISocketTransport **result)
+{
+ // Check FlyWeb table for host mappings. If one exists, then use that.
+ RefPtr<mozilla::dom::FlyWebService> fws =
+ mozilla::dom::FlyWebService::GetExisting();
+ if (fws) {
+ nsresult rv = fws->CreateTransportForHost(types, typeCount, host, port,
+ hostRoute, portRoute,
+ proxyInfo, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*result) {
+ return NS_OK;
+ }
+ }
+
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE);
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+ nsresult rv = trans->Init(types, typeCount, host, port, hostRoute, portRoute, proxyInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ trans.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateUnixDomainTransport(nsIFile *aPath,
+ nsISocketTransport **result)
+{
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ nsAutoCString path;
+ rv = aPath->GetNativePath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+
+ rv = trans->InitWithFilename(path.get());
+ if (NS_FAILED(rv))
+ return rv;
+
+ trans.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::OnDispatchedEvent(nsIThreadInternal *thread)
+{
+#ifndef XP_WIN
+ // On windows poll can hang and this became worse when we introduced the
+ // patch for bug 698882 (see also bug 1292181), therefore we reverted the
+ // behavior on windows to be as before bug 698882, e.g. write to the socket
+ // also if an event dispatch is on the socket thread and writing to the
+ // socket for each event.
+ if (PR_GetCurrentThread() == gSocketThread) {
+ // this check is redundant to one done inside ::Signal(), but
+ // we can do it here and skip obtaining the lock - given that
+ // this is a relatively common occurance its worth the
+ // redundant code
+ SOCKET_LOG(("OnDispatchedEvent Same Thread Skip Signal\n"));
+ return NS_OK;
+ }
+#else
+ if (gIOService->IsNetTearingDown()) {
+ // Poll can hang sometimes. If we are in shutdown, we are going to
+ // start a watchdog. If we do not exit poll within
+ // REPAIR_POLLABLE_EVENT_TIME signal a pollable event again.
+ StartPollWatchdog();
+ }
+#endif
+
+ MutexAutoLock lock(mLock);
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal *thread,
+ bool mayWait)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed)
+{
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::MarkTheLastElementOfPendingQueue()
+{
+ mServingPendingQueue = false;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Run()
+{
+ SOCKET_LOG(("STS thread init %d sockets\n", gMaxCount));
+
+ psm::InitializeSSLServerCertVerificationThreads();
+
+ gSocketThread = PR_GetCurrentThread();
+
+ {
+ MutexAutoLock lock(mLock);
+ mPollableEvent.reset(new PollableEvent());
+ //
+ // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
+ // or similar software.
+ //
+ // NOTE: per bug 191739, this failure could also be caused by lack
+ // of a loopback device on Windows and OS/2 platforms (it creates
+ // a loopback socket pair on these platforms to implement a pollable
+ // event object). if we can't create a pollable event, then we'll
+ // have to "busy wait" to implement the socket event queue :-(
+ //
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ NS_WARNING("running socket transport thread without a pollable event");
+ SOCKET_LOG(("running socket transport thread without a pollable event"));
+ }
+
+ mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+ mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ mPollList[0].out_flags = 0;
+ }
+
+ mRawThread = NS_GetCurrentThread();
+
+ // hook ourselves up to observe event processing for this thread
+ nsCOMPtr<nsIThreadInternal> threadInt = do_QueryInterface(mRawThread);
+ threadInt->SetObserver(this);
+
+ // make sure the pseudo random number generator is seeded on this thread
+ srand(static_cast<unsigned>(PR_Now()));
+
+ // For the calculation of the duration of the last cycle (i.e. the last for-loop
+ // iteration before shutdown).
+ TimeStamp startOfCycleForLastCycleCalc;
+ int numberOfPendingEventsLastCycle;
+
+ // For measuring of the poll iteration duration without time spent blocked
+ // in poll().
+ TimeStamp pollCycleStart;
+ // Time blocked in poll().
+ TimeDuration singlePollDuration;
+
+ // For calculating the time needed for a new element to run.
+ TimeStamp startOfIteration;
+ TimeStamp startOfNextIteration;
+ int numberOfPendingEvents;
+
+ // If there is too many pending events queued, we will run some poll()
+ // between them and the following variable is cumulative time spent
+ // blocking in poll().
+ TimeDuration pollDuration;
+
+ for (;;) {
+ bool pendingEvents = false;
+
+ numberOfPendingEvents = 0;
+ numberOfPendingEventsLastCycle = 0;
+ if (mTelemetryEnabledPref) {
+ startOfCycleForLastCycleCalc = TimeStamp::NowLoRes();
+ startOfNextIteration = TimeStamp::NowLoRes();
+ }
+ pollDuration = 0;
+
+ do {
+ if (mTelemetryEnabledPref) {
+ pollCycleStart = TimeStamp::NowLoRes();
+ }
+
+ DoPollIteration(&singlePollDuration);
+
+ if (mTelemetryEnabledPref && !pollCycleStart.IsNull()) {
+ Telemetry::Accumulate(Telemetry::STS_POLL_BLOCK_TIME,
+ singlePollDuration.ToMilliseconds());
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::STS_POLL_CYCLE,
+ pollCycleStart + singlePollDuration,
+ TimeStamp::NowLoRes());
+ pollDuration += singlePollDuration;
+ }
+
+ mRawThread->HasPendingEvents(&pendingEvents);
+ if (pendingEvents) {
+ if (!mServingPendingQueue) {
+ nsresult rv = Dispatch(NewRunnableMethod(this,
+ &nsSocketTransportService::MarkTheLastElementOfPendingQueue),
+ nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not dispatch a new event on the "
+ "socket thread.");
+ } else {
+ mServingPendingQueue = true;
+ }
+
+ if (mTelemetryEnabledPref) {
+ startOfIteration = startOfNextIteration;
+ // Everything that comes after this point will
+ // be served in the next iteration. If no even
+ // arrives, startOfNextIteration will be reset at the
+ // beginning of each for-loop.
+ startOfNextIteration = TimeStamp::NowLoRes();
+ }
+ }
+ TimeStamp eventQueueStart = TimeStamp::NowLoRes();
+ do {
+ NS_ProcessNextEvent(mRawThread);
+ numberOfPendingEvents++;
+ pendingEvents = false;
+ mRawThread->HasPendingEvents(&pendingEvents);
+ } while (pendingEvents && mServingPendingQueue &&
+ ((TimeStamp::NowLoRes() -
+ eventQueueStart).ToMilliseconds() <
+ mMaxTimePerPollIter));
+
+ if (mTelemetryEnabledPref && !mServingPendingQueue &&
+ !startOfIteration.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::STS_POLL_AND_EVENTS_CYCLE,
+ startOfIteration + pollDuration,
+ TimeStamp::NowLoRes());
+
+ Telemetry::Accumulate(
+ Telemetry::STS_NUMBER_OF_PENDING_EVENTS,
+ numberOfPendingEvents);
+
+ numberOfPendingEventsLastCycle += numberOfPendingEvents;
+ numberOfPendingEvents = 0;
+ pollDuration = 0;
+ }
+ }
+ } while (pendingEvents);
+
+ bool goingOffline = false;
+ // now that our event queue is empty, check to see if we should exit
+ {
+ MutexAutoLock lock(mLock);
+ if (mShuttingDown) {
+ if (mTelemetryEnabledPref &&
+ !startOfCycleForLastCycleCalc.IsNull()) {
+ Telemetry::Accumulate(
+ Telemetry::STS_NUMBER_OF_PENDING_EVENTS_IN_THE_LAST_CYCLE,
+ numberOfPendingEventsLastCycle);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::STS_POLL_AND_EVENT_THE_LAST_CYCLE,
+ startOfCycleForLastCycleCalc,
+ TimeStamp::NowLoRes());
+ }
+ break;
+ }
+ if (mGoingOffline) {
+ mGoingOffline = false;
+ goingOffline = true;
+ }
+ }
+ // Avoid potential deadlock
+ if (goingOffline)
+ Reset(true);
+ }
+
+ SOCKET_LOG(("STS shutting down thread\n"));
+
+ // detach all sockets, including locals
+ Reset(false);
+
+ // Final pass over the event queue. This makes sure that events posted by
+ // socket detach handlers get processed.
+ NS_ProcessPendingEvents(mRawThread);
+
+ gSocketThread = nullptr;
+
+ psm::StopSSLServerCertVerificationThreads();
+
+ SOCKET_LOG(("STS thread exit\n"));
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::DetachSocketWithGuard(bool aGuardLocals,
+ SocketContext *socketList,
+ int32_t index)
+{
+ bool isGuarded = false;
+ if (aGuardLocals) {
+ socketList[index].mHandler->IsLocal(&isGuarded);
+ if (!isGuarded)
+ socketList[index].mHandler->KeepWhenOffline(&isGuarded);
+ }
+ if (!isGuarded)
+ DetachSocket(socketList, &socketList[index]);
+}
+
+void
+nsSocketTransportService::Reset(bool aGuardLocals)
+{
+ // detach any sockets
+ int32_t i;
+ for (i = mActiveCount - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mActiveList, i);
+ }
+ for (i = mIdleCount - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mIdleList, i);
+ }
+}
+
+nsresult
+nsSocketTransportService::DoPollIteration(TimeDuration *pollDuration)
+{
+ SOCKET_LOG(("STS poll iter\n"));
+
+ int32_t i, count;
+ //
+ // poll loop
+ //
+ // walk active list backwards to see if any sockets should actually be
+ // idle, then walk the idle list backwards to see if any idle sockets
+ // should become active. take care to check only idle sockets that
+ // were idle to begin with ;-)
+ //
+ count = mIdleCount;
+ for (i=mActiveCount-1; i>=0; --i) {
+ //---
+ SOCKET_LOG((" active [%u] { handler=%p condition=%x pollflags=%hu }\n", i,
+ mActiveList[i].mHandler,
+ mActiveList[i].mHandler->mCondition,
+ mActiveList[i].mHandler->mPollFlags));
+ //---
+ if (NS_FAILED(mActiveList[i].mHandler->mCondition))
+ DetachSocket(mActiveList, &mActiveList[i]);
+ else {
+ uint16_t in_flags = mActiveList[i].mHandler->mPollFlags;
+ if (in_flags == 0)
+ MoveToIdleList(&mActiveList[i]);
+ else {
+ // update poll flags
+ mPollList[i+1].in_flags = in_flags;
+ mPollList[i+1].out_flags = 0;
+ }
+ }
+ }
+ for (i=count-1; i>=0; --i) {
+ //---
+ SOCKET_LOG((" idle [%u] { handler=%p condition=%x pollflags=%hu }\n", i,
+ mIdleList[i].mHandler,
+ mIdleList[i].mHandler->mCondition,
+ mIdleList[i].mHandler->mPollFlags));
+ //---
+ if (NS_FAILED(mIdleList[i].mHandler->mCondition))
+ DetachSocket(mIdleList, &mIdleList[i]);
+ else if (mIdleList[i].mHandler->mPollFlags != 0)
+ MoveToPollList(&mIdleList[i]);
+ }
+
+ SOCKET_LOG((" calling PR_Poll [active=%u idle=%u]\n", mActiveCount, mIdleCount));
+
+#if defined(XP_WIN)
+ // 30 active connections is the historic limit before firefox 7's 256. A few
+ // windows systems have troubles with the higher limit, so actively probe a
+ // limit the first time we exceed 30.
+ if ((mActiveCount > 30) && !mProbedMaxCount)
+ ProbeMaxCount();
+#endif
+
+ // Measures seconds spent while blocked on PR_Poll
+ uint32_t pollInterval = 0;
+ int32_t n = 0;
+ *pollDuration = 0;
+ if (!gIOService->IsNetTearingDown()) {
+ // Let's not do polling during shutdown.
+#if defined(XP_WIN)
+ StartPolling();
+#endif
+ n = Poll(&pollInterval, pollDuration);
+#if defined(XP_WIN)
+ EndPolling();
+#endif
+ }
+
+ if (n < 0) {
+ SOCKET_LOG((" PR_Poll error [%d] os error [%d]\n", PR_GetError(),
+ PR_GetOSError()));
+ }
+ else {
+ //
+ // service "active" sockets...
+ //
+ uint32_t numberOfOnSocketReadyCalls = 0;
+ for (i=0; i<int32_t(mActiveCount); ++i) {
+ PRPollDesc &desc = mPollList[i+1];
+ SocketContext &s = mActiveList[i];
+ if (n > 0 && desc.out_flags != 0) {
+ s.mElapsedTime = 0;
+ s.mHandler->OnSocketReady(desc.fd, desc.out_flags);
+ numberOfOnSocketReadyCalls++;
+ }
+ // check for timeout errors unless disabled...
+ else if (s.mHandler->mPollTimeout != UINT16_MAX) {
+ // update elapsed time counter
+ // (NOTE: We explicitly cast UINT16_MAX to be an unsigned value
+ // here -- otherwise, some compilers will treat it as signed,
+ // which makes them fire signed/unsigned-comparison build
+ // warnings for the comparison against 'pollInterval'.)
+ if (MOZ_UNLIKELY(pollInterval >
+ static_cast<uint32_t>(UINT16_MAX) -
+ s.mElapsedTime))
+ s.mElapsedTime = UINT16_MAX;
+ else
+ s.mElapsedTime += uint16_t(pollInterval);
+ // check for timeout expiration
+ if (s.mElapsedTime >= s.mHandler->mPollTimeout) {
+ s.mElapsedTime = 0;
+ s.mHandler->OnSocketReady(desc.fd, -1);
+ numberOfOnSocketReadyCalls++;
+ }
+ }
+ }
+ if (mTelemetryEnabledPref) {
+ Telemetry::Accumulate(
+ Telemetry::STS_NUMBER_OF_ONSOCKETREADY_CALLS,
+ numberOfOnSocketReadyCalls);
+ }
+
+ //
+ // check for "dead" sockets and remove them (need to do this in
+ // reverse order obviously).
+ //
+ for (i=mActiveCount-1; i>=0; --i) {
+ if (NS_FAILED(mActiveList[i].mHandler->mCondition))
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+
+ if (n != 0 && (mPollList[0].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT))) {
+ MutexAutoLock lock(mLock);
+
+ // acknowledge pollable event (should not block)
+ if (mPollableEvent &&
+ ((mPollList[0].out_flags & PR_POLL_EXCEPT) ||
+ !mPollableEvent->Clear())) {
+ // On Windows, the TCP loopback connection in the
+ // pollable event may become broken when a laptop
+ // switches between wired and wireless networks or
+ // wakes up from hibernation. We try to create a
+ // new pollable event. If that fails, we fall back
+ // on "busy wait".
+ NS_WARNING("Trying to repair mPollableEvent");
+ mPollableEvent.reset(new PollableEvent());
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ }
+ SOCKET_LOG(("running socket transport thread without "
+ "a pollable event now valid=%d", !!mPollableEvent));
+ mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+ mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ mPollList[0].out_flags = 0;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::UpdateSendBufferPref(nsIPrefBranch *pref)
+{
+ int32_t bufferSize;
+
+ // If the pref is set, honor it. 0 means use OS defaults.
+ nsresult rv = pref->GetIntPref(SEND_BUFFER_PREF, &bufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ mSendBufferSize = bufferSize;
+ return;
+ }
+
+#if defined(XP_WIN)
+ // If the pref is not set but this is windows set it depending on windows version
+ if (!IsWin2003OrLater()) { // windows xp
+ mSendBufferSize = 131072;
+ } else { // vista or later
+ mSendBufferSize = 131072 * 4;
+ }
+#endif
+}
+
+nsresult
+nsSocketTransportService::UpdatePrefs()
+{
+ mSendBufferSize = 0;
+
+ nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (tmpPrefService) {
+ UpdateSendBufferPref(tmpPrefService);
+
+ // Default TCP Keepalive Values.
+ int32_t keepaliveIdleTimeS;
+ nsresult rv = tmpPrefService->GetIntPref(KEEPALIVE_IDLE_TIME_PREF,
+ &keepaliveIdleTimeS);
+ if (NS_SUCCEEDED(rv))
+ mKeepaliveIdleTimeS = clamped(keepaliveIdleTimeS,
+ 1, kMaxTCPKeepIdle);
+
+ int32_t keepaliveRetryIntervalS;
+ rv = tmpPrefService->GetIntPref(KEEPALIVE_RETRY_INTERVAL_PREF,
+ &keepaliveRetryIntervalS);
+ if (NS_SUCCEEDED(rv))
+ mKeepaliveRetryIntervalS = clamped(keepaliveRetryIntervalS,
+ 1, kMaxTCPKeepIntvl);
+
+ int32_t keepaliveProbeCount;
+ rv = tmpPrefService->GetIntPref(KEEPALIVE_PROBE_COUNT_PREF,
+ &keepaliveProbeCount);
+ if (NS_SUCCEEDED(rv))
+ mKeepaliveProbeCount = clamped(keepaliveProbeCount,
+ 1, kMaxTCPKeepCount);
+ bool keepaliveEnabled = false;
+ rv = tmpPrefService->GetBoolPref(KEEPALIVE_ENABLED_PREF,
+ &keepaliveEnabled);
+ if (NS_SUCCEEDED(rv) && keepaliveEnabled != mKeepaliveEnabledPref) {
+ mKeepaliveEnabledPref = keepaliveEnabled;
+ OnKeepaliveEnabledPrefChange();
+ }
+
+ int32_t maxTimePref;
+ rv = tmpPrefService->GetIntPref(MAX_TIME_BETWEEN_TWO_POLLS,
+ &maxTimePref);
+ if (NS_SUCCEEDED(rv) && maxTimePref >= 0) {
+ mMaxTimePerPollIter = maxTimePref;
+ }
+
+ bool telemetryPref = false;
+ rv = tmpPrefService->GetBoolPref(TELEMETRY_PREF,
+ &telemetryPref);
+ if (NS_SUCCEEDED(rv)) {
+ mTelemetryEnabledPref = telemetryPref;
+ }
+
+ int32_t maxTimeForPrClosePref;
+ rv = tmpPrefService->GetIntPref(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN,
+ &maxTimeForPrClosePref);
+ if (NS_SUCCEEDED(rv) && maxTimeForPrClosePref >=0) {
+ mMaxTimeForPrClosePref = PR_MillisecondsToInterval(maxTimeForPrClosePref);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::OnKeepaliveEnabledPrefChange()
+{
+ // Dispatch to socket thread if we're not executing there.
+ if (PR_GetCurrentThread() != gSocketThread) {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod(
+ this, &nsSocketTransportService::OnKeepaliveEnabledPrefChange),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SOCKET_LOG(("nsSocketTransportService::OnKeepaliveEnabledPrefChange %s",
+ mKeepaliveEnabledPref ? "enabled" : "disabled"));
+
+ // Notify each socket that keepalive has been en/disabled globally.
+ for (int32_t i = mActiveCount - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mActiveList[i]);
+ }
+ for (int32_t i = mIdleCount - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mIdleList[i]);
+ }
+}
+
+void
+nsSocketTransportService::NotifyKeepaliveEnabledPrefChange(SocketContext *sock)
+{
+ MOZ_ASSERT(sock, "SocketContext cannot be null!");
+ MOZ_ASSERT(sock->mHandler, "SocketContext does not have a handler!");
+
+ if (!sock || !sock->mHandler) {
+ return;
+ }
+
+ sock->mHandler->OnKeepaliveEnabledPrefChange(mKeepaliveEnabledPref);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ UpdatePrefs();
+ return NS_OK;
+ }
+
+ if (!strcmp(topic, "profile-initial-state")) {
+ int32_t blipInterval = Preferences::GetInt(BLIP_INTERVAL_PREF, 0);
+ if (blipInterval <= 0) {
+ return NS_OK;
+ }
+
+ return net::NetworkActivityMonitor::Init(blipInterval);
+ }
+
+ if (!strcmp(topic, "last-pb-context-exited")) {
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this,
+ &nsSocketTransportService::ClosePrivateConnections);
+ nsresult rv = Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
+ if (timer == mAfterWakeUpTimer) {
+ mAfterWakeUpTimer = nullptr;
+ mSleepPhase = false;
+ }
+
+#if defined(XP_WIN)
+ if (timer == mPollRepairTimer) {
+ DoPollRepair();
+ }
+#endif
+
+ } else if (!strcmp(topic, NS_WIDGET_SLEEP_OBSERVER_TOPIC)) {
+ mSleepPhase = true;
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Cancel();
+ mAfterWakeUpTimer = nullptr;
+ }
+ } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ if (mSleepPhase && !mAfterWakeUpTimer) {
+ mAfterWakeUpTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Init(this, 2000, nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+ } else if (!strcmp(topic, "xpcom-shutdown-threads")) {
+ ShutdownThread();
+ }
+
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::ClosePrivateConnections()
+{
+ // Must be called on the socket thread.
+#ifdef DEBUG
+ bool onSTSThread;
+ IsOnCurrentThread(&onSTSThread);
+ MOZ_ASSERT(onSTSThread);
+#endif
+
+ for (int32_t i = mActiveCount - 1; i >= 0; --i) {
+ if (mActiveList[i].mHandler->mIsPrivate) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+ }
+ for (int32_t i = mIdleCount - 1; i >= 0; --i) {
+ if (mIdleList[i].mHandler->mIsPrivate) {
+ DetachSocket(mIdleList, &mIdleList[i]);
+ }
+ }
+
+ ClearPrivateSSLState();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetSendBufferSize(int32_t *value)
+{
+ *value = mSendBufferSize;
+ return NS_OK;
+}
+
+
+/// ugly OS specific includes are placed at the bottom of the src for clarity
+
+#if defined(XP_WIN)
+#include <windows.h>
+#elif defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+#include <sys/resource.h>
+#endif
+
+// Right now the only need to do this is on windows.
+#if defined(XP_WIN)
+void
+nsSocketTransportService::ProbeMaxCount()
+{
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (mProbedMaxCount)
+ return;
+ mProbedMaxCount = true;
+
+ // Allocate and test a PR_Poll up to the gMaxCount number of unconnected
+ // sockets. See bug 692260 - windows should be able to handle 1000 sockets
+ // in select() without a problem, but LSPs have been known to balk at lower
+ // numbers. (64 in the bug).
+
+ // Allocate
+ struct PRPollDesc pfd[SOCKET_LIMIT_TARGET];
+ uint32_t numAllocated = 0;
+
+ for (uint32_t index = 0 ; index < gMaxCount; ++index) {
+ pfd[index].in_flags = PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT;
+ pfd[index].out_flags = 0;
+ pfd[index].fd = PR_OpenTCPSocket(PR_AF_INET);
+ if (!pfd[index].fd) {
+ SOCKET_LOG(("Socket Limit Test index %d failed\n", index));
+ if (index < SOCKET_LIMIT_MIN)
+ gMaxCount = SOCKET_LIMIT_MIN;
+ else
+ gMaxCount = index;
+ break;
+ }
+ ++numAllocated;
+ }
+
+ // Test
+ static_assert(SOCKET_LIMIT_MIN >= 32U, "Minimum Socket Limit is >= 32");
+ while (gMaxCount <= numAllocated) {
+ int32_t rv = PR_Poll(pfd, gMaxCount, PR_MillisecondsToInterval(0));
+
+ SOCKET_LOG(("Socket Limit Test poll() size=%d rv=%d\n",
+ gMaxCount, rv));
+
+ if (rv >= 0)
+ break;
+
+ SOCKET_LOG(("Socket Limit Test poll confirmationSize=%d rv=%d error=%d\n",
+ gMaxCount, rv, PR_GetError()));
+
+ gMaxCount -= 32;
+ if (gMaxCount <= SOCKET_LIMIT_MIN) {
+ gMaxCount = SOCKET_LIMIT_MIN;
+ break;
+ }
+ }
+
+ // Free
+ for (uint32_t index = 0 ; index < numAllocated; ++index)
+ if (pfd[index].fd)
+ PR_Close(pfd[index].fd);
+
+ Telemetry::Accumulate(Telemetry::NETWORK_PROBE_MAXCOUNT, gMaxCount);
+ SOCKET_LOG(("Socket Limit Test max was confirmed at %d\n", gMaxCount));
+}
+#endif // windows
+
+PRStatus
+nsSocketTransportService::DiscoverMaxCount()
+{
+ gMaxCount = SOCKET_LIMIT_MIN;
+
+#if defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+ // On unix and os x network sockets and file
+ // descriptors are the same. OS X comes defaulted at 256,
+ // most linux at 1000. We can reliably use [sg]rlimit to
+ // query that and raise it if needed.
+
+ struct rlimit rlimitData;
+ if (getrlimit(RLIMIT_NOFILE, &rlimitData) == -1) // rlimit broken - use min
+ return PR_SUCCESS;
+
+ if (rlimitData.rlim_cur >= SOCKET_LIMIT_TARGET) { // larger than target!
+ gMaxCount = SOCKET_LIMIT_TARGET;
+ return PR_SUCCESS;
+ }
+
+ int32_t maxallowed = rlimitData.rlim_max;
+ if ((uint32_t)maxallowed <= SOCKET_LIMIT_MIN) {
+ return PR_SUCCESS; // so small treat as if rlimit is broken
+ }
+
+ if ((maxallowed == -1) || // no hard cap - ok to set target
+ ((uint32_t)maxallowed >= SOCKET_LIMIT_TARGET)) {
+ maxallowed = SOCKET_LIMIT_TARGET;
+ }
+
+ rlimitData.rlim_cur = maxallowed;
+ setrlimit(RLIMIT_NOFILE, &rlimitData);
+ if ((getrlimit(RLIMIT_NOFILE, &rlimitData) != -1) &&
+ (rlimitData.rlim_cur > SOCKET_LIMIT_MIN)) {
+ gMaxCount = rlimitData.rlim_cur;
+ }
+
+#elif defined(XP_WIN) && !defined(WIN_CE)
+ // >= XP is confirmed to have at least 1000
+ static_assert(SOCKET_LIMIT_TARGET <= 1000, "SOCKET_LIMIT_TARGET max value is 1000");
+ gMaxCount = SOCKET_LIMIT_TARGET;
+#else
+ // other platforms are harder to test - so leave at safe legacy value
+#endif
+
+ return PR_SUCCESS;
+}
+
+
+// Used to return connection info to Dashboard.cpp
+void
+nsSocketTransportService::AnalyzeConnection(nsTArray<SocketInfo> *data,
+ struct SocketContext *context, bool aActive)
+{
+ if (context->mHandler->mIsPrivate)
+ return;
+ PRFileDesc *aFD = context->mFD;
+
+ PRFileDesc *idLayer = PR_GetIdentitiesLayer(aFD, PR_NSPR_IO_LAYER);
+
+ NS_ENSURE_TRUE_VOID(idLayer);
+
+ bool tcp = PR_GetDescType(idLayer) == PR_DESC_SOCKET_TCP;
+
+ PRNetAddr peer_addr;
+ PodZero(&peer_addr);
+ PRStatus rv = PR_GetPeerName(aFD, &peer_addr);
+ if (rv != PR_SUCCESS)
+ return;
+
+ char host[64] = {0};
+ rv = PR_NetAddrToString(&peer_addr, host, sizeof(host));
+ if (rv != PR_SUCCESS)
+ return;
+
+ uint16_t port;
+ if (peer_addr.raw.family == PR_AF_INET)
+ port = peer_addr.inet.port;
+ else
+ port = peer_addr.ipv6.port;
+ port = PR_ntohs(port);
+ uint64_t sent = context->mHandler->ByteCountSent();
+ uint64_t received = context->mHandler->ByteCountReceived();
+ SocketInfo info = { nsCString(host), sent, received, port, aActive, tcp };
+
+ data->AppendElement(info);
+}
+
+void
+nsSocketTransportService::GetSocketConnections(nsTArray<SocketInfo> *data)
+{
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ for (uint32_t i = 0; i < mActiveCount; i++)
+ AnalyzeConnection(data, &mActiveList[i], true);
+ for (uint32_t i = 0; i < mIdleCount; i++)
+ AnalyzeConnection(data, &mIdleList[i], false);
+}
+
+#if defined(XP_WIN)
+void
+nsSocketTransportService::StartPollWatchdog()
+{
+ MutexAutoLock lock(mLock);
+
+ // Poll can hang sometimes. If we are in shutdown, we are going to start a
+ // watchdog. If we do not exit poll within REPAIR_POLLABLE_EVENT_TIME
+ // signal a pollable event again.
+ MOZ_ASSERT(gIOService->IsNetTearingDown());
+ if (mPolling && !mPollRepairTimer) {
+ mPollRepairTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ mPollRepairTimer->Init(this, REPAIR_POLLABLE_EVENT_TIME,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+}
+
+void
+nsSocketTransportService::DoPollRepair()
+{
+ MutexAutoLock lock(mLock);
+ if (mPolling && mPollableEvent) {
+ mPollableEvent->Signal();
+ } else if (mPollRepairTimer) {
+ mPollRepairTimer->Cancel();
+ }
+}
+
+void
+nsSocketTransportService::StartPolling()
+{
+ MutexAutoLock lock(mLock);
+ mPolling = true;
+}
+
+void
+nsSocketTransportService::EndPolling()
+{
+ MutexAutoLock lock(mLock);
+ mPolling = false;
+ if (mPollRepairTimer) {
+ mPollRepairTimer->Cancel();
+ }
+}
+#endif
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransportService2.h b/netwerk/base/nsSocketTransportService2.h
new file mode 100644
index 0000000000..81c806793c
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.h
@@ -0,0 +1,282 @@
+/* vim:set ts=4 sw=4 sts=4 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransportService2_h__
+#define nsSocketTransportService2_h__
+
+#include "nsPISocketTransportService.h"
+#include "nsIThreadInternal.h"
+#include "nsIRunnable.h"
+#include "nsEventQueue.h"
+#include "nsCOMPtr.h"
+#include "prinrval.h"
+#include "mozilla/Logging.h"
+#include "prinit.h"
+#include "nsIObserver.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "nsITimer.h"
+#include "mozilla/UniquePtr.h"
+#include "PollableEvent.h"
+
+class nsASocketHandler;
+struct PRPollDesc;
+class nsIPrefBranch;
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+//
+// set MOZ_LOG=nsSocketTransport:5
+//
+extern LazyLogModule gSocketTransportLog;
+#define SOCKET_LOG(args) MOZ_LOG(gSocketTransportLog, LogLevel::Debug, args)
+#define SOCKET_LOG_ENABLED() MOZ_LOG_TEST(gSocketTransportLog, LogLevel::Debug)
+
+//
+// set MOZ_LOG=UDPSocket:5
+//
+extern LazyLogModule gUDPSocketLog;
+#define UDPSOCKET_LOG(args) MOZ_LOG(gUDPSocketLog, LogLevel::Debug, args)
+#define UDPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gUDPSocketLog, LogLevel::Debug)
+
+//-----------------------------------------------------------------------------
+
+#define NS_SOCKET_POLL_TIMEOUT PR_INTERVAL_NO_TIMEOUT
+
+//-----------------------------------------------------------------------------
+
+// These maximums are borrowed from the linux kernel.
+static const int32_t kMaxTCPKeepIdle = 32767; // ~9 hours.
+static const int32_t kMaxTCPKeepIntvl = 32767;
+static const int32_t kMaxTCPKeepCount = 127;
+static const int32_t kDefaultTCPKeepCount =
+#if defined (XP_WIN)
+ 10; // Hardcoded in Windows.
+#elif defined (XP_MACOSX)
+ 8; // Hardcoded in OSX.
+#else
+ 4; // Specifiable in Linux.
+#endif
+
+class LinkedRunnableEvent final : public LinkedListElement<LinkedRunnableEvent>
+{
+public:
+ explicit LinkedRunnableEvent(nsIRunnable *event) : mEvent(event) {}
+ ~LinkedRunnableEvent() {}
+
+ already_AddRefed<nsIRunnable> TakeEvent()
+ {
+ return mEvent.forget();
+ }
+private:
+ nsCOMPtr<nsIRunnable> mEvent;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransportService final : public nsPISocketTransportService
+ , public nsIEventTarget
+ , public nsIThreadObserver
+ , public nsIRunnable
+ , public nsIObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSPISOCKETTRANSPORTSERVICE
+ NS_DECL_NSISOCKETTRANSPORTSERVICE
+ NS_DECL_NSIROUTEDSOCKETTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET
+ NS_DECL_NSITHREADOBSERVER
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+ using nsIEventTarget::Dispatch;
+
+ nsSocketTransportService();
+
+ // Max Socket count may need to get initialized/used by nsHttpHandler
+ // before this class is initialized.
+ static uint32_t gMaxCount;
+ static PRCallOnceType gMaxCountInitOnce;
+ static PRStatus DiscoverMaxCount();
+
+ bool CanAttachSocket();
+
+ // Called by the networking dashboard on the socket thread only
+ // Fills the passed array with socket information
+ void GetSocketConnections(nsTArray<SocketInfo> *);
+ uint64_t GetSentBytes() { return mSentBytesCount; }
+ uint64_t GetReceivedBytes() { return mReceivedBytesCount; }
+
+ // Returns true if keepalives are enabled in prefs.
+ bool IsKeepaliveEnabled() { return mKeepaliveEnabledPref; }
+
+ bool IsTelemetryEnabledAndNotSleepPhase() { return mTelemetryEnabledPref &&
+ !mSleepPhase; }
+ PRIntervalTime MaxTimeForPrClosePref() {return mMaxTimeForPrClosePref; }
+protected:
+
+ virtual ~nsSocketTransportService();
+
+private:
+
+ //-------------------------------------------------------------------------
+ // misc (any thread)
+ //-------------------------------------------------------------------------
+
+ nsCOMPtr<nsIThread> mThread; // protected by mLock
+ UniquePtr<PollableEvent> mPollableEvent;
+
+ // Returns mThread, protecting the get-and-addref with mLock
+ already_AddRefed<nsIThread> GetThreadSafely();
+
+ //-------------------------------------------------------------------------
+ // initialization and shutdown (any thread)
+ //-------------------------------------------------------------------------
+
+ Mutex mLock;
+ bool mInitialized;
+ bool mShuttingDown;
+ // indicates whether we are currently in the
+ // process of shutting down
+ bool mOffline;
+ bool mGoingOffline;
+
+ // Detaches all sockets.
+ void Reset(bool aGuardLocals);
+
+ nsresult ShutdownThread();
+
+ //-------------------------------------------------------------------------
+ // socket lists (socket thread only)
+ //
+ // only "active" sockets are on the poll list. the active list is kept
+ // in sync with the poll list such that:
+ //
+ // mActiveList[k].mFD == mPollList[k+1].fd
+ //
+ // where k=0,1,2,...
+ //-------------------------------------------------------------------------
+
+ struct SocketContext
+ {
+ PRFileDesc *mFD;
+ nsASocketHandler *mHandler;
+ uint16_t mElapsedTime; // time elapsed w/o activity
+ };
+
+ SocketContext *mActiveList; /* mListSize entries */
+ SocketContext *mIdleList; /* mListSize entries */
+ nsIThread *mRawThread;
+
+ uint32_t mActiveListSize;
+ uint32_t mIdleListSize;
+ uint32_t mActiveCount;
+ uint32_t mIdleCount;
+
+ nsresult DetachSocket(SocketContext *, SocketContext *);
+ nsresult AddToIdleList(SocketContext *);
+ nsresult AddToPollList(SocketContext *);
+ void RemoveFromIdleList(SocketContext *);
+ void RemoveFromPollList(SocketContext *);
+ void MoveToIdleList(SocketContext *sock);
+ void MoveToPollList(SocketContext *sock);
+
+ bool GrowActiveList();
+ bool GrowIdleList();
+ void InitMaxCount();
+
+ // Total bytes number transfered through all the sockets except active ones
+ uint64_t mSentBytesCount;
+ uint64_t mReceivedBytesCount;
+ //-------------------------------------------------------------------------
+ // poll list (socket thread only)
+ //
+ // first element of the poll list is mPollableEvent (or null if the pollable
+ // event cannot be created).
+ //-------------------------------------------------------------------------
+
+ PRPollDesc *mPollList; /* mListSize + 1 entries */
+
+ PRIntervalTime PollTimeout(); // computes ideal poll timeout
+ nsresult DoPollIteration(TimeDuration *pollDuration);
+ // perfoms a single poll iteration
+ int32_t Poll(uint32_t *interval,
+ TimeDuration *pollDuration);
+ // calls PR_Poll. the out param
+ // interval indicates the poll
+ // duration in seconds.
+ // pollDuration is used only for
+ // telemetry
+
+ //-------------------------------------------------------------------------
+ // pending socket queue - see NotifyWhenCanAttachSocket
+ //-------------------------------------------------------------------------
+ AutoCleanLinkedList<LinkedRunnableEvent> mPendingSocketQueue;
+
+ // Preference Monitor for SendBufferSize and Keepalive prefs.
+ nsresult UpdatePrefs();
+ void UpdateSendBufferPref(nsIPrefBranch *);
+ int32_t mSendBufferSize;
+ // Number of seconds of connection is idle before first keepalive ping.
+ int32_t mKeepaliveIdleTimeS;
+ // Number of seconds between retries should keepalive pings fail.
+ int32_t mKeepaliveRetryIntervalS;
+ // Number of keepalive probes to send.
+ int32_t mKeepaliveProbeCount;
+ // True if TCP keepalive is enabled globally.
+ bool mKeepaliveEnabledPref;
+
+ Atomic<bool> mServingPendingQueue;
+ Atomic<int32_t, Relaxed> mMaxTimePerPollIter;
+ Atomic<bool, Relaxed> mTelemetryEnabledPref;
+ Atomic<PRIntervalTime, Relaxed> mMaxTimeForPrClosePref;
+
+ // Between a computer going to sleep and waking up the PR_*** telemetry
+ // will be corrupted - so do not record it.
+ Atomic<bool, Relaxed> mSleepPhase;
+ nsCOMPtr<nsITimer> mAfterWakeUpTimer;
+
+ void OnKeepaliveEnabledPrefChange();
+ void NotifyKeepaliveEnabledPrefChange(SocketContext *sock);
+
+ // Socket thread only for dynamically adjusting max socket size
+#if defined(XP_WIN)
+ void ProbeMaxCount();
+#endif
+ bool mProbedMaxCount;
+
+ void AnalyzeConnection(nsTArray<SocketInfo> *data,
+ SocketContext *context, bool aActive);
+
+ void ClosePrivateConnections();
+ void DetachSocketWithGuard(bool aGuardLocals,
+ SocketContext *socketList,
+ int32_t index);
+
+ void MarkTheLastElementOfPendingQueue();
+
+#if defined(XP_WIN)
+ Atomic<bool> mPolling;
+ nsCOMPtr<nsITimer> mPollRepairTimer;
+ void StartPollWatchdog();
+ void DoPollRepair();
+ void StartPolling();
+ void EndPolling();
+#endif
+};
+
+extern nsSocketTransportService *gSocketTransportService;
+extern Atomic<PRThread*, Relaxed> gSocketThread;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsSocketTransportService_h__
diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
new file mode 100644
index 0000000000..922d8a3ba2
--- /dev/null
+++ b/netwerk/base/nsStandardURL.cpp
@@ -0,0 +1,3619 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IPCMessageUtils.h"
+
+#include "nsStandardURL.h"
+#include "nsCRT.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIIDNService.h"
+#include "mozilla/Logging.h"
+#include "nsAutoPtr.h"
+#include "nsIURLParser.h"
+#include "nsNetCID.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ipc/URIUtils.h"
+#include <algorithm>
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsContentUtils.h"
+#include "prprf.h"
+#include "nsReadableUtils.h"
+
+using mozilla::dom::EncodingUtils;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
+static NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID);
+
+nsIIDNService *nsStandardURL::gIDN = nullptr;
+bool nsStandardURL::gInitialized = false;
+bool nsStandardURL::gEscapeUTF8 = true;
+bool nsStandardURL::gAlwaysEncodeInUTF8 = true;
+char nsStandardURL::gHostLimitDigits[] = { '/', '\\', '?', '#', 0 };
+
+//
+// setenv MOZ_LOG nsStandardURL:5
+//
+static LazyLogModule gStandardURLLog("nsStandardURL");
+
+// The Chromium code defines its own LOG macro which we don't want
+#undef LOG
+#define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
+
+//----------------------------------------------------------------------------
+
+#define ENSURE_MUTABLE() \
+ PR_BEGIN_MACRO \
+ if (!mMutable) { \
+ NS_WARNING("attempt to modify an immutable nsStandardURL"); \
+ return NS_ERROR_ABORT; \
+ } \
+ PR_END_MACRO
+
+//----------------------------------------------------------------------------
+
+#ifdef MOZ_RUST_URLPARSE
+extern "C" int32_t c_fn_set_size(void * container, size_t size)
+{
+ ((nsACString *) container)->SetLength(size);
+ return 0;
+}
+
+extern "C" char * c_fn_get_buffer(void * container)
+{
+ return ((nsACString *) container)->BeginWriting();
+}
+#endif
+
+static nsresult
+EncodeString(nsIUnicodeEncoder *encoder, const nsAFlatString &str, nsACString &result)
+{
+ nsresult rv;
+ int32_t len = str.Length();
+ int32_t maxlen;
+
+ rv = encoder->GetMaxLength(str.get(), len, &maxlen);
+ if (NS_FAILED(rv))
+ return rv;
+
+ char buf[256], *p = buf;
+ if (uint32_t(maxlen) > sizeof(buf) - 1) {
+ p = (char *) malloc(maxlen + 1);
+ if (!p)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = encoder->Convert(str.get(), &len, p, &maxlen);
+ if (NS_FAILED(rv))
+ goto end;
+ if (rv == NS_ERROR_UENC_NOMAPPING) {
+ NS_WARNING("unicode conversion failed");
+ rv = NS_ERROR_UNEXPECTED;
+ goto end;
+ }
+ p[maxlen] = 0;
+ result.Assign(p);
+
+ len = sizeof(buf) - 1;
+ rv = encoder->Finish(buf, &len);
+ if (NS_FAILED(rv))
+ goto end;
+ buf[len] = 0;
+ result.Append(buf);
+
+end:
+ encoder->Reset();
+
+ if (p != buf)
+ free(p);
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsPrefObserver
+//----------------------------------------------------------------------------
+
+#define NS_NET_PREF_ESCAPEUTF8 "network.standard-url.escape-utf8"
+#define NS_NET_PREF_ALWAYSENCODEINUTF8 "network.standard-url.encode-utf8"
+
+NS_IMPL_ISUPPORTS(nsStandardURL::nsPrefObserver, nsIObserver)
+
+NS_IMETHODIMP nsStandardURL::
+nsPrefObserver::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch( do_QueryInterface(subject) );
+ if (prefBranch) {
+ PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get());
+ }
+ }
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsSegmentEncoder
+//----------------------------------------------------------------------------
+
+nsStandardURL::
+nsSegmentEncoder::nsSegmentEncoder(const char *charset)
+ : mCharset(charset)
+{
+}
+
+int32_t nsStandardURL::
+nsSegmentEncoder::EncodeSegmentCount(const char *str,
+ const URLSegment &seg,
+ int16_t mask,
+ nsAFlatCString &result,
+ bool &appended,
+ uint32_t extraLen)
+{
+ // extraLen is characters outside the segment that will be
+ // added when the segment is not empty (like the @ following
+ // a username).
+ appended = false;
+ if (!str)
+ return 0;
+ int32_t len = 0;
+ if (seg.mLen > 0) {
+ uint32_t pos = seg.mPos;
+ len = seg.mLen;
+
+ // first honor the origin charset if appropriate. as an optimization,
+ // only do this if the segment is non-ASCII. Further, if mCharset is
+ // null or the empty string then the origin charset is UTF-8 and there
+ // is nothing to do.
+ nsAutoCString encBuf;
+ if (mCharset && *mCharset && !nsCRT::IsAscii(str + pos, len)) {
+ // we have to encode this segment
+ if (mEncoder || InitUnicodeEncoder()) {
+ NS_ConvertUTF8toUTF16 ucsBuf(Substring(str + pos, str + pos + len));
+ if (NS_SUCCEEDED(EncodeString(mEncoder, ucsBuf, encBuf))) {
+ str = encBuf.get();
+ pos = 0;
+ len = encBuf.Length();
+ }
+ // else some failure occurred... assume UTF-8 is ok.
+ }
+ }
+
+ // escape per RFC2396 unless UTF-8 and allowed by preferences
+ int16_t escapeFlags = (gEscapeUTF8 || mEncoder) ? 0 : esc_OnlyASCII;
+
+ uint32_t initLen = result.Length();
+
+ // now perform any required escaping
+ if (NS_EscapeURL(str + pos, len, mask | escapeFlags, result)) {
+ len = result.Length() - initLen;
+ appended = true;
+ }
+ else if (str == encBuf.get()) {
+ result += encBuf; // append only!!
+ len = encBuf.Length();
+ appended = true;
+ }
+ len += extraLen;
+ }
+ return len;
+}
+
+const nsACString &nsStandardURL::
+nsSegmentEncoder::EncodeSegment(const nsASingleFragmentCString &str,
+ int16_t mask,
+ nsAFlatCString &result)
+{
+ const char *text;
+ bool encoded;
+ EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask, result, encoded);
+ if (encoded)
+ return result;
+ return str;
+}
+
+bool nsStandardURL::
+nsSegmentEncoder::InitUnicodeEncoder()
+{
+ NS_ASSERTION(!mEncoder, "Don't call this if we have an encoder already!");
+ // "replacement" won't survive another label resolution
+ nsDependentCString label(mCharset);
+ if (label.EqualsLiteral("replacement")) {
+ mEncoder = EncodingUtils::EncoderForEncoding(label);
+ return true;
+ }
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(label, encoding)) {
+ return false;
+ }
+ mEncoder = EncodingUtils::EncoderForEncoding(encoding);
+ return true;
+}
+
+#define GET_SEGMENT_ENCODER_INTERNAL(name, useUTF8) \
+ nsSegmentEncoder name(useUTF8 ? nullptr : mOriginCharset.get())
+
+#define GET_SEGMENT_ENCODER(name) \
+ GET_SEGMENT_ENCODER_INTERNAL(name, gAlwaysEncodeInUTF8)
+
+#define GET_QUERY_ENCODER(name) \
+ GET_SEGMENT_ENCODER_INTERNAL(name, false)
+
+//----------------------------------------------------------------------------
+// nsStandardURL <public>
+//----------------------------------------------------------------------------
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+static PRCList gAllURLs;
+#endif
+
+nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
+ : mDefaultPort(-1)
+ , mPort(-1)
+ , mHostA(nullptr)
+ , mHostEncoding(eEncoding_ASCII)
+ , mSpecEncoding(eEncoding_Unknown)
+ , mURLType(URLTYPE_STANDARD)
+ , mMutable(true)
+ , mSupportsFileURL(aSupportsFileURL)
+{
+ LOG(("Creating nsStandardURL @%p\n", this));
+
+ if (!gInitialized) {
+ gInitialized = true;
+ InitGlobalObjects();
+ }
+
+ // default parser in case nsIStandardURL::Init is never called
+ mParser = net_GetStdURLParser();
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ memset(&mDebugCList, 0, sizeof(mDebugCList));
+ if (NS_IsMainThread()) {
+ if (aTrackURL) {
+ PR_APPEND_LINK(&mDebugCList, &gAllURLs);
+ } else {
+ PR_INIT_CLIST(&mDebugCList);
+ }
+ }
+#endif
+}
+
+nsStandardURL::~nsStandardURL()
+{
+ LOG(("Destroying nsStandardURL @%p\n", this));
+
+ if (mHostA) {
+ free(mHostA);
+ }
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (NS_IsMainThread()) {
+ if (!PR_CLIST_IS_EMPTY(&mDebugCList)) {
+ PR_REMOVE_LINK(&mDebugCList);
+ }
+ }
+#endif
+}
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+struct DumpLeakedURLs {
+ DumpLeakedURLs() {}
+ ~DumpLeakedURLs();
+};
+
+DumpLeakedURLs::~DumpLeakedURLs()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!PR_CLIST_IS_EMPTY(&gAllURLs)) {
+ printf("Leaked URLs:\n");
+ for (PRCList *l = PR_LIST_HEAD(&gAllURLs); l != &gAllURLs; l = PR_NEXT_LINK(l)) {
+ nsStandardURL *url = reinterpret_cast<nsStandardURL*>(reinterpret_cast<char*>(l) - offsetof(nsStandardURL, mDebugCList));
+ url->PrintSpec();
+ }
+ }
+}
+#endif
+
+void
+nsStandardURL::InitGlobalObjects()
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID) );
+ if (prefBranch) {
+ nsCOMPtr<nsIObserver> obs( new nsPrefObserver() );
+ prefBranch->AddObserver(NS_NET_PREF_ESCAPEUTF8, obs.get(), false);
+ prefBranch->AddObserver(NS_NET_PREF_ALWAYSENCODEINUTF8, obs.get(), false);
+
+ PrefsChanged(prefBranch, nullptr);
+ }
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ PR_INIT_CLIST(&gAllURLs);
+#endif
+}
+
+void
+nsStandardURL::ShutdownGlobalObjects()
+{
+ NS_IF_RELEASE(gIDN);
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (gInitialized) {
+ // This instanciates a dummy class, and will trigger the class
+ // destructor when libxul is unloaded. This is equivalent to atexit(),
+ // but gracefully handles dlclose().
+ static DumpLeakedURLs d;
+ }
+#endif
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL <private>
+//----------------------------------------------------------------------------
+
+void
+nsStandardURL::Clear()
+{
+ mSpec.Truncate();
+
+ mPort = -1;
+
+ mScheme.Reset();
+ mAuthority.Reset();
+ mUsername.Reset();
+ mPassword.Reset();
+ mHost.Reset();
+ mHostEncoding = eEncoding_ASCII;
+
+ mPath.Reset();
+ mFilepath.Reset();
+ mDirectory.Reset();
+ mBasename.Reset();
+
+ mExtension.Reset();
+ mQuery.Reset();
+ mRef.Reset();
+
+ InvalidateCache();
+}
+
+void
+nsStandardURL::InvalidateCache(bool invalidateCachedFile)
+{
+ if (invalidateCachedFile)
+ mFile = nullptr;
+ if (mHostA) {
+ free(mHostA);
+ mHostA = nullptr;
+ }
+ mSpecEncoding = eEncoding_Unknown;
+}
+
+// |base| should be 8, 10 or 16. Not check the precondition for performance.
+/* static */ inline bool
+nsStandardURL::IsValidOfBase(unsigned char c, const uint32_t base) {
+ MOZ_ASSERT(base == 8 || base == 10 || base == 16, "invalid base");
+ if ('0' <= c && c <= '7') {
+ return true;
+ } else if (c == '8' || c== '9') {
+ return base != 8;
+ } else if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) {
+ return base == 16;
+ }
+ return false;
+}
+
+/* static */ inline nsresult
+nsStandardURL::ParseIPv4Number(nsCString &input, uint32_t &number)
+{
+ if (input.Length() == 0) {
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t base;
+ uint32_t prefixLength = 0;
+
+ if (input[0] == '0') {
+ if (input.Length() == 1) {
+ base = 10;
+ } else if (input[1] == 'x' || input[1] == 'X') {
+ base = 16;
+ prefixLength = 2;
+ } else {
+ base = 8;
+ prefixLength = 1;
+ }
+ } else {
+ base = 10;
+ }
+ if (prefixLength == input.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+ // ignore leading zeros to calculate the valid length of number
+ while (prefixLength < input.Length() && input[prefixLength] == '0') {
+ prefixLength++;
+ }
+ // all zero case
+ if (prefixLength == input.Length()) {
+ number = 0;
+ return NS_OK;
+ }
+ // overflow case
+ if (input.Length() - prefixLength > 16) {
+ return NS_ERROR_FAILURE;
+ }
+ for (uint32_t i = prefixLength; i < input.Length(); ++i) {
+ if (!IsValidOfBase(input[i], base)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ const char* fmt = "";
+ switch (base) {
+ case 8:
+ fmt = "%llo";
+ break;
+ case 10:
+ fmt = "%lli";
+ break;
+ case 16:
+ fmt = "%llx";
+ break;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+ uint64_t number64;
+ if (PR_sscanf(input.get(), fmt, &number64) == 1 &&
+ number64 <= 0xffffffffu) {
+ number = number64;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
+/* static */ nsresult
+nsStandardURL::NormalizeIPv4(const nsCSubstring &host, nsCString &result)
+{
+ if (host.Length() == 0 ||
+ host[0] < '0' || '9' < host[0] || // bail-out fast
+ FindInReadable(NS_LITERAL_CSTRING(".."), host)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<nsCString> parts;
+ if (!ParseString(host, '.', parts) ||
+ parts.Length() == 0 ||
+ parts.Length() > 4) {
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t n = 0;
+ nsTArray<int32_t> numbers;
+ for (uint32_t i = 0; i < parts.Length(); ++i) {
+ if (NS_FAILED(ParseIPv4Number(parts[i], n))) {
+ return NS_ERROR_FAILURE;
+ }
+ numbers.AppendElement(n);
+ }
+ uint32_t ipv4 = numbers.LastElement();
+ static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu,
+ 0xffffu, 0xffu};
+ if (ipv4 > upperBounds[numbers.Length() - 1]) {
+ return NS_ERROR_FAILURE;
+ }
+ for (uint32_t i = 0; i < numbers.Length() - 1; ++i) {
+ if (numbers[i] > 255) {
+ return NS_ERROR_FAILURE;
+ }
+ ipv4 += numbers[i] << (8 * (3 - i));
+ }
+
+ uint8_t ipSegments[4];
+ NetworkEndian::writeUint32(ipSegments, ipv4);
+ result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
+ ipSegments[2], ipSegments[3]);
+
+ return NS_OK;
+}
+
+nsresult
+nsStandardURL::NormalizeIDN(const nsCSubstring &host, nsCString &result)
+{
+ // If host is ACE, then convert to UTF-8. Else, if host is already UTF-8,
+ // then make sure it is normalized per IDN.
+
+ // this function returns true if normalization succeeds.
+
+ // NOTE: As a side-effect this function sets mHostEncoding. While it would
+ // be nice to avoid side-effects in this function, the implementation of
+ // this function is already somewhat bound to the behavior of the
+ // callsites. Anyways, this function exists to avoid code duplication, so
+ // side-effects abound :-/
+
+ NS_ASSERTION(mHostEncoding == eEncoding_ASCII, "unexpected default encoding");
+
+ bool isASCII;
+ if (!gIDN) {
+ nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
+ if (serv) {
+ NS_ADDREF(gIDN = serv.get());
+ }
+ }
+
+ result.Truncate();
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (gIDN) {
+ rv = gIDN->ConvertToDisplayIDN(host, &isASCII, result);
+ if (NS_SUCCEEDED(rv) && !isASCII) {
+ mHostEncoding = eEncoding_UTF8;
+ }
+ }
+
+ return rv;
+}
+
+bool
+nsStandardURL::ValidIPv6orHostname(const char *host, uint32_t length)
+{
+ if (!host || !*host) {
+ // Should not be NULL or empty string
+ return false;
+ }
+
+ if (length != strlen(host)) {
+ // Embedded null
+ return false;
+ }
+
+ bool openBracket = host[0] == '[';
+ bool closeBracket = host[length - 1] == ']';
+
+ if (openBracket && closeBracket) {
+ return net_IsValidIPv6Addr(host + 1, length - 2);
+ }
+
+ if (openBracket || closeBracket) {
+ // Fail if only one of the brackets is present
+ return false;
+ }
+
+ const char *end = host + length;
+ if (end != net_FindCharInSet(host, end, CONTROL_CHARACTERS " #/:?@[\\]*<>|\"")) {
+ // We still allow % because it is in the ID of addons.
+ // Any percent encoded ASCII characters that are not allowed in the
+ // hostname are not percent decoded, and will be parsed just fine.
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char *path)
+{
+ net_CoalesceDirs(coalesceFlag, path);
+ int32_t newLen = strlen(path);
+ if (newLen < mPath.mLen) {
+ int32_t diff = newLen - mPath.mLen;
+ mPath.mLen = newLen;
+ mDirectory.mLen += diff;
+ mFilepath.mLen += diff;
+ ShiftFromBasename(diff);
+ }
+}
+
+uint32_t
+nsStandardURL::AppendSegmentToBuf(char *buf, uint32_t i, const char *str,
+ const URLSegment &segInput, URLSegment &segOutput,
+ const nsCString *escapedStr,
+ bool useEscaped, int32_t *diff)
+{
+ MOZ_ASSERT(segInput.mLen == segOutput.mLen);
+
+ if (diff) *diff = 0;
+
+ if (segInput.mLen > 0) {
+ if (useEscaped) {
+ MOZ_ASSERT(diff);
+ segOutput.mLen = escapedStr->Length();
+ *diff = segOutput.mLen - segInput.mLen;
+ memcpy(buf + i, escapedStr->get(), segOutput.mLen);
+ } else {
+ memcpy(buf + i, str + segInput.mPos, segInput.mLen);
+ }
+ segOutput.mPos = i;
+ i += segOutput.mLen;
+ } else {
+ segOutput.mPos = i;
+ }
+ return i;
+}
+
+uint32_t
+nsStandardURL::AppendToBuf(char *buf, uint32_t i, const char *str, uint32_t len)
+{
+ memcpy(buf + i, str, len);
+ return i + len;
+}
+
+// basic algorithm:
+// 1- escape url segments (for improved GetSpec efficiency)
+// 2- allocate spec buffer
+// 3- write url segments
+// 4- update url segment positions and lengths
+nsresult
+nsStandardURL::BuildNormalizedSpec(const char *spec)
+{
+ // Assumptions: all member URLSegments must be relative the |spec| argument
+ // passed to this function.
+
+ // buffers for holding escaped url segments (these will remain empty unless
+ // escaping is required).
+ nsAutoCString encUsername, encPassword, encHost, encDirectory,
+ encBasename, encExtension, encQuery, encRef;
+ bool useEncUsername, useEncPassword, useEncHost = false,
+ useEncDirectory, useEncBasename, useEncExtension, useEncQuery, useEncRef;
+ nsAutoCString portbuf;
+
+ //
+ // escape each URL segment, if necessary, and calculate approximate normalized
+ // spec length.
+ //
+ // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref]
+
+ uint32_t approxLen = 0;
+
+ // the scheme is already ASCII
+ if (mScheme.mLen > 0)
+ approxLen += mScheme.mLen + 3; // includes room for "://", which we insert always
+
+ // encode URL segments; convert UTF-8 to origin charset and possibly escape.
+ // results written to encXXX variables only if |spec| is not already in the
+ // appropriate encoding.
+ {
+ GET_SEGMENT_ENCODER(encoder);
+ GET_QUERY_ENCODER(queryEncoder);
+ // Items using an extraLen of 1 don't add anything unless mLen > 0
+ // Username@
+ approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username, encUsername, useEncUsername, 1);
+ // :password - we insert the ':' even if there's no actual password if "user:@" was in the spec
+ if (mPassword.mLen >= 0)
+ approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword, esc_Password, encPassword, useEncPassword);
+ // mHost is handled differently below due to encoding differences
+ MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
+ if (mPort != -1 && mPort != mDefaultPort)
+ {
+ // :port
+ portbuf.AppendInt(mPort);
+ approxLen += portbuf.Length() + 1;
+ }
+
+ approxLen += 1; // reserve space for possible leading '/' - may not be needed
+ // Should just use mPath? These are pessimistic, and thus waste space
+ approxLen += encoder.EncodeSegmentCount(spec, mDirectory, esc_Directory, encDirectory, useEncDirectory, 1);
+ approxLen += encoder.EncodeSegmentCount(spec, mBasename, esc_FileBaseName, encBasename, useEncBasename);
+ approxLen += encoder.EncodeSegmentCount(spec, mExtension, esc_FileExtension, encExtension, useEncExtension, 1);
+
+ // These next ones *always* add their leading character even if length is 0
+ // Handles items like "http://#"
+ // ?query
+ if (mQuery.mLen >= 0)
+ approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query, encQuery, useEncQuery);
+ // #ref
+
+ if (mRef.mLen >= 0) {
+ if (nsContentUtils::EncodeDecodeURLHash()) {
+ approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref,
+ encRef, useEncRef);
+ } else {
+ approxLen += 1 + mRef.mLen;
+ useEncRef = false;
+ }
+ }
+ }
+
+ // do not escape the hostname, if IPv6 address literal, mHost will
+ // already point to a [ ] delimited IPv6 address literal.
+ // However, perform Unicode normalization on it, as IDN does.
+ mHostEncoding = eEncoding_ASCII;
+ // Note that we don't disallow URLs without a host - file:, etc
+ if (mHost.mLen > 0) {
+ nsAutoCString tempHost;
+ NS_UnescapeURL(spec + mHost.mPos, mHost.mLen, esc_AlwaysCopy | esc_Host, tempHost);
+ if (tempHost.Contains('\0'))
+ return NS_ERROR_MALFORMED_URI; // null embedded in hostname
+ if (tempHost.Contains(' '))
+ return NS_ERROR_MALFORMED_URI; // don't allow spaces in the hostname
+ nsresult rv = NormalizeIDN(tempHost, encHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!SegmentIs(spec, mScheme, "resource") &&
+ !SegmentIs(spec, mScheme, "chrome")) {
+ nsAutoCString ipString;
+ if (NS_SUCCEEDED(NormalizeIPv4(encHost, ipString))) {
+ encHost = ipString;
+ }
+ }
+
+ // NormalizeIDN always copies, if the call was successful.
+ useEncHost = true;
+ approxLen += encHost.Length();
+
+ if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ // We must take a copy of every single segment because they are pointing to
+ // the |spec| while we are changing their value, in case we must use
+ // encoded strings.
+ URLSegment username(mUsername);
+ URLSegment password(mPassword);
+ URLSegment host(mHost);
+ URLSegment path(mPath);
+ URLSegment filepath(mFilepath);
+ URLSegment directory(mDirectory);
+ URLSegment basename(mBasename);
+ URLSegment extension(mExtension);
+ URLSegment query(mQuery);
+ URLSegment ref(mRef);
+
+ //
+ // generate the normalized URL string
+ //
+ // approxLen should be correct or 1 high
+ if (!mSpec.SetLength(approxLen+1, fallible)) // buf needs a trailing '\0' below
+ return NS_ERROR_OUT_OF_MEMORY;
+ char *buf;
+ mSpec.BeginWriting(buf);
+ uint32_t i = 0;
+ int32_t diff = 0;
+
+ if (mScheme.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, mScheme, mScheme);
+ net_ToLowerCase(buf + mScheme.mPos, mScheme.mLen);
+ i = AppendToBuf(buf, i, "://", 3);
+ }
+
+ // record authority starting position
+ mAuthority.mPos = i;
+
+ // append authority
+ if (mUsername.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, username, mUsername,
+ &encUsername, useEncUsername, &diff);
+ ShiftFromPassword(diff);
+ if (password.mLen >= 0) {
+ buf[i++] = ':';
+ i = AppendSegmentToBuf(buf, i, spec, password, mPassword,
+ &encPassword, useEncPassword, &diff);
+ ShiftFromHost(diff);
+ }
+ buf[i++] = '@';
+ }
+ if (host.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost,
+ &diff);
+ ShiftFromPath(diff);
+
+ net_ToLowerCase(buf + mHost.mPos, mHost.mLen);
+ MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
+ if (mPort != -1 && mPort != mDefaultPort) {
+ buf[i++] = ':';
+ // Already formatted while building approxLen
+ i = AppendToBuf(buf, i, portbuf.get(), portbuf.Length());
+ }
+ }
+
+ // record authority length
+ mAuthority.mLen = i - mAuthority.mPos;
+
+ // path must always start with a "/"
+ if (mPath.mLen <= 0) {
+ LOG(("setting path=/"));
+ mDirectory.mPos = mFilepath.mPos = mPath.mPos = i;
+ mDirectory.mLen = mFilepath.mLen = mPath.mLen = 1;
+ // basename must exist, even if empty (bug 113508)
+ mBasename.mPos = i+1;
+ mBasename.mLen = 0;
+ buf[i++] = '/';
+ }
+ else {
+ uint32_t leadingSlash = 0;
+ if (spec[path.mPos] != '/') {
+ LOG(("adding leading slash to path\n"));
+ leadingSlash = 1;
+ buf[i++] = '/';
+ // basename must exist, even if empty (bugs 113508, 429347)
+ if (mBasename.mLen == -1) {
+ mBasename.mPos = basename.mPos = i;
+ mBasename.mLen = basename.mLen = 0;
+ }
+ }
+
+ // record corrected (file)path starting position
+ mPath.mPos = mFilepath.mPos = i - leadingSlash;
+
+ i = AppendSegmentToBuf(buf, i, spec, directory, mDirectory,
+ &encDirectory, useEncDirectory, &diff);
+ ShiftFromBasename(diff);
+
+ // the directory must end with a '/'
+ if (buf[i-1] != '/') {
+ buf[i++] = '/';
+ mDirectory.mLen++;
+ }
+
+ i = AppendSegmentToBuf(buf, i, spec, basename, mBasename,
+ &encBasename, useEncBasename, &diff);
+ ShiftFromExtension(diff);
+
+ // make corrections to directory segment if leadingSlash
+ if (leadingSlash) {
+ mDirectory.mPos = mPath.mPos;
+ if (mDirectory.mLen >= 0)
+ mDirectory.mLen += leadingSlash;
+ else
+ mDirectory.mLen = 1;
+ }
+
+ if (mExtension.mLen >= 0) {
+ buf[i++] = '.';
+ i = AppendSegmentToBuf(buf, i, spec, extension, mExtension,
+ &encExtension, useEncExtension, &diff);
+ ShiftFromQuery(diff);
+ }
+ // calculate corrected filepath length
+ mFilepath.mLen = i - mFilepath.mPos;
+
+ if (mQuery.mLen >= 0) {
+ buf[i++] = '?';
+ i = AppendSegmentToBuf(buf, i, spec, query, mQuery,
+ &encQuery, useEncQuery,
+ &diff);
+ ShiftFromRef(diff);
+ }
+ if (mRef.mLen >= 0) {
+ buf[i++] = '#';
+ i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef,
+ &diff);
+ }
+ // calculate corrected path length
+ mPath.mLen = i - mPath.mPos;
+ }
+
+ buf[i] = '\0';
+
+ if (mDirectory.mLen > 1) {
+ netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
+ if (SegmentIs(buf,mScheme,"ftp")) {
+ coalesceFlag = (netCoalesceFlags) (coalesceFlag
+ | NET_COALESCE_ALLOW_RELATIVE_ROOT
+ | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ CoalescePath(coalesceFlag, buf + mDirectory.mPos);
+ }
+ mSpec.SetLength(strlen(buf));
+ NS_ASSERTION(mSpec.Length() <= approxLen, "We've overflowed the mSpec buffer!");
+ return NS_OK;
+}
+
+bool
+nsStandardURL::SegmentIs(const URLSegment &seg, const char *val, bool ignoreCase)
+{
+ // one or both may be null
+ if (!val || mSpec.IsEmpty())
+ return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
+ if (seg.mLen < 0)
+ return false;
+ // if the first |seg.mLen| chars of |val| match, then |val| must
+ // also be null terminated at |seg.mLen|.
+ if (ignoreCase)
+ return !PL_strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen)
+ && (val[seg.mLen] == '\0');
+ else
+ return !strncmp(mSpec.get() + seg.mPos, val, seg.mLen)
+ && (val[seg.mLen] == '\0');
+}
+
+bool
+nsStandardURL::SegmentIs(const char* spec, const URLSegment &seg, const char *val, bool ignoreCase)
+{
+ // one or both may be null
+ if (!val || !spec)
+ return (!val && (!spec || seg.mLen < 0));
+ if (seg.mLen < 0)
+ return false;
+ // if the first |seg.mLen| chars of |val| match, then |val| must
+ // also be null terminated at |seg.mLen|.
+ if (ignoreCase)
+ return !PL_strncasecmp(spec + seg.mPos, val, seg.mLen)
+ && (val[seg.mLen] == '\0');
+ else
+ return !strncmp(spec + seg.mPos, val, seg.mLen)
+ && (val[seg.mLen] == '\0');
+}
+
+bool
+nsStandardURL::SegmentIs(const URLSegment &seg1, const char *val, const URLSegment &seg2, bool ignoreCase)
+{
+ if (seg1.mLen != seg2.mLen)
+ return false;
+ if (seg1.mLen == -1 || (!val && mSpec.IsEmpty()))
+ return true; // both are empty
+ if (!val)
+ return false;
+ if (ignoreCase)
+ return !PL_strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
+ else
+ return !strncmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
+}
+
+int32_t
+nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len, const char *val, uint32_t valLen)
+{
+ if (val && valLen) {
+ if (len == 0)
+ mSpec.Insert(val, pos, valLen);
+ else
+ mSpec.Replace(pos, len, nsDependentCString(val, valLen));
+ return valLen - len;
+ }
+
+ // else remove the specified segment
+ mSpec.Cut(pos, len);
+ return -int32_t(len);
+}
+
+int32_t
+nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len, const nsACString &val)
+{
+ if (len == 0)
+ mSpec.Insert(val, pos);
+ else
+ mSpec.Replace(pos, len, val);
+ return val.Length() - len;
+}
+
+nsresult
+nsStandardURL::ParseURL(const char *spec, int32_t specLen)
+{
+ nsresult rv;
+
+ if (specLen > net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ //
+ // parse given URL string
+ //
+ rv = mParser->ParseURL(spec, specLen,
+ &mScheme.mPos, &mScheme.mLen,
+ &mAuthority.mPos, &mAuthority.mLen,
+ &mPath.mPos, &mPath.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef DEBUG
+ if (mScheme.mLen <= 0) {
+ printf("spec=%s\n", spec);
+ NS_WARNING("malformed url: no scheme");
+ }
+#endif
+
+ if (mAuthority.mLen > 0) {
+ rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
+ &mUsername.mPos, &mUsername.mLen,
+ &mPassword.mPos, &mPassword.mLen,
+ &mHost.mPos, &mHost.mLen,
+ &mPort);
+ if (NS_FAILED(rv)) return rv;
+
+ // Don't allow mPort to be set to this URI's default port
+ if (mPort == mDefaultPort)
+ mPort = -1;
+
+ mUsername.mPos += mAuthority.mPos;
+ mPassword.mPos += mAuthority.mPos;
+ mHost.mPos += mAuthority.mPos;
+ }
+
+ if (mPath.mLen > 0)
+ rv = ParsePath(spec, mPath.mPos, mPath.mLen);
+
+ return rv;
+}
+
+nsresult
+nsStandardURL::ParsePath(const char *spec, uint32_t pathPos, int32_t pathLen)
+{
+ LOG(("ParsePath: %s pathpos %d len %d\n",spec,pathPos,pathLen));
+
+ if (pathLen > net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsresult rv = mParser->ParsePath(spec + pathPos, pathLen,
+ &mFilepath.mPos, &mFilepath.mLen,
+ &mQuery.mPos, &mQuery.mLen,
+ &mRef.mPos, &mRef.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+ mFilepath.mPos += pathPos;
+ mQuery.mPos += pathPos;
+ mRef.mPos += pathPos;
+
+ if (mFilepath.mLen > 0) {
+ rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
+ &mDirectory.mPos, &mDirectory.mLen,
+ &mBasename.mPos, &mBasename.mLen,
+ &mExtension.mPos, &mExtension.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+ mDirectory.mPos += mFilepath.mPos;
+ mBasename.mPos += mFilepath.mPos;
+ mExtension.mPos += mFilepath.mPos;
+ }
+ return NS_OK;
+}
+
+char *
+nsStandardURL::AppendToSubstring(uint32_t pos,
+ int32_t len,
+ const char *tail)
+{
+ // Verify pos and length are within boundaries
+ if (pos > mSpec.Length())
+ return nullptr;
+ if (len < 0)
+ return nullptr;
+ if ((uint32_t)len > (mSpec.Length() - pos))
+ return nullptr;
+ if (!tail)
+ return nullptr;
+
+ uint32_t tailLen = strlen(tail);
+
+ // Check for int overflow for proposed length of combined string
+ if (UINT32_MAX - ((uint32_t)len + 1) < tailLen)
+ return nullptr;
+
+ char *result = (char *) moz_xmalloc(len + tailLen + 1);
+ if (result) {
+ memcpy(result, mSpec.get() + pos, len);
+ memcpy(result + len, tail, tailLen);
+ result[len + tailLen] = '\0';
+ }
+ return result;
+}
+
+nsresult
+nsStandardURL::ReadSegment(nsIBinaryInputStream *stream, URLSegment &seg)
+{
+ nsresult rv;
+
+ rv = stream->Read32(&seg.mPos);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Read32((uint32_t *) &seg.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult
+nsStandardURL::WriteSegment(nsIBinaryOutputStream *stream, const URLSegment &seg)
+{
+ nsresult rv;
+
+ rv = stream->Write32(seg.mPos);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Write32(uint32_t(seg.mLen));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+/* static */ void
+nsStandardURL::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
+{
+ bool val;
+
+ LOG(("nsStandardURL::PrefsChanged [pref=%s]\n", pref));
+
+#define PREF_CHANGED(p) ((pref == nullptr) || !strcmp(pref, p))
+#define GOT_PREF(p, b) (NS_SUCCEEDED(prefs->GetBoolPref(p, &b)))
+
+ if (PREF_CHANGED(NS_NET_PREF_ESCAPEUTF8)) {
+ if (GOT_PREF(NS_NET_PREF_ESCAPEUTF8, val))
+ gEscapeUTF8 = val;
+ LOG(("escape UTF-8 %s\n", gEscapeUTF8 ? "enabled" : "disabled"));
+ }
+
+ if (PREF_CHANGED(NS_NET_PREF_ALWAYSENCODEINUTF8)) {
+ if (GOT_PREF(NS_NET_PREF_ALWAYSENCODEINUTF8, val))
+ gAlwaysEncodeInUTF8 = val;
+ LOG(("encode in UTF-8 %s\n", gAlwaysEncodeInUTF8 ? "enabled" : "disabled"));
+ }
+#undef PREF_CHANGED
+#undef GOT_PREF
+}
+
+#define SHIFT_FROM(name, what) \
+void \
+nsStandardURL::name(int32_t diff) \
+{ \
+ if (!diff) return; \
+ if (what.mLen >= 0) { \
+ CheckedInt<int32_t> pos = what.mPos; \
+ pos += diff; \
+ MOZ_ASSERT(pos.isValid()); \
+ what.mPos = pos.value(); \
+ }
+
+#define SHIFT_FROM_NEXT(name, what, next) \
+ SHIFT_FROM(name, what) \
+ next(diff); \
+}
+
+#define SHIFT_FROM_LAST(name, what) \
+ SHIFT_FROM(name, what) \
+}
+
+SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername)
+SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword)
+SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost)
+SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath)
+SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath)
+SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory)
+SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename)
+SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension)
+SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery)
+SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef)
+SHIFT_FROM_LAST(ShiftFromRef, mRef)
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsStandardURL)
+NS_IMPL_RELEASE(nsStandardURL)
+
+NS_INTERFACE_MAP_BEGIN(nsStandardURL)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURIWithQuery)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
+ NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
+ NS_INTERFACE_MAP_ENTRY(nsIMutable)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI)
+ NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI)
+ // see nsStandardURL::Equals
+ if (aIID.Equals(kThisImplCID))
+ foundInterface = static_cast<nsIURI *>(this);
+ else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIURI
+//----------------------------------------------------------------------------
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSpec(nsACString &result)
+{
+ MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
+ "The spec should never be this long, we missed a check.");
+ result = mSpec;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString &result)
+{
+ result = mSpec;
+ if (mPassword.mLen >= 0) {
+ result.Replace(mPassword.mPos, mPassword.mLen, "****");
+ }
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSpecIgnoringRef(nsACString &result)
+{
+ // URI without ref is 0 to one char before ref
+ if (mRef.mLen >= 0) {
+ URLSegment noRef(0, mRef.mPos - 1);
+
+ result = Segment(noRef);
+ } else {
+ result = mSpec;
+ }
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPrePath(nsACString &result)
+{
+ result = Prepath();
+ return NS_OK;
+}
+
+// result is strictly US-ASCII
+NS_IMETHODIMP
+nsStandardURL::GetScheme(nsACString &result)
+{
+ result = Scheme();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetUserPass(nsACString &result)
+{
+ result = Userpass();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetUsername(nsACString &result)
+{
+ result = Username();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPassword(nsACString &result)
+{
+ result = Password();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHostPort(nsACString &result)
+{
+ result = Hostport();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHost(nsACString &result)
+{
+ result = Host();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetPort(int32_t *result)
+{
+ // should never be more than 16 bit
+ MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
+ *result = mPort;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPath(nsACString &result)
+{
+ result = Path();
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiSpec(nsACString &result)
+{
+ if (mSpecEncoding == eEncoding_Unknown) {
+ if (IsASCII(mSpec))
+ mSpecEncoding = eEncoding_ASCII;
+ else
+ mSpecEncoding = eEncoding_UTF8;
+ }
+
+ if (mSpecEncoding == eEncoding_ASCII) {
+ result = mSpec;
+ return NS_OK;
+ }
+
+ // try to guess the capacity required for result...
+ result.SetCapacity(mSpec.Length() + std::min<uint32_t>(32, mSpec.Length()/10));
+
+ result = Substring(mSpec, 0, mScheme.mLen + 3);
+
+ // This is left fallible as this entire function is expected to be
+ // infallible.
+ NS_EscapeURL(Userpass(true), esc_OnlyNonASCII | esc_AlwaysCopy, result);
+
+ // get the hostport
+ nsAutoCString hostport;
+ MOZ_ALWAYS_SUCCEEDS(GetAsciiHostPort(hostport));
+ result += hostport;
+
+ // This is left fallible as this entire function is expected to be
+ // infallible.
+ NS_EscapeURL(Path(), esc_OnlyNonASCII | esc_AlwaysCopy, result);
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiHostPort(nsACString &result)
+{
+ if (mHostEncoding == eEncoding_ASCII) {
+ result = Hostport();
+ return NS_OK;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(GetAsciiHost(result));
+
+ // As our mHostEncoding is not eEncoding_ASCII, we know that
+ // the our host is not ipv6, and we can avoid looking at it.
+ MOZ_ASSERT(result.FindChar(':') == -1, "The host must not be ipv6");
+
+ // hostport = "hostA" + ":port"
+ uint32_t pos = mHost.mPos + mHost.mLen;
+ if (pos < mPath.mPos)
+ result += Substring(mSpec, pos, mPath.mPos - pos);
+
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiHost(nsACString &result)
+{
+ if (mHostEncoding == eEncoding_ASCII) {
+ result = Host();
+ return NS_OK;
+ }
+
+ // perhaps we have it cached...
+ if (mHostA) {
+ result = mHostA;
+ return NS_OK;
+ }
+
+ if (gIDN) {
+ nsresult rv;
+ rv = gIDN->ConvertUTF8toACE(Host(), result);
+ if (NS_SUCCEEDED(rv)) {
+ mHostA = ToNewCString(result);
+ return NS_OK;
+ }
+ NS_WARNING("nsIDNService::ConvertUTF8toACE failed");
+ }
+
+ // something went wrong... guess all we can do is URL escape :-/
+ NS_EscapeURL(Host(), esc_OnlyNonASCII | esc_AlwaysCopy, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetOriginCharset(nsACString &result)
+{
+ if (mOriginCharset.IsEmpty())
+ result.AssignLiteral("UTF-8");
+ else
+ result = mOriginCharset;
+ return NS_OK;
+}
+
+static bool
+IsSpecialProtocol(const nsACString &input)
+{
+ nsACString::const_iterator start, end;
+ input.BeginReading(start);
+ nsACString::const_iterator iterator(start);
+ input.EndReading(end);
+
+ while (iterator != end && *iterator != ':') {
+ iterator++;
+ }
+
+ nsAutoCString protocol(nsDependentCSubstring(start.get(), iterator.get()));
+
+ return protocol.LowerCaseEqualsLiteral("http") ||
+ protocol.LowerCaseEqualsLiteral("https") ||
+ protocol.LowerCaseEqualsLiteral("ftp") ||
+ protocol.LowerCaseEqualsLiteral("ws") ||
+ protocol.LowerCaseEqualsLiteral("wss") ||
+ protocol.LowerCaseEqualsLiteral("file") ||
+ protocol.LowerCaseEqualsLiteral("gopher");
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetSpec(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));
+
+ if (input.Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI;
+ net_FilterURIString(flat, filteredURI);
+
+ if (filteredURI.Length() == 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Make a backup of the curent URL
+ nsStandardURL prevURL(false,false);
+ prevURL.CopyMembers(this, eHonorRef, EmptyCString());
+ Clear();
+
+ if (IsSpecialProtocol(filteredURI)) {
+ // Bug 652186: Replace all backslashes with slashes when parsing paths
+ // Stop when we reach the query or the hash.
+ nsAutoCString::iterator start;
+ nsAutoCString::iterator end;
+ filteredURI.BeginWriting(start);
+ filteredURI.EndWriting(end);
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ const char *spec = filteredURI.get();
+ int32_t specLength = filteredURI.Length();
+
+ // parse the given URL...
+ nsresult rv = ParseURL(spec, specLength);
+ if (NS_SUCCEEDED(rv)) {
+ // finally, use the URLSegment member variables to build a normalized
+ // copy of |spec|
+ rv = BuildNormalizedSpec(spec);
+ }
+
+ if (NS_FAILED(rv)) {
+ Clear();
+ // If parsing the spec has failed, restore the old URL
+ // so we don't end up with an empty URL.
+ CopyMembers(&prevURL, eHonorRef, EmptyCString());
+ return rv;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG((" spec = %s\n", mSpec.get()));
+ LOG((" port = %d\n", mPort));
+ LOG((" scheme = (%u,%d)\n", mScheme.mPos, mScheme.mLen));
+ LOG((" authority = (%u,%d)\n", mAuthority.mPos, mAuthority.mLen));
+ LOG((" username = (%u,%d)\n", mUsername.mPos, mUsername.mLen));
+ LOG((" password = (%u,%d)\n", mPassword.mPos, mPassword.mLen));
+ LOG((" hostname = (%u,%d)\n", mHost.mPos, mHost.mLen));
+ LOG((" path = (%u,%d)\n", mPath.mPos, mPath.mLen));
+ LOG((" filepath = (%u,%d)\n", mFilepath.mPos, mFilepath.mLen));
+ LOG((" directory = (%u,%d)\n", mDirectory.mPos, mDirectory.mLen));
+ LOG((" basename = (%u,%d)\n", mBasename.mPos, mBasename.mLen));
+ LOG((" extension = (%u,%d)\n", mExtension.mPos, mExtension.mLen));
+ LOG((" query = (%u,%d)\n", mQuery.mPos, mQuery.mLen));
+ LOG((" ref = (%u,%d)\n", mRef.mPos, mRef.mLen));
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetScheme(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &scheme = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));
+
+ if (scheme.IsEmpty()) {
+ NS_WARNING("cannot remove the scheme from an url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mScheme.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!net_IsValidScheme(scheme)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Scheme().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
+
+ if (shift) {
+ mScheme.mLen = scheme.Length();
+ ShiftFromAuthority(shift);
+ }
+
+ // ensure new scheme is lowercase
+ //
+ // XXX the string code unfortunately doesn't provide a ToLowerCase
+ // that operates on a substring.
+ net_ToLowerCase((char *) mSpec.get(), mScheme.mLen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetUserPass(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &userpass = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (userpass.IsEmpty())
+ return NS_OK;
+ NS_WARNING("cannot set user:pass on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mAuthority.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mSpec.Length() + input.Length() - Userpass(true).Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (userpass.IsEmpty()) {
+ // remove user:pass
+ if (mUsername.mLen > 0) {
+ if (mPassword.mLen > 0)
+ mUsername.mLen += (mPassword.mLen + 1);
+ mUsername.mLen++;
+ mSpec.Cut(mUsername.mPos, mUsername.mLen);
+ mAuthority.mLen -= mUsername.mLen;
+ ShiftFromHost(-mUsername.mLen);
+ mUsername.mLen = -1;
+ mPassword.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ NS_ASSERTION(mHost.mLen >= 0, "uninitialized");
+
+ nsresult rv;
+ uint32_t usernamePos, passwordPos;
+ int32_t usernameLen, passwordLen;
+
+ rv = mParser->ParseUserInfo(userpass.get(), userpass.Length(),
+ &usernamePos, &usernameLen,
+ &passwordPos, &passwordLen);
+ if (NS_FAILED(rv)) return rv;
+
+ // build new user:pass in |buf|
+ nsAutoCString buf;
+ if (usernameLen > 0) {
+ GET_SEGMENT_ENCODER(encoder);
+ bool ignoredOut;
+ usernameLen = encoder.EncodeSegmentCount(userpass.get(),
+ URLSegment(usernamePos,
+ usernameLen),
+ esc_Username | esc_AlwaysCopy,
+ buf, ignoredOut);
+ if (passwordLen >= 0) {
+ buf.Append(':');
+ passwordLen = encoder.EncodeSegmentCount(userpass.get(),
+ URLSegment(passwordPos,
+ passwordLen),
+ esc_Password |
+ esc_AlwaysCopy, buf,
+ ignoredOut);
+ }
+ if (mUsername.mLen < 0)
+ buf.Append('@');
+ }
+
+ uint32_t shift = 0;
+
+ if (mUsername.mLen < 0) {
+ // no existing user:pass
+ if (!buf.IsEmpty()) {
+ mSpec.Insert(buf, mHost.mPos);
+ mUsername.mPos = mHost.mPos;
+ shift = buf.Length();
+ }
+ }
+ else {
+ // replace existing user:pass
+ uint32_t userpassLen = mUsername.mLen;
+ if (mPassword.mLen >= 0)
+ userpassLen += (mPassword.mLen + 1);
+ mSpec.Replace(mUsername.mPos, userpassLen, buf);
+ shift = buf.Length() - userpassLen;
+ }
+ if (shift) {
+ ShiftFromHost(shift);
+ mAuthority.mLen += shift;
+ }
+ // update positions and lengths
+ mUsername.mLen = usernameLen;
+ mPassword.mLen = passwordLen;
+ if (passwordLen)
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetUsername(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &username = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetUsername [username=%s]\n", username.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (username.IsEmpty())
+ return NS_OK;
+ NS_WARNING("cannot set username on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (username.IsEmpty())
+ return SetUserPass(username);
+
+ if (mSpec.Length() + input.Length() - Username().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ // escape username if necessary
+ nsAutoCString buf;
+ GET_SEGMENT_ENCODER(encoder);
+ const nsACString &escUsername =
+ encoder.EncodeSegment(username, esc_Username, buf);
+
+ int32_t shift;
+
+ if (mUsername.mLen < 0) {
+ mUsername.mPos = mAuthority.mPos;
+ mSpec.Insert(escUsername + NS_LITERAL_CSTRING("@"), mUsername.mPos);
+ shift = escUsername.Length() + 1;
+ }
+ else
+ shift = ReplaceSegment(mUsername.mPos, mUsername.mLen, escUsername);
+
+ if (shift) {
+ mUsername.mLen = escUsername.Length();
+ mAuthority.mLen += shift;
+ ShiftFromPassword(shift);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetPassword(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &password = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetPassword [password=%s]\n", password.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (password.IsEmpty())
+ return NS_OK;
+ NS_WARNING("cannot set password on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mUsername.mLen <= 0) {
+ NS_WARNING("cannot set password without existing username");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mSpec.Length() + input.Length() - Password().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (password.IsEmpty()) {
+ if (mPassword.mLen >= 0) {
+ // cut(":password")
+ mSpec.Cut(mPassword.mPos - 1, mPassword.mLen + 1);
+ ShiftFromHost(-(mPassword.mLen + 1));
+ mAuthority.mLen -= (mPassword.mLen + 1);
+ mPassword.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ // escape password if necessary
+ nsAutoCString buf;
+ GET_SEGMENT_ENCODER(encoder);
+ const nsACString &escPassword =
+ encoder.EncodeSegment(password, esc_Password, buf);
+
+ int32_t shift;
+
+ if (mPassword.mLen < 0) {
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ mSpec.Insert(NS_LITERAL_CSTRING(":") + escPassword, mPassword.mPos - 1);
+ shift = escPassword.Length() + 1;
+ }
+ else
+ shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword);
+
+ if (shift) {
+ mPassword.mLen = escPassword.Length();
+ mAuthority.mLen += shift;
+ ShiftFromHost(shift);
+ }
+ return NS_OK;
+}
+
+void
+nsStandardURL::FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd)
+{
+ for (int32_t i = 0; gHostLimitDigits[i]; ++i) {
+ nsACString::const_iterator c(aStart);
+ if (FindCharInReadable(gHostLimitDigits[i], c, aEnd)) {
+ aEnd = c;
+ }
+ }
+}
+
+// If aValue only has a host part and no port number, the port
+// will not be reset!!!
+NS_IMETHODIMP
+nsStandardURL::SetHostPort(const nsACString &aValue)
+{
+ ENSURE_MUTABLE();
+
+ // We cannot simply call nsIURI::SetHost because that would treat the name as
+ // an IPv6 address (like http:://[server:443]/). We also cannot call
+ // nsIURI::SetHostPort because that isn't implemented. Sadfaces.
+
+ nsACString::const_iterator start, end;
+ aValue.BeginReading(start);
+ aValue.EndReading(end);
+ nsACString::const_iterator iter(start);
+ bool isIPv6 = false;
+
+ FindHostLimit(start, end);
+
+ if (*start == '[') { // IPv6 address
+ if (!FindCharInReadable(']', iter, end)) {
+ // the ] character is missing
+ return NS_ERROR_MALFORMED_URI;
+ }
+ // iter now at the ']' character
+ isIPv6 = true;
+ } else {
+ nsACString::const_iterator iter2(start);
+ if (FindCharInReadable(']', iter2, end)) {
+ // if the first char isn't [ then there should be no ] character
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ FindCharInReadable(':', iter, end);
+
+ if (!isIPv6 && iter != end) {
+ nsACString::const_iterator iter2(iter);
+ iter2++; // Skip over the first ':' character
+ if (FindCharInReadable(':', iter2, end)) {
+ // If there is more than one ':' character it suggests an IPv6
+ // The format should be [2001::1]:80 where the port is optional
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ nsresult rv = SetHost(Substring(start, iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Also set the port if needed.
+ if (iter != end) {
+ iter++;
+ if (iter != end) {
+ nsCString portStr(Substring(iter, end));
+ nsresult rv;
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetPort(port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Failure parsing port number
+ return NS_ERROR_MALFORMED_URI;
+ }
+ } else {
+ // port number is missing
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ return NS_OK;
+}
+
+// This function is different than SetHostPort in that the port number will be
+// reset as well if aValue parameter does not contain a port port number.
+NS_IMETHODIMP
+nsStandardURL::SetHostAndPort(const nsACString &aValue)
+{
+ // Reset the port and than call SetHostPort. SetHostPort does not reset
+ // the port number.
+ nsresult rv = SetPort(-1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetHostPort(aValue);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetHost(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &hostname = PromiseFlatCString(input);
+
+ nsACString::const_iterator start, end;
+ hostname.BeginReading(start);
+ hostname.EndReading(end);
+
+ FindHostLimit(start, end);
+
+ const nsCString unescapedHost(Substring(start, end));
+ // Do percent decoding on the the input.
+ nsAutoCString flat;
+ NS_UnescapeURL(unescapedHost.BeginReading(), unescapedHost.Length(),
+ esc_AlwaysCopy | esc_Host, flat);
+ const char *host = flat.get();
+
+ LOG(("nsStandardURL::SetHost [host=%s]\n", host));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (flat.IsEmpty())
+ return NS_OK;
+ NS_WARNING("cannot set host on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ } else {
+ if (flat.IsEmpty()) {
+ // Setting an empty hostname is not allowed for
+ // URLTYPE_STANDARD and URLTYPE_AUTHORITY.
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (strlen(host) < flat.Length())
+ return NS_ERROR_MALFORMED_URI; // found embedded null
+
+ // For consistency with SetSpec/nsURLParsers, don't allow spaces
+ // in the hostname.
+ if (strchr(host, ' '))
+ return NS_ERROR_MALFORMED_URI;
+
+ if (mSpec.Length() + strlen(host) - Host().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+ mHostEncoding = eEncoding_ASCII;
+
+ uint32_t len;
+ nsAutoCString hostBuf;
+ nsresult rv = NormalizeIDN(flat, hostBuf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!SegmentIs(mScheme, "resource") && !SegmentIs(mScheme, "chrome")) {
+ nsAutoCString ipString;
+ if (NS_SUCCEEDED(NormalizeIPv4(hostBuf, ipString))) {
+ hostBuf = ipString;
+ }
+ }
+
+ // NormalizeIDN always copies if the call was successful
+ host = hostBuf.get();
+ len = hostBuf.Length();
+
+ if (!ValidIPv6orHostname(host, len)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mHost.mLen < 0) {
+ int port_length = 0;
+ if (mPort != -1) {
+ nsAutoCString buf;
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ port_length = buf.Length();
+ }
+ if (mAuthority.mLen > 0) {
+ mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length;
+ mHost.mLen = 0;
+ } else if (mScheme.mLen > 0) {
+ mHost.mPos = mScheme.mPos + mScheme.mLen + 3;
+ mHost.mLen = 0;
+ }
+ }
+
+ int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len);
+
+ if (shift) {
+ mHost.mLen = len;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+ }
+
+ // Now canonicalize the host to lowercase
+ net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetPort(int32_t port)
+{
+ ENSURE_MUTABLE();
+
+ LOG(("nsStandardURL::SetPort [port=%d]\n", port));
+
+ if ((port == mPort) || (mPort == -1 && port == mDefaultPort))
+ return NS_OK;
+
+ // ports must be >= 0 and 16 bit
+ // -1 == use default
+ if (port < -1 || port > std::numeric_limits<uint16_t>::max())
+ return NS_ERROR_MALFORMED_URI;
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ NS_WARNING("cannot set port on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ InvalidateCache();
+ if (port == mDefaultPort) {
+ port = -1;
+ }
+
+ ReplacePortInSpec(port);
+
+ mPort = port;
+ return NS_OK;
+}
+
+/**
+ * Replaces the existing port in mSpec with aNewPort.
+ *
+ * The caller is responsible for:
+ * - Calling InvalidateCache (since our mSpec is changing).
+ * - Checking whether aNewPort is mDefaultPort (in which case the
+ * caller should pass aNewPort=-1).
+ */
+void
+nsStandardURL::ReplacePortInSpec(int32_t aNewPort)
+{
+ MOZ_ASSERT(mMutable, "Caller should ensure we're mutable");
+ NS_ASSERTION(aNewPort != mDefaultPort || mDefaultPort == -1,
+ "Caller should check its passed-in value and pass -1 instead of "
+ "mDefaultPort, to avoid encoding default port into mSpec");
+
+ // Create the (possibly empty) string that we're planning to replace:
+ nsAutoCString buf;
+ if (mPort != -1) {
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ }
+ // Find the position & length of that string:
+ const uint32_t replacedLen = buf.Length();
+ const uint32_t replacedStart =
+ mAuthority.mPos + mAuthority.mLen - replacedLen;
+
+ // Create the (possibly empty) replacement string:
+ if (aNewPort == -1) {
+ buf.Truncate();
+ } else {
+ buf.Assign(':');
+ buf.AppendInt(aNewPort);
+ }
+ // Perform the replacement:
+ mSpec.Replace(replacedStart, replacedLen, buf);
+
+ // Bookkeeping to reflect the new length:
+ int32_t shift = buf.Length() - replacedLen;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetPath(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &path = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetPath [path=%s]\n", path.get()));
+
+ InvalidateCache();
+
+ if (!path.IsEmpty()) {
+ nsAutoCString spec;
+
+ spec.Assign(mSpec.get(), mPath.mPos);
+ if (path.First() != '/')
+ spec.Append('/');
+ spec.Append(path);
+
+ return SetSpec(spec);
+ }
+ else if (mPath.mLen >= 1) {
+ mSpec.Cut(mPath.mPos + 1, mPath.mLen - 1);
+ // these contain only a '/'
+ mPath.mLen = 1;
+ mDirectory.mLen = 1;
+ mFilepath.mLen = 1;
+ // these are no longer defined
+ mBasename.mLen = -1;
+ mExtension.mLen = -1;
+ mQuery.mLen = -1;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Equals(nsIURI *unknownOther, bool *result)
+{
+ return EqualsInternal(unknownOther, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::EqualsExceptRef(nsIURI *unknownOther, bool *result)
+{
+ return EqualsInternal(unknownOther, eIgnoreRef, result);
+}
+
+nsresult
+nsStandardURL::EqualsInternal(nsIURI *unknownOther,
+ nsStandardURL::RefHandlingEnum refHandlingMode,
+ bool *result)
+{
+ NS_ENSURE_ARG_POINTER(unknownOther);
+ NS_PRECONDITION(result, "null pointer");
+
+ RefPtr<nsStandardURL> other;
+ nsresult rv = unknownOther->QueryInterface(kThisImplCID,
+ getter_AddRefs(other));
+ if (NS_FAILED(rv)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // First, check whether one URIs is an nsIFileURL while the other
+ // is not. If that's the case, they're different.
+ if (mSupportsFileURL != other->mSupportsFileURL) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // Next check parts of a URI that, if different, automatically make the
+ // URIs different
+ if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) ||
+ // Check for host manually, since conversion to file will
+ // ignore the host!
+ !SegmentIs(mHost, other->mSpec.get(), other->mHost) ||
+ !SegmentIs(mQuery, other->mSpec.get(), other->mQuery) ||
+ !SegmentIs(mUsername, other->mSpec.get(), other->mUsername) ||
+ !SegmentIs(mPassword, other->mSpec.get(), other->mPassword) ||
+ Port() != other->Port()) {
+ // No need to compare files or other URI parts -- these are different
+ // beasties
+ *result = false;
+ return NS_OK;
+ }
+
+ if (refHandlingMode == eHonorRef &&
+ !SegmentIs(mRef, other->mSpec.get(), other->mRef)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // Then check for exact identity of URIs. If we have it, they're equal
+ if (SegmentIs(mDirectory, other->mSpec.get(), other->mDirectory) &&
+ SegmentIs(mBasename, other->mSpec.get(), other->mBasename) &&
+ SegmentIs(mExtension, other->mSpec.get(), other->mExtension)) {
+ *result = true;
+ return NS_OK;
+ }
+
+ // At this point, the URIs are not identical, but they only differ in the
+ // directory/filename/extension. If these are file URLs, then get the
+ // corresponding file objects and compare those, since two filenames that
+ // differ, eg, only in case could still be equal.
+ if (mSupportsFileURL) {
+ // Assume not equal for failure cases... but failures in GetFile are
+ // really failures, more or less, so propagate them to caller.
+ *result = false;
+
+ rv = EnsureFile();
+ nsresult rv2 = other->EnsureFile();
+ // special case for resource:// urls that don't resolve to files
+ if (rv == NS_ERROR_NO_INTERFACE && rv == rv2)
+ return NS_OK;
+
+ if (NS_FAILED(rv)) {
+ LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file",
+ this, mSpec.get()));
+ return rv;
+ }
+ NS_ASSERTION(mFile, "EnsureFile() lied!");
+ rv = rv2;
+ if (NS_FAILED(rv)) {
+ LOG(("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure file",
+ other.get(), other->mSpec.get()));
+ return rv;
+ }
+ NS_ASSERTION(other->mFile, "EnsureFile() lied!");
+ return mFile->Equals(other->mFile, result);
+ }
+
+ // The URLs are not identical, and they do not correspond to the
+ // same file, so they are different.
+ *result = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SchemeIs(const char *scheme, bool *result)
+{
+ NS_PRECONDITION(result, "null pointer");
+
+ *result = SegmentIs(mScheme, scheme);
+ return NS_OK;
+}
+
+/* virtual */ nsStandardURL*
+nsStandardURL::StartClone()
+{
+ nsStandardURL *clone = new nsStandardURL();
+ return clone;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Clone(nsIURI **result)
+{
+ return CloneInternal(eHonorRef, EmptyCString(), result);
+}
+
+
+NS_IMETHODIMP
+nsStandardURL::CloneIgnoringRef(nsIURI **result)
+{
+ return CloneInternal(eIgnoreRef, EmptyCString(), result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::CloneWithNewRef(const nsACString& newRef, nsIURI **result)
+{
+ return CloneInternal(eReplaceRef, newRef, result);
+}
+
+nsresult
+nsStandardURL::CloneInternal(nsStandardURL::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef,
+ nsIURI **result)
+
+{
+ RefPtr<nsStandardURL> clone = StartClone();
+ if (!clone)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Copy local members into clone.
+ // Also copies the cached members mFile, mHostA
+ clone->CopyMembers(this, refHandlingMode, newRef, true);
+
+ clone.forget(result);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::CopyMembers(nsStandardURL * source,
+ nsStandardURL::RefHandlingEnum refHandlingMode, const nsACString& newRef,
+ bool copyCached)
+{
+ mSpec = source->mSpec;
+ mDefaultPort = source->mDefaultPort;
+ mPort = source->mPort;
+ mScheme = source->mScheme;
+ mAuthority = source->mAuthority;
+ mUsername = source->mUsername;
+ mPassword = source->mPassword;
+ mHost = source->mHost;
+ mPath = source->mPath;
+ mFilepath = source->mFilepath;
+ mDirectory = source->mDirectory;
+ mBasename = source->mBasename;
+ mExtension = source->mExtension;
+ mQuery = source->mQuery;
+ mRef = source->mRef;
+ mOriginCharset = source->mOriginCharset;
+ mURLType = source->mURLType;
+ mParser = source->mParser;
+ mMutable = true;
+ mSupportsFileURL = source->mSupportsFileURL;
+ mHostEncoding = source->mHostEncoding;
+
+ if (copyCached) {
+ mFile = source->mFile;
+ mHostA = source->mHostA ? strdup(source->mHostA) : nullptr;
+ mSpecEncoding = source->mSpecEncoding;
+ } else {
+ // The same state as after calling InvalidateCache()
+ mFile = nullptr;
+ mHostA = nullptr;
+ mSpecEncoding = eEncoding_Unknown;
+ }
+
+ if (refHandlingMode == eIgnoreRef) {
+ SetRef(EmptyCString());
+ } else if (refHandlingMode == eReplaceRef) {
+ SetRef(newRef);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Resolve(const nsACString &in, nsACString &out)
+{
+ const nsPromiseFlatCString &flat = PromiseFlatCString(in);
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString buf;
+ net_FilterURIString(flat, buf);
+
+ const char *relpath = buf.get();
+ int32_t relpathLen = buf.Length();
+
+ char *result = nullptr;
+
+ LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n",
+ this, mSpec.get(), relpath));
+
+ NS_ASSERTION(mParser, "no parser: unitialized");
+
+ // NOTE: there is no need for this function to produce normalized
+ // output. normalization will occur when the result is used to
+ // initialize a nsStandardURL object.
+
+ if (mScheme.mLen < 0) {
+ NS_WARNING("unable to Resolve URL: this URL not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+ URLSegment scheme;
+ char *resultPath = nullptr;
+ bool relative = false;
+ uint32_t offset = 0;
+ netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
+
+ // relative urls should never contain a host, so we always want to use
+ // the noauth url parser.
+ // use it to extract a possible scheme
+ rv = mParser->ParseURL(relpath,
+ relpathLen,
+ &scheme.mPos, &scheme.mLen,
+ nullptr, nullptr,
+ nullptr, nullptr);
+
+ // if the parser fails (for example because there is no valid scheme)
+ // reset the scheme and assume a relative url
+ if (NS_FAILED(rv)) scheme.Reset();
+
+ nsAutoCString protocol(Segment(scheme));
+ nsAutoCString baseProtocol(Scheme());
+
+ // We need to do backslash replacement for the following cases:
+ // 1. The input is an absolute path with a http/https/ftp scheme
+ // 2. The input is a relative path, and the base URL has a http/https/ftp scheme
+ if ((protocol.IsEmpty() && IsSpecialProtocol(baseProtocol)) ||
+ IsSpecialProtocol(protocol)) {
+
+ nsAutoCString::iterator start;
+ nsAutoCString::iterator end;
+ buf.BeginWriting(start);
+ buf.EndWriting(end);
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ if (scheme.mLen >= 0) {
+ // add some flags to coalesceFlag if it is an ftp-url
+ // need this later on when coalescing the resulting URL
+ if (SegmentIs(relpath, scheme, "ftp", true)) {
+ coalesceFlag = (netCoalesceFlags) (coalesceFlag
+ | NET_COALESCE_ALLOW_RELATIVE_ROOT
+ | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+
+ }
+ // this URL appears to be absolute
+ // but try to find out more
+ if (SegmentIs(mScheme, relpath, scheme, true)) {
+ // mScheme and Scheme are the same
+ // but this can still be relative
+ if (nsCRT::strncmp(relpath + scheme.mPos + scheme.mLen,
+ "://",3) == 0) {
+ // now this is really absolute
+ // because a :// follows the scheme
+ result = NS_strdup(relpath);
+ } else {
+ // This is a deprecated form of relative urls like
+ // http:file or http:/path/file
+ // we will support it for now ...
+ relative = true;
+ offset = scheme.mLen + 1;
+ }
+ } else {
+ // the schemes are not the same, we are also done
+ // because we have to assume this is absolute
+ result = NS_strdup(relpath);
+ }
+ } else {
+ // add some flags to coalesceFlag if it is an ftp-url
+ // need this later on when coalescing the resulting URL
+ if (SegmentIs(mScheme,"ftp")) {
+ coalesceFlag = (netCoalesceFlags) (coalesceFlag
+ | NET_COALESCE_ALLOW_RELATIVE_ROOT
+ | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ if (relpath[0] == '/' && relpath[1] == '/') {
+ // this URL //host/path is almost absolute
+ result = AppendToSubstring(mScheme.mPos, mScheme.mLen + 1, relpath);
+ } else {
+ // then it must be relative
+ relative = true;
+ }
+ }
+ if (relative) {
+ uint32_t len = 0;
+ const char *realrelpath = relpath + offset;
+ switch (*realrelpath) {
+ case '/':
+ // overwrite everything after the authority
+ len = mAuthority.mPos + mAuthority.mLen;
+ break;
+ case '?':
+ // overwrite the existing ?query and #ref
+ if (mQuery.mLen >= 0)
+ len = mQuery.mPos - 1;
+ else if (mRef.mLen >= 0)
+ len = mRef.mPos - 1;
+ else
+ len = mPath.mPos + mPath.mLen;
+ break;
+ case '#':
+ case '\0':
+ // overwrite the existing #ref
+ if (mRef.mLen < 0)
+ len = mPath.mPos + mPath.mLen;
+ else
+ len = mRef.mPos - 1;
+ break;
+ default:
+ if (coalesceFlag & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) {
+ if (Filename().Equals(NS_LITERAL_CSTRING("%2F"),
+ nsCaseInsensitiveCStringComparator())) {
+ // if ftp URL ends with %2F then simply
+ // append relative part because %2F also
+ // marks the root directory with ftp-urls
+ len = mFilepath.mPos + mFilepath.mLen;
+ } else {
+ // overwrite everything after the directory
+ len = mDirectory.mPos + mDirectory.mLen;
+ }
+ } else {
+ // overwrite everything after the directory
+ len = mDirectory.mPos + mDirectory.mLen;
+ }
+ }
+ result = AppendToSubstring(0, len, realrelpath);
+ // locate result path
+ resultPath = result + mPath.mPos;
+ }
+ if (!result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (resultPath)
+ net_CoalesceDirs(coalesceFlag, resultPath);
+ else {
+ // locate result path
+ resultPath = PL_strstr(result, "://");
+ if (resultPath) {
+ resultPath = PL_strchr(resultPath + 3, '/');
+ if (resultPath)
+ net_CoalesceDirs(coalesceFlag,resultPath);
+ }
+ }
+ out.Adopt(result);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetCommonBaseSpec(nsIURI *uri2, nsACString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ // if uri's are equal, then return uri as is
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals)
+ return GetSpec(aResult);
+
+ aResult.Truncate();
+
+ // check pre-path; if they don't match, then return empty string
+ nsStandardURL *stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, (void **) &stdurl2);
+ isEquals = NS_SUCCEEDED(rv)
+ && SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme)
+ && SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost)
+ && SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername)
+ && SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword)
+ && (Port() == stdurl2->Port());
+ if (!isEquals)
+ {
+ if (NS_SUCCEEDED(rv))
+ NS_RELEASE(stdurl2);
+ return NS_OK;
+ }
+
+ // scan for first mismatched character
+ const char *thisIndex, *thatIndex, *startCharPos;
+ startCharPos = mSpec.get() + mDirectory.mPos;
+ thisIndex = startCharPos;
+ thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
+ while ((*thisIndex == *thatIndex) && *thisIndex)
+ {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // backup to just after previous slash so we grab an appropriate path
+ // segment such as a directory (not partial segments)
+ // todo: also check for file matches which include '?' and '#'
+ while ((thisIndex != startCharPos) && (*(thisIndex-1) != '/'))
+ thisIndex--;
+
+ // grab spec from beginning to thisIndex
+ aResult = Substring(mSpec, mScheme.mPos, thisIndex - mSpec.get());
+
+ NS_RELEASE(stdurl2);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetRelativeSpec(nsIURI *uri2, nsACString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ aResult.Truncate();
+
+ // if uri's are equal, then return empty string
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals)
+ return NS_OK;
+
+ nsStandardURL *stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, (void **) &stdurl2);
+ isEquals = NS_SUCCEEDED(rv)
+ && SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme)
+ && SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost)
+ && SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername)
+ && SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword)
+ && (Port() == stdurl2->Port());
+ if (!isEquals)
+ {
+ if (NS_SUCCEEDED(rv))
+ NS_RELEASE(stdurl2);
+
+ return uri2->GetSpec(aResult);
+ }
+
+ // scan for first mismatched character
+ const char *thisIndex, *thatIndex, *startCharPos;
+ startCharPos = mSpec.get() + mDirectory.mPos;
+ thisIndex = startCharPos;
+ thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
+
+#ifdef XP_WIN
+ bool isFileScheme = SegmentIs(mScheme, "file");
+ if (isFileScheme)
+ {
+ // on windows, we need to match the first segment of the path
+ // if these don't match then we need to return an absolute path
+ // skip over any leading '/' in path
+ while ((*thisIndex == *thatIndex) && (*thisIndex == '/'))
+ {
+ thisIndex++;
+ thatIndex++;
+ }
+ // look for end of first segment
+ while ((*thisIndex == *thatIndex) && *thisIndex && (*thisIndex != '/'))
+ {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // if we didn't match through the first segment, return absolute path
+ if ((*thisIndex != '/') || (*thatIndex != '/'))
+ {
+ NS_RELEASE(stdurl2);
+ return uri2->GetSpec(aResult);
+ }
+ }
+#endif
+
+ while ((*thisIndex == *thatIndex) && *thisIndex)
+ {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // backup to just after previous slash so we grab an appropriate path
+ // segment such as a directory (not partial segments)
+ // todo: also check for file matches with '#' and '?'
+ while ((*(thatIndex-1) != '/') && (thatIndex != startCharPos))
+ thatIndex--;
+
+ const char *limit = mSpec.get() + mFilepath.mPos + mFilepath.mLen;
+
+ // need to account for slashes and add corresponding "../"
+ for (; thisIndex <= limit && *thisIndex; ++thisIndex)
+ {
+ if (*thisIndex == '/')
+ aResult.AppendLiteral("../");
+ }
+
+ // grab spec from thisIndex to end
+ uint32_t startPos = stdurl2->mScheme.mPos + thatIndex - stdurl2->mSpec.get();
+ aResult.Append(Substring(stdurl2->mSpec, startPos,
+ stdurl2->mSpec.Length() - startPos));
+
+ NS_RELEASE(stdurl2);
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIURL
+//----------------------------------------------------------------------------
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFilePath(nsACString &result)
+{
+ result = Filepath();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetQuery(nsACString &result)
+{
+ result = Query();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetRef(nsACString &result)
+{
+ result = Ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasRef(bool *result)
+{
+ *result = (mRef.mLen >= 0);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetDirectory(nsACString &result)
+{
+ result = Directory();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileName(nsACString &result)
+{
+ result = Filename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileBaseName(nsACString &result)
+{
+ result = Basename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileExtension(nsACString &result)
+{
+ result = Extension();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFilePath(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ const char *filepath = flat.get();
+
+ LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath));
+
+ // if there isn't a filepath, then there can't be anything
+ // after the path either. this url is likely uninitialized.
+ if (mFilepath.mLen < 0)
+ return SetPath(flat);
+
+ if (filepath && *filepath) {
+ nsAutoCString spec;
+ uint32_t dirPos, basePos, extPos;
+ int32_t dirLen, baseLen, extLen;
+ nsresult rv;
+
+ rv = mParser->ParseFilePath(filepath, flat.Length(),
+ &dirPos, &dirLen,
+ &basePos, &baseLen,
+ &extPos, &extLen);
+ if (NS_FAILED(rv)) return rv;
+
+ // build up new candidate spec
+ spec.Assign(mSpec.get(), mPath.mPos);
+
+ // ensure leading '/'
+ if (filepath[dirPos] != '/')
+ spec.Append('/');
+
+ GET_SEGMENT_ENCODER(encoder);
+
+ // append encoded filepath components
+ if (dirLen > 0)
+ encoder.EncodeSegment(Substring(filepath + dirPos,
+ filepath + dirPos + dirLen),
+ esc_Directory | esc_AlwaysCopy, spec);
+ if (baseLen > 0)
+ encoder.EncodeSegment(Substring(filepath + basePos,
+ filepath + basePos + baseLen),
+ esc_FileBaseName | esc_AlwaysCopy, spec);
+ if (extLen >= 0) {
+ spec.Append('.');
+ if (extLen > 0)
+ encoder.EncodeSegment(Substring(filepath + extPos,
+ filepath + extPos + extLen),
+ esc_FileExtension | esc_AlwaysCopy,
+ spec);
+ }
+
+ // compute the ending position of the current filepath
+ if (mFilepath.mLen >= 0) {
+ uint32_t end = mFilepath.mPos + mFilepath.mLen;
+ if (mSpec.Length() > end)
+ spec.Append(mSpec.get() + end, mSpec.Length() - end);
+ }
+
+ return SetSpec(spec);
+ }
+ else if (mPath.mLen > 1) {
+ mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1);
+ // left shift query, and ref
+ ShiftFromQuery(1 - mFilepath.mLen);
+ // these contain only a '/'
+ mPath.mLen = 1;
+ mDirectory.mLen = 1;
+ mFilepath.mLen = 1;
+ // these are no longer defined
+ mBasename.mLen = -1;
+ mExtension.mLen = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetQuery(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ const char *query = flat.get();
+
+ LOG(("nsStandardURL::SetQuery [query=%s]\n", query));
+
+ if (mPath.mLen < 0)
+ return SetPath(flat);
+
+ if (mSpec.Length() + input.Length() - Query().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (!query || !*query) {
+ // remove existing query
+ if (mQuery.mLen >= 0) {
+ // remove query and leading '?'
+ mSpec.Cut(mQuery.mPos - 1, mQuery.mLen + 1);
+ ShiftFromRef(-(mQuery.mLen + 1));
+ mPath.mLen -= (mQuery.mLen + 1);
+ mQuery.mPos = 0;
+ mQuery.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ int32_t queryLen = flat.Length();
+ if (query[0] == '?') {
+ query++;
+ queryLen--;
+ }
+
+ if (mQuery.mLen < 0) {
+ if (mRef.mLen < 0)
+ mQuery.mPos = mSpec.Length();
+ else
+ mQuery.mPos = mRef.mPos - 1;
+ mSpec.Insert('?', mQuery.mPos);
+ mQuery.mPos++;
+ mQuery.mLen = 0;
+ // the insertion pushes these out by 1
+ mPath.mLen++;
+ mRef.mPos++;
+ }
+
+ // encode query if necessary
+ nsAutoCString buf;
+ bool encoded;
+ GET_QUERY_ENCODER(encoder);
+ encoder.EncodeSegmentCount(query, URLSegment(0, queryLen), esc_Query,
+ buf, encoded);
+ if (encoded) {
+ query = buf.get();
+ queryLen = buf.Length();
+ }
+
+ int32_t shift = ReplaceSegment(mQuery.mPos, mQuery.mLen, query, queryLen);
+
+ if (shift) {
+ mQuery.mLen = queryLen;
+ mPath.mLen += shift;
+ ShiftFromRef(shift);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetRef(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ const char *ref = flat.get();
+
+ LOG(("nsStandardURL::SetRef [ref=%s]\n", ref));
+
+ if (mPath.mLen < 0)
+ return SetPath(flat);
+
+ if (mSpec.Length() + input.Length() - Ref().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (!ref || !*ref) {
+ // remove existing ref
+ if (mRef.mLen >= 0) {
+ // remove ref and leading '#'
+ mSpec.Cut(mRef.mPos - 1, mRef.mLen + 1);
+ mPath.mLen -= (mRef.mLen + 1);
+ mRef.mPos = 0;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ int32_t refLen = flat.Length();
+ if (ref[0] == '#') {
+ ref++;
+ refLen--;
+ }
+
+ if (mRef.mLen < 0) {
+ mSpec.Append('#');
+ ++mPath.mLen; // Include the # in the path.
+ mRef.mPos = mSpec.Length();
+ mRef.mLen = 0;
+ }
+
+ // If precent encoding is necessary, `ref` will point to `buf`'s content.
+ // `buf` needs to outlive any use of the `ref` pointer.
+ nsAutoCString buf;
+ if (nsContentUtils::EncodeDecodeURLHash()) {
+ // encode ref if necessary
+ bool encoded;
+ GET_SEGMENT_ENCODER(encoder);
+ encoder.EncodeSegmentCount(ref, URLSegment(0, refLen), esc_Ref,
+ buf, encoded);
+ if (encoded) {
+ ref = buf.get();
+ refLen = buf.Length();
+ }
+ }
+
+ int32_t shift = ReplaceSegment(mRef.mPos, mRef.mLen, ref, refLen);
+ mPath.mLen += shift;
+ mRef.mLen = refLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetDirectory(const nsACString &input)
+{
+ NS_NOTYETIMPLEMENTED("");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFileName(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ const char *filename = flat.get();
+
+ LOG(("nsStandardURL::SetFileName [filename=%s]\n", filename));
+
+ if (mPath.mLen < 0)
+ return SetPath(flat);
+
+ if (mSpec.Length() + input.Length() - Filename().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ int32_t shift = 0;
+
+ if (!(filename && *filename)) {
+ // remove the filename
+ if (mBasename.mLen > 0) {
+ if (mExtension.mLen >= 0)
+ mBasename.mLen += (mExtension.mLen + 1);
+ mSpec.Cut(mBasename.mPos, mBasename.mLen);
+ shift = -mBasename.mLen;
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ }
+ else {
+ nsresult rv;
+ URLSegment basename, extension;
+
+ // let the parser locate the basename and extension
+ rv = mParser->ParseFileName(filename, flat.Length(),
+ &basename.mPos, &basename.mLen,
+ &extension.mPos, &extension.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+ if (basename.mLen < 0) {
+ // remove existing filename
+ if (mBasename.mLen >= 0) {
+ uint32_t len = mBasename.mLen;
+ if (mExtension.mLen >= 0)
+ len += (mExtension.mLen + 1);
+ mSpec.Cut(mBasename.mPos, len);
+ shift = -int32_t(len);
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ }
+ else {
+ nsAutoCString newFilename;
+ bool ignoredOut;
+ GET_SEGMENT_ENCODER(encoder);
+ basename.mLen = encoder.EncodeSegmentCount(filename, basename,
+ esc_FileBaseName |
+ esc_AlwaysCopy,
+ newFilename,
+ ignoredOut);
+ if (extension.mLen >= 0) {
+ newFilename.Append('.');
+ extension.mLen = encoder.EncodeSegmentCount(filename, extension,
+ esc_FileExtension |
+ esc_AlwaysCopy,
+ newFilename,
+ ignoredOut);
+ }
+
+ if (mBasename.mLen < 0) {
+ // insert new filename
+ mBasename.mPos = mDirectory.mPos + mDirectory.mLen;
+ mSpec.Insert(newFilename, mBasename.mPos);
+ shift = newFilename.Length();
+ }
+ else {
+ // replace existing filename
+ uint32_t oldLen = uint32_t(mBasename.mLen);
+ if (mExtension.mLen >= 0)
+ oldLen += (mExtension.mLen + 1);
+ mSpec.Replace(mBasename.mPos, oldLen, newFilename);
+ shift = newFilename.Length() - oldLen;
+ }
+
+ mBasename.mLen = basename.mLen;
+ mExtension.mLen = extension.mLen;
+ if (mExtension.mLen >= 0)
+ mExtension.mPos = mBasename.mPos + mBasename.mLen + 1;
+ }
+ }
+ if (shift) {
+ ShiftFromQuery(shift);
+ mFilepath.mLen += shift;
+ mPath.mLen += shift;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFileBaseName(const nsACString &input)
+{
+ nsAutoCString extension;
+ nsresult rv = GetFileExtension(extension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newFileName(input);
+
+ if (!extension.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(extension);
+ }
+
+ return SetFileName(newFileName);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFileExtension(const nsACString &input)
+{
+ nsAutoCString newFileName;
+ nsresult rv = GetFileBaseName(newFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!input.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(input);
+ }
+
+ return SetFileName(newFileName);
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIFileURL
+//----------------------------------------------------------------------------
+
+nsresult
+nsStandardURL::EnsureFile()
+{
+ NS_PRECONDITION(mSupportsFileURL,
+ "EnsureFile() called on a URL that doesn't support files!");
+ if (mFile) {
+ // Nothing to do
+ return NS_OK;
+ }
+
+ // Parse the spec if we don't have a cached result
+ if (mSpec.IsEmpty()) {
+ NS_WARNING("url not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!SegmentIs(mScheme, "file")) {
+ NS_WARNING("not a file URL");
+ return NS_ERROR_FAILURE;
+ }
+
+ return net_GetFileFromURLSpec(mSpec, getter_AddRefs(mFile));
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetFile(nsIFile **result)
+{
+ NS_PRECONDITION(mSupportsFileURL,
+ "GetFile() called on a URL that doesn't support files!");
+ nsresult rv = EnsureFile();
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (LOG_ENABLED()) {
+ nsAutoCString path;
+ mFile->GetNativePath(path);
+ LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n",
+ this, mSpec.get(), path.get()));
+ }
+
+ // clone the file, so the caller can modify it.
+ // XXX nsIFileURL.idl specifies that the consumer must _not_ modify the
+ // nsIFile returned from this method; but it seems that some folks do
+ // (see bug 161921). until we can be sure that all the consumers are
+ // behaving themselves, we'll stay on the safe side and clone the file.
+ // see bug 212724 about fixing the consumers.
+ return mFile->Clone(result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFile(nsIFile *file)
+{
+ ENSURE_MUTABLE();
+
+ NS_ENSURE_ARG_POINTER(file);
+
+ nsresult rv;
+ nsAutoCString url;
+
+ rv = net_GetURLSpecFromFile(file, url);
+ if (NS_FAILED(rv)) return rv;
+
+ SetSpec(url);
+
+ rv = Init(mURLType, mDefaultPort, url, nullptr, nullptr);
+
+ // must clone |file| since its value is not guaranteed to remain constant
+ if (NS_SUCCEEDED(rv)) {
+ InvalidateCache();
+ if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) {
+ NS_WARNING("nsIFile::Clone failed");
+ // failure to clone is not fatal (GetFile will generate mFile)
+ mFile = nullptr;
+ }
+ }
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIStandardURL
+//----------------------------------------------------------------------------
+
+inline bool
+IsUTFCharset(const char *aCharset)
+{
+ return ((aCharset[0] == 'U' || aCharset[0] == 'u') &&
+ (aCharset[1] == 'T' || aCharset[1] == 't') &&
+ (aCharset[2] == 'F' || aCharset[2] == 'f'));
+}
+
+NS_IMETHODIMP
+nsStandardURL::Init(uint32_t urlType,
+ int32_t defaultPort,
+ const nsACString &spec,
+ const char *charset,
+ nsIURI *baseURI)
+{
+ ENSURE_MUTABLE();
+
+ if (spec.Length() > (uint32_t) net_GetURLMaxLength() ||
+ defaultPort > std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ switch (urlType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ NS_NOTREACHED("bad urlType");
+ return NS_ERROR_INVALID_ARG;
+ }
+ mDefaultPort = defaultPort;
+ mURLType = urlType;
+
+ mOriginCharset.Truncate();
+
+ //if charset override is absent, use UTF8 as url encoding
+ if (charset != nullptr && *charset != '\0' && !IsUTFCharset(charset)) {
+ mOriginCharset = charset;
+ }
+
+ if (baseURI && net_IsAbsoluteURL(spec)) {
+ baseURI = nullptr;
+ }
+
+ if (!baseURI)
+ return SetSpec(spec);
+
+ nsAutoCString buf;
+ nsresult rv = baseURI->Resolve(spec, buf);
+ if (NS_FAILED(rv)) return rv;
+
+ return SetSpec(buf);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort)
+{
+ ENSURE_MUTABLE();
+
+ InvalidateCache();
+
+ // should never be more than 16 bit
+ if (aNewDefaultPort >= std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // If we're already using the new default-port as a custom port, then clear
+ // it off of our mSpec & set mPort to -1, to indicate that we'll be using
+ // the default from now on (which happens to match what we already had).
+ if (mPort == aNewDefaultPort) {
+ ReplacePortInSpec(-1);
+ mPort = -1;
+ }
+ mDefaultPort = aNewDefaultPort;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetMutable(bool *value)
+{
+ *value = mMutable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetMutable(bool value)
+{
+ NS_ENSURE_ARG(mMutable || !value);
+
+ mMutable = value;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISerializable
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsStandardURL::Read(nsIObjectInputStream *stream)
+{
+ NS_PRECONDITION(!mHostA, "Shouldn't have cached ASCII host");
+ NS_PRECONDITION(mSpecEncoding == eEncoding_Unknown,
+ "Shouldn't have spec encoding here");
+
+ nsresult rv;
+
+ uint32_t urlType;
+ rv = stream->Read32(&urlType);
+ if (NS_FAILED(rv)) return rv;
+ mURLType = urlType;
+ switch (mURLType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ NS_NOTREACHED("bad urlType");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = stream->Read32((uint32_t *) &mPort);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Read32((uint32_t *) &mDefaultPort);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_ReadOptionalCString(stream, mSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mAuthority);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mUsername);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mPassword);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mHost);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mPath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mFilepath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mDirectory);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mBasename);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ // handle forward compatibility from older serializations that included mParam
+ URLSegment old_param;
+ rv = ReadSegment(stream, old_param);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mQuery);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mRef);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_ReadOptionalCString(stream, mOriginCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ bool isMutable;
+ rv = stream->ReadBoolean(&isMutable);
+ if (NS_FAILED(rv)) return rv;
+ mMutable = isMutable;
+
+ bool supportsFileURL;
+ rv = stream->ReadBoolean(&supportsFileURL);
+ if (NS_FAILED(rv)) return rv;
+ mSupportsFileURL = supportsFileURL;
+
+ uint32_t hostEncoding;
+ rv = stream->Read32(&hostEncoding);
+ if (NS_FAILED(rv)) return rv;
+ if (hostEncoding != eEncoding_ASCII && hostEncoding != eEncoding_UTF8) {
+ NS_WARNING("Unexpected host encoding");
+ return NS_ERROR_UNEXPECTED;
+ }
+ mHostEncoding = hostEncoding;
+
+ // wait until object is set up, then modify path to include the param
+ if (old_param.mLen >= 0) { // note that mLen=0 is ";"
+ // If this wasn't empty, it marks characters between the end of the
+ // file and start of the query - mPath should include the param,
+ // query and ref already. Bump the mFilePath and
+ // directory/basename/extension components to include this.
+ mFilepath.Merge(mSpec, ';', old_param);
+ mDirectory.Merge(mSpec, ';', old_param);
+ mBasename.Merge(mSpec, ';', old_param);
+ mExtension.Merge(mSpec, ';', old_param);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Write(nsIObjectOutputStream *stream)
+{
+ MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
+ "The spec should never be this long, we missed a check.");
+ nsresult rv;
+
+ rv = stream->Write32(mURLType);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Write32(uint32_t(mPort));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Write32(uint32_t(mDefaultPort));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_WriteOptionalStringZ(stream, mSpec.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mAuthority);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mUsername);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mPassword);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mHost);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mPath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mFilepath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mDirectory);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mBasename);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ // for backwards compatibility since we removed mParam. Note that this will mean that
+ // an older browser will read "" for mParam, and the param(s) will be part of mPath (as they
+ // after the removal of special handling). It only matters if you downgrade a browser to before
+ // the patch.
+ URLSegment empty;
+ rv = WriteSegment(stream, empty);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mQuery);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mRef);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_WriteOptionalStringZ(stream, mOriginCharset.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->WriteBoolean(mMutable);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->WriteBoolean(mSupportsFileURL);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Write32(mHostEncoding);
+ if (NS_FAILED(rv)) return rv;
+
+ // mSpecEncoding and mHostA are just caches that can be recovered as needed.
+
+ return NS_OK;
+}
+
+//---------------------------------------------------------------------------
+// nsStandardURL::nsIIPCSerializableURI
+//---------------------------------------------------------------------------
+
+inline
+ipc::StandardURLSegment
+ToIPCSegment(const nsStandardURL::URLSegment& aSegment)
+{
+ return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen);
+}
+
+inline
+nsStandardURL::URLSegment
+FromIPCSegment(const ipc::StandardURLSegment& aSegment)
+{
+ return nsStandardURL::URLSegment(aSegment.position(), aSegment.length());
+}
+
+void
+nsStandardURL::Serialize(URIParams& aParams)
+{
+ MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
+ "The spec should never be this long, we missed a check.");
+ StandardURLParams params;
+
+ params.urlType() = mURLType;
+ params.port() = mPort;
+ params.defaultPort() = mDefaultPort;
+ params.spec() = mSpec;
+ params.scheme() = ToIPCSegment(mScheme);
+ params.authority() = ToIPCSegment(mAuthority);
+ params.username() = ToIPCSegment(mUsername);
+ params.password() = ToIPCSegment(mPassword);
+ params.host() = ToIPCSegment(mHost);
+ params.path() = ToIPCSegment(mPath);
+ params.filePath() = ToIPCSegment(mFilepath);
+ params.directory() = ToIPCSegment(mDirectory);
+ params.baseName() = ToIPCSegment(mBasename);
+ params.extension() = ToIPCSegment(mExtension);
+ params.query() = ToIPCSegment(mQuery);
+ params.ref() = ToIPCSegment(mRef);
+ params.originCharset() = mOriginCharset;
+ params.isMutable() = !!mMutable;
+ params.supportsFileURL() = !!mSupportsFileURL;
+ params.hostEncoding() = mHostEncoding;
+ // mSpecEncoding and mHostA are just caches that can be recovered as needed.
+
+ aParams = params;
+}
+
+bool
+nsStandardURL::Deserialize(const URIParams& aParams)
+{
+ NS_PRECONDITION(!mHostA, "Shouldn't have cached ASCII host");
+ NS_PRECONDITION(mSpecEncoding == eEncoding_Unknown,
+ "Shouldn't have spec encoding here");
+ NS_PRECONDITION(!mFile, "Shouldn't have cached file");
+
+ if (aParams.type() != URIParams::TStandardURLParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const StandardURLParams& params = aParams.get_StandardURLParams();
+
+ mURLType = params.urlType();
+ switch (mURLType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ NS_NOTREACHED("bad urlType");
+ return false;
+ }
+
+ if (params.hostEncoding() != eEncoding_ASCII &&
+ params.hostEncoding() != eEncoding_UTF8) {
+ NS_WARNING("Unexpected host encoding");
+ return false;
+ }
+
+ mPort = params.port();
+ mDefaultPort = params.defaultPort();
+ mSpec = params.spec();
+ mScheme = FromIPCSegment(params.scheme());
+ mAuthority = FromIPCSegment(params.authority());
+ mUsername = FromIPCSegment(params.username());
+ mPassword = FromIPCSegment(params.password());
+ mHost = FromIPCSegment(params.host());
+ mPath = FromIPCSegment(params.path());
+ mFilepath = FromIPCSegment(params.filePath());
+ mDirectory = FromIPCSegment(params.directory());
+ mBasename = FromIPCSegment(params.baseName());
+ mExtension = FromIPCSegment(params.extension());
+ mQuery = FromIPCSegment(params.query());
+ mRef = FromIPCSegment(params.ref());
+ mOriginCharset = params.originCharset();
+ mMutable = params.isMutable();
+ mSupportsFileURL = params.supportsFileURL();
+ mHostEncoding = params.hostEncoding();
+
+ // mSpecEncoding and mHostA are just caches that can be recovered as needed.
+ return true;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsStandardURL::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ *count = 0;
+ *array = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetContractID(char * *aContractID)
+{
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetClassID(nsCID * *aClassID)
+{
+ *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
+ if (!*aClassID)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return GetClassIDNoAlloc(*aClassID);
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetFlags(uint32_t *aFlags)
+{
+ *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kStandardURLCID;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t
+nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mOriginCharset.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ aMallocSizeOf(mHostA);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mParser
+ // - mFile
+}
+
+size_t
+nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStandardURL.h b/netwerk/base/nsStandardURL.h
new file mode 100644
index 0000000000..90f7f7db24
--- /dev/null
+++ b/netwerk/base/nsStandardURL.h
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStandardURL_h__
+#define nsStandardURL_h__
+
+#include "nsString.h"
+#include "nsISerializable.h"
+#include "nsIFileURL.h"
+#include "nsIStandardURL.h"
+#include "nsIUnicodeEncoder.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsURLHelper.h"
+#include "nsIClassInfo.h"
+#include "nsISizeOf.h"
+#include "prclist.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsIIPCSerializableURI.h"
+#include "nsISensitiveInfoHiddenURI.h"
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+#define DEBUG_DUMP_URLS_AT_SHUTDOWN
+#endif
+
+class nsIBinaryInputStream;
+class nsIBinaryOutputStream;
+class nsIIDNService;
+class nsIPrefBranch;
+class nsIFile;
+class nsIURLParser;
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// standard URL implementation
+//-----------------------------------------------------------------------------
+
+class nsStandardURL : public nsIFileURL
+ , public nsIStandardURL
+ , public nsISerializable
+ , public nsIClassInfo
+ , public nsISizeOf
+ , public nsIIPCSerializableURI
+ , public nsISensitiveInfoHiddenURI
+{
+protected:
+ virtual ~nsStandardURL();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIURIWITHQUERY
+ NS_DECL_NSIURL
+ NS_DECL_NSIFILEURL
+ NS_DECL_NSISTANDARDURL
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIMUTABLE
+ NS_DECL_NSIIPCSERIALIZABLEURI
+ NS_DECL_NSISENSITIVEINFOHIDDENURI
+
+ // nsISizeOf
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ explicit nsStandardURL(bool aSupportsFileURL = false, bool aTrackURL = true);
+
+ static void InitGlobalObjects();
+ static void ShutdownGlobalObjects();
+
+public: /* internal -- HPUX compiler can't handle this being private */
+ //
+ // location and length of an url segment relative to mSpec
+ //
+ struct URLSegment
+ {
+ uint32_t mPos;
+ int32_t mLen;
+
+ URLSegment() : mPos(0), mLen(-1) {}
+ URLSegment(uint32_t pos, int32_t len) : mPos(pos), mLen(len) {}
+ URLSegment(const URLSegment& aCopy) : mPos(aCopy.mPos), mLen(aCopy.mLen) {}
+ void Reset() { mPos = 0; mLen = -1; }
+ // Merge another segment following this one to it if they're contiguous
+ // Assumes we have something like "foo;bar" where this object is 'foo' and right
+ // is 'bar'.
+ void Merge(const nsCString &spec, const char separator, const URLSegment &right) {
+ if (mLen >= 0 &&
+ *(spec.get() + mPos + mLen) == separator &&
+ mPos + mLen + 1 == right.mPos) {
+ mLen += 1 + right.mLen;
+ }
+ }
+ };
+
+ //
+ // Pref observer
+ //
+ class nsPrefObserver final : public nsIObserver
+ {
+ ~nsPrefObserver() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsPrefObserver() { }
+ };
+ friend class nsPrefObserver;
+
+ //
+ // URL segment encoder : performs charset conversion and URL escaping.
+ //
+ class nsSegmentEncoder
+ {
+ public:
+ explicit nsSegmentEncoder(const char *charset);
+
+ // Encode the given segment if necessary, and return the length of
+ // the encoded segment. The encoded segment is appended to |buf|
+ // if and only if encoding is required.
+ int32_t EncodeSegmentCount(const char *str,
+ const URLSegment &segment,
+ int16_t mask,
+ nsAFlatCString &buf,
+ bool& appended,
+ uint32_t extraLen = 0);
+
+ // Encode the given string if necessary, and return a reference to
+ // the encoded string. Returns a reference to |buf| if encoding
+ // is required. Otherwise, a reference to |str| is returned.
+ const nsACString &EncodeSegment(const nsASingleFragmentCString &str,
+ int16_t mask,
+ nsAFlatCString &buf);
+ private:
+ bool InitUnicodeEncoder();
+
+ const char* mCharset; // Caller should keep this alive for
+ // the life of the segment encoder
+ nsCOMPtr<nsIUnicodeEncoder> mEncoder;
+ };
+ friend class nsSegmentEncoder;
+
+protected:
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+
+ // Helper to share code between Equals and EqualsExceptRef
+ // NOTE: *not* virtual, because no one needs to override this so far...
+ nsresult EqualsInternal(nsIURI* unknownOther,
+ RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ virtual nsStandardURL* StartClone();
+
+ // Helper to share code between Clone methods.
+ nsresult CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI** aClone);
+ // Helper method that copies member variables from the source StandardURL
+ // if copyCached = true, it will also copy mFile and mHostA
+ nsresult CopyMembers(nsStandardURL * source, RefHandlingEnum mode,
+ const nsACString& newRef,
+ bool copyCached = false);
+
+ // Helper for subclass implementation of GetFile(). Subclasses that map
+ // URIs to files in a special way should implement this method. It should
+ // ensure that our mFile is initialized, if it's possible.
+ // returns NS_ERROR_NO_INTERFACE if the url does not map to a file
+ virtual nsresult EnsureFile();
+
+private:
+ int32_t Port() { return mPort == -1 ? mDefaultPort : mPort; }
+
+ void ReplacePortInSpec(int32_t aNewPort);
+ void Clear();
+ void InvalidateCache(bool invalidateCachedFile = true);
+
+ bool ValidIPv6orHostname(const char *host, uint32_t aLen);
+ static bool IsValidOfBase(unsigned char c, const uint32_t base);
+ static nsresult ParseIPv4Number(nsCString &input, uint32_t &number);
+ static nsresult NormalizeIPv4(const nsCSubstring &host, nsCString &result);
+ nsresult NormalizeIDN(const nsCSubstring &host, nsCString &result);
+ void CoalescePath(netCoalesceFlags coalesceFlag, char *path);
+
+ uint32_t AppendSegmentToBuf(char *, uint32_t, const char *,
+ const URLSegment &input, URLSegment &output,
+ const nsCString *esc=nullptr,
+ bool useEsc = false, int32_t* diff = nullptr);
+ uint32_t AppendToBuf(char *, uint32_t, const char *, uint32_t);
+
+ nsresult BuildNormalizedSpec(const char *spec);
+
+ bool SegmentIs(const URLSegment &s1, const char *val, bool ignoreCase = false);
+ bool SegmentIs(const char* spec, const URLSegment &s1, const char *val, bool ignoreCase = false);
+ bool SegmentIs(const URLSegment &s1, const char *val, const URLSegment &s2, bool ignoreCase = false);
+
+ int32_t ReplaceSegment(uint32_t pos, uint32_t len, const char *val, uint32_t valLen);
+ int32_t ReplaceSegment(uint32_t pos, uint32_t len, const nsACString &val);
+
+ nsresult ParseURL(const char *spec, int32_t specLen);
+ nsresult ParsePath(const char *spec, uint32_t pathPos, int32_t pathLen = -1);
+
+ char *AppendToSubstring(uint32_t pos, int32_t len, const char *tail);
+
+ // dependent substring helpers
+ const nsDependentCSubstring Segment(uint32_t pos, int32_t len); // see below
+ const nsDependentCSubstring Segment(const URLSegment &s) { return Segment(s.mPos, s.mLen); }
+
+ // dependent substring getters
+ const nsDependentCSubstring Prepath(); // see below
+ const nsDependentCSubstring Scheme() { return Segment(mScheme); }
+ const nsDependentCSubstring Userpass(bool includeDelim = false); // see below
+ const nsDependentCSubstring Username() { return Segment(mUsername); }
+ const nsDependentCSubstring Password() { return Segment(mPassword); }
+ const nsDependentCSubstring Hostport(); // see below
+ const nsDependentCSubstring Host(); // see below
+ const nsDependentCSubstring Path() { return Segment(mPath); }
+ const nsDependentCSubstring Filepath() { return Segment(mFilepath); }
+ const nsDependentCSubstring Directory() { return Segment(mDirectory); }
+ const nsDependentCSubstring Filename(); // see below
+ const nsDependentCSubstring Basename() { return Segment(mBasename); }
+ const nsDependentCSubstring Extension() { return Segment(mExtension); }
+ const nsDependentCSubstring Query() { return Segment(mQuery); }
+ const nsDependentCSubstring Ref() { return Segment(mRef); }
+
+ // shift the URLSegments to the right by diff
+ void ShiftFromAuthority(int32_t diff);
+ void ShiftFromUsername(int32_t diff);
+ void ShiftFromPassword(int32_t diff);
+ void ShiftFromHost(int32_t diff);
+ void ShiftFromPath(int32_t diff);
+ void ShiftFromFilepath(int32_t diff);
+ void ShiftFromDirectory(int32_t diff);
+ void ShiftFromBasename(int32_t diff);
+ void ShiftFromExtension(int32_t diff);
+ void ShiftFromQuery(int32_t diff);
+ void ShiftFromRef(int32_t diff);
+
+ // fastload helper functions
+ nsresult ReadSegment(nsIBinaryInputStream *, URLSegment &);
+ nsresult WriteSegment(nsIBinaryOutputStream *, const URLSegment &);
+
+ static void PrefsChanged(nsIPrefBranch *prefs, const char *pref);
+
+ void FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd);
+
+ // mSpec contains the normalized version of the URL spec (UTF-8 encoded).
+ nsCString mSpec;
+ int32_t mDefaultPort;
+ int32_t mPort;
+
+ // url parts (relative to mSpec)
+ URLSegment mScheme;
+ URLSegment mAuthority;
+ URLSegment mUsername;
+ URLSegment mPassword;
+ URLSegment mHost;
+ URLSegment mPath;
+ URLSegment mFilepath;
+ URLSegment mDirectory;
+ URLSegment mBasename;
+ URLSegment mExtension;
+ URLSegment mQuery;
+ URLSegment mRef;
+
+ nsCString mOriginCharset;
+ nsCOMPtr<nsIURLParser> mParser;
+
+ // mFile is protected so subclasses can access it directly
+protected:
+ nsCOMPtr<nsIFile> mFile; // cached result for nsIFileURL::GetFile
+
+private:
+ char *mHostA; // cached result for nsIURI::GetHostA
+
+ enum {
+ eEncoding_Unknown,
+ eEncoding_ASCII,
+ eEncoding_UTF8
+ };
+
+ uint32_t mHostEncoding : 2; // eEncoding_xxx
+ uint32_t mSpecEncoding : 2; // eEncoding_xxx
+ uint32_t mURLType : 2; // nsIStandardURL::URLTYPE_xxx
+ uint32_t mMutable : 1; // nsIStandardURL::mutable
+ uint32_t mSupportsFileURL : 1; // QI to nsIFileURL?
+
+ // global objects. don't use COMPtr as its destructor will cause a
+ // coredump if we leak it.
+ static nsIIDNService *gIDN;
+ static char gHostLimitDigits[];
+ static bool gInitialized;
+ static bool gEscapeUTF8;
+ static bool gAlwaysEncodeInUTF8;
+ static bool gEncodeQueryInUTF8;
+
+public:
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ PRCList mDebugCList;
+ void PrintSpec() const { printf(" %s\n", mSpec.get()); }
+#endif
+};
+
+#define NS_THIS_STANDARDURL_IMPL_CID \
+{ /* b8e3e97b-1ccd-4b45-af5a-79596770f5d7 */ \
+ 0xb8e3e97b, \
+ 0x1ccd, \
+ 0x4b45, \
+ {0xaf, 0x5a, 0x79, 0x59, 0x67, 0x70, 0xf5, 0xd7} \
+}
+
+//-----------------------------------------------------------------------------
+// Dependent substring getters
+//-----------------------------------------------------------------------------
+
+inline const nsDependentCSubstring
+nsStandardURL::Segment(uint32_t pos, int32_t len)
+{
+ if (len < 0) {
+ pos = 0;
+ len = 0;
+ }
+ return Substring(mSpec, pos, uint32_t(len));
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Prepath()
+{
+ uint32_t len = 0;
+ if (mAuthority.mLen >= 0)
+ len = mAuthority.mPos + mAuthority.mLen;
+ return Substring(mSpec, 0, len);
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Userpass(bool includeDelim)
+{
+ uint32_t pos=0, len=0;
+ // if there is no username, then there can be no password
+ if (mUsername.mLen > 0) {
+ pos = mUsername.mPos;
+ len = mUsername.mLen;
+ if (mPassword.mLen >= 0)
+ len += (mPassword.mLen + 1);
+ if (includeDelim)
+ len++;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Hostport()
+{
+ uint32_t pos=0, len=0;
+ if (mAuthority.mLen > 0) {
+ pos = mHost.mPos;
+ len = mAuthority.mPos + mAuthority.mLen - pos;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Host()
+{
+ uint32_t pos=0, len=0;
+ if (mHost.mLen > 0) {
+ pos = mHost.mPos;
+ len = mHost.mLen;
+ if (mSpec.CharAt(pos) == '[' && mSpec.CharAt(pos + len - 1) == ']') {
+ pos++;
+ len -= 2;
+ }
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Filename()
+{
+ uint32_t pos=0, len=0;
+ // if there is no basename, then there can be no extension
+ if (mBasename.mLen > 0) {
+ pos = mBasename.mPos;
+ len = mBasename.mLen;
+ if (mExtension.mLen >= 0)
+ len += (mExtension.mLen + 1);
+ }
+ return Substring(mSpec, pos, len);
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStandardURL_h__
diff --git a/netwerk/base/nsStreamListenerTee.cpp b/netwerk/base/nsStreamListenerTee.cpp
new file mode 100644
index 0000000000..d88370b2bf
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.cpp
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerTee.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerTee,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIStreamListenerTee,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnStartRequest(nsIRequest *request,
+ nsISupports *context)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ nsresult rv1 = mListener->OnStartRequest(request, context);
+ nsresult rv2 = NS_OK;
+ if (mObserver)
+ rv2 = mObserver->OnStartRequest(request, context);
+
+ // Preserve NS_SUCCESS_XXX in rv1 in case mObserver didn't throw
+ return (NS_FAILED(rv2) && NS_SUCCEEDED(rv1)) ? rv2 : rv1;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnStopRequest(nsIRequest *request,
+ nsISupports *context,
+ nsresult status)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ // it is critical that we close out the input stream tee
+ if (mInputTee) {
+ mInputTee->SetSink(nullptr);
+ mInputTee = nullptr;
+ }
+
+ // release sink on the same thread where the data was written (bug 716293)
+ if (mEventTarget) {
+ NS_ProxyRelease(mEventTarget, mSink.forget());
+ }
+ else {
+ mSink = nullptr;
+ }
+
+ nsresult rv = mListener->OnStopRequest(request, context, status);
+ if (mObserver)
+ mObserver->OnStopRequest(request, context, status);
+ mObserver = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnDataAvailable(nsIRequest *request,
+ nsISupports *context,
+ nsIInputStream *input,
+ uint64_t offset,
+ uint32_t count)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mSink, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIInputStream> tee;
+ nsresult rv;
+
+ if (!mInputTee) {
+ if (mEventTarget)
+ rv = NS_NewInputStreamTeeAsync(getter_AddRefs(tee), input,
+ mSink, mEventTarget);
+ else
+ rv = NS_NewInputStreamTee(getter_AddRefs(tee), input, mSink);
+ if (NS_FAILED(rv)) return rv;
+
+ mInputTee = do_QueryInterface(tee, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // re-initialize the input tee since the input stream may have changed.
+ rv = mInputTee->SetSource(input);
+ if (NS_FAILED(rv)) return rv;
+
+ tee = do_QueryInterface(mInputTee, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return mListener->OnDataAvailable(request, context, tee, offset, count);
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::CheckListenerChain()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!mObserver) {
+ return rv;
+ }
+ retargetableListener = do_QueryInterface(mObserver, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::Init(nsIStreamListener *listener,
+ nsIOutputStream *sink,
+ nsIRequestObserver *requestObserver)
+{
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_ARG_POINTER(sink);
+ mListener = listener;
+ mSink = sink;
+ mObserver = requestObserver;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::InitAsync(nsIStreamListener *listener,
+ nsIEventTarget *eventTarget,
+ nsIOutputStream *sink,
+ nsIRequestObserver *requestObserver)
+{
+ NS_ENSURE_ARG_POINTER(eventTarget);
+ mEventTarget = eventTarget;
+ return Init(listener, sink, requestObserver);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerTee.h b/netwerk/base/nsStreamListenerTee.h
new file mode 100644
index 0000000000..d6a6f23a60
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerTee_h__
+#define nsStreamListenerTee_h__
+
+#include "nsIStreamListenerTee.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIInputStreamTee.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "nsIEventTarget.h"
+
+namespace mozilla {
+namespace net {
+
+class nsStreamListenerTee : public nsIStreamListenerTee
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSISTREAMLISTENERTEE
+
+ nsStreamListenerTee() { }
+
+private:
+ virtual ~nsStreamListenerTee() { }
+
+ nsCOMPtr<nsIInputStreamTee> mInputTee;
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsStreamListenerWrapper.cpp b/netwerk/base/nsStreamListenerWrapper.cpp
new file mode 100644
index 0000000000..9273e4558d
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.cpp
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerWrapper.h"
+#ifdef DEBUG
+#include "MainThreadUtils.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerWrapper,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamListenerWrapper::CheckListenerChain()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerWrapper.h b/netwerk/base/nsStreamListenerWrapper.h
new file mode 100644
index 0000000000..36bb93daca
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerWrapper_h__
+#define nsStreamListenerWrapper_h__
+
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsIRequestObserver.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace net {
+
+// Wrapper class to make replacement of nsHttpChannel's listener
+// from JavaScript possible. It is workaround for bug 433711 and 682305.
+class nsStreamListenerWrapper final : public nsIStreamListener
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ explicit nsStreamListenerWrapper(nsIStreamListener *listener)
+ : mListener(listener)
+ {
+ MOZ_ASSERT(mListener, "no stream listener specified");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_SAFE_NSIREQUESTOBSERVER(mListener)
+ NS_FORWARD_SAFE_NSISTREAMLISTENER(mListener)
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+private:
+ ~nsStreamListenerWrapper() {}
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStreamListenerWrapper_h__
+
diff --git a/netwerk/base/nsStreamLoader.cpp b/netwerk/base/nsStreamLoader.cpp
new file mode 100644
index 0000000000..a73b038a7e
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamLoader.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsError.h"
+#include "GeckoProfiler.h"
+
+#include <limits>
+
+namespace mozilla {
+namespace net {
+
+nsStreamLoader::nsStreamLoader()
+ : mData()
+{
+}
+
+nsStreamLoader::~nsStreamLoader()
+{
+}
+
+NS_IMETHODIMP
+nsStreamLoader::Init(nsIStreamLoaderObserver* aStreamObserver,
+ nsIRequestObserver* aRequestObserver)
+{
+ NS_ENSURE_ARG_POINTER(aStreamObserver);
+ mObserver = aStreamObserver;
+ mRequestObserver = aRequestObserver;
+ return NS_OK;
+}
+
+nsresult
+nsStreamLoader::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ nsStreamLoader* it = new nsStreamLoader();
+ if (it == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsStreamLoader, nsIStreamLoader,
+ nsIRequestObserver, nsIStreamListener,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamLoader::GetNumBytesRead(uint32_t* aNumBytes)
+{
+ *aNumBytes = mData.length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::GetRequest(nsIRequest **aRequest)
+{
+ NS_IF_ADDREF(*aRequest = mRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
+{
+ nsCOMPtr<nsIChannel> chan( do_QueryInterface(request) );
+ if (chan) {
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+ if (contentLength >= 0) {
+ if (uint64_t(contentLength) > std::numeric_limits<size_t>::max()) {
+ // Too big to fit into size_t, so let's bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // preallocate buffer
+ if (!mData.initCapacity(contentLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ mContext = ctxt;
+ if (mRequestObserver) {
+ mRequestObserver->OnStartRequest(request, ctxt);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnStopRequest(nsIRequest* request, nsISupports *ctxt,
+ nsresult aStatus)
+{
+ PROFILER_LABEL("nsStreamLoader", "OnStopRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ if (mObserver) {
+ // provide nsIStreamLoader::request during call to OnStreamComplete
+ mRequest = request;
+ size_t length = mData.length();
+ uint8_t* elems = mData.extractOrCopyRawBuffer();
+ nsresult rv = mObserver->OnStreamComplete(this, mContext, aStatus,
+ length, elems);
+ if (rv != NS_SUCCESS_ADOPTED_DATA) {
+ // The observer didn't take ownership of the extracted data buffer, so
+ // put it back into mData.
+ mData.replaceRawBuffer(elems, length);
+ }
+ // done.. cleanup
+ ReleaseData();
+ mRequest = nullptr;
+ mObserver = nullptr;
+ mContext = nullptr;
+ }
+
+ if (mRequestObserver) {
+ mRequestObserver->OnStopRequest(request, ctxt, aStatus);
+ mRequestObserver = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsStreamLoader::WriteSegmentFun(nsIInputStream *inStr,
+ void *closure,
+ const char *fromSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ nsStreamLoader *self = (nsStreamLoader *) closure;
+
+ if (!self->mData.append(fromSegment, count)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *writeCount = count;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnDataAvailable(nsIRequest* request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ uint32_t countRead;
+ return inStr->ReadSegments(WriteSegmentFun, this, count, &countRead);
+}
+
+void
+nsStreamLoader::ReleaseData()
+{
+ mData.clearAndFree();
+}
+
+NS_IMETHODIMP
+nsStreamLoader::CheckListenerChain()
+{
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamLoader.h b/netwerk/base/nsStreamLoader.h
new file mode 100644
index 0000000000..671fc441ff
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamLoader_h__
+#define nsStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamLoader final : public nsIStreamLoader
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsStreamLoader();
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ ~nsStreamLoader();
+
+ static nsresult WriteSegmentFun(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ // Utility method to free mData, if present, and update other state to
+ // reflect that no data has been allocated.
+ void ReleaseData();
+
+ nsCOMPtr<nsIStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsISupports> mContext; // the observer's context
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIRequestObserver> mRequestObserver;
+
+ // Buffer to accumulate incoming data. We preallocate if contentSize is
+ // available.
+ mozilla::Vector<uint8_t, 0> mData;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStreamLoader_h__
diff --git a/netwerk/base/nsStreamTransportService.cpp b/netwerk/base/nsStreamTransportService.cpp
new file mode 100644
index 0000000000..3461480b6c
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.cpp
@@ -0,0 +1,563 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamTransportService.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsNetSegmentUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIPipe.h"
+#include "nsITransport.h"
+#include "nsIObserverService.h"
+#include "nsIThreadPool.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsInputStreamTransport
+//
+// Implements nsIInputStream as a wrapper around the real input stream. This
+// allows the transport to support seeking, range-limiting, progress reporting,
+// and close-when-done semantics while utilizing NS_AsyncCopy.
+//-----------------------------------------------------------------------------
+
+class nsInputStreamTransport : public nsITransport
+ , public nsIInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSIINPUTSTREAM
+
+ nsInputStreamTransport(nsIInputStream *source,
+ uint64_t offset,
+ uint64_t limit,
+ bool closeWhenDone)
+ : mSource(source)
+ , mOffset(offset)
+ , mLimit(limit)
+ , mCloseWhenDone(closeWhenDone)
+ , mFirstTime(true)
+ , mInProgress(false)
+ {
+ }
+
+private:
+ virtual ~nsInputStreamTransport()
+ {
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> mPipeIn;
+
+ // while the copy is active, these members may only be accessed from the
+ // nsIInputStream implementation.
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsIInputStream> mSource;
+ int64_t mOffset;
+ int64_t mLimit;
+ bool mCloseWhenDone;
+ bool mFirstTime;
+
+ // this variable serves as a lock to prevent the state of the transport
+ // from being modified once the copy is in progress.
+ bool mInProgress;
+};
+
+NS_IMPL_ISUPPORTS(nsInputStreamTransport,
+ nsITransport,
+ nsIInputStream)
+
+/** nsITransport **/
+
+NS_IMETHODIMP
+nsInputStreamTransport::OpenInputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream **result)
+{
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX if the caller requests an unbuffered stream, then perhaps
+ // we'd want to simply return mSource; however, then we would
+ // not be reading mSource on a background thread. is this ok?
+
+ bool nonblocking = !(flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ rv = NS_NewPipe2(getter_AddRefs(mPipeIn),
+ getter_AddRefs(pipeOut),
+ nonblocking, true,
+ segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ mInProgress = true;
+
+ // startup async copy process...
+ rv = NS_AsyncCopy(this, pipeOut, target,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
+ if (NS_SUCCEEDED(rv))
+ NS_ADDREF(*result = mPipeIn);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::OpenOutputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream **result)
+{
+ // this transport only supports reading!
+ NS_NOTREACHED("nsInputStreamTransport::OpenOutputStream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Close(nsresult reason)
+{
+ if (NS_SUCCEEDED(reason))
+ reason = NS_BASE_STREAM_CLOSED;
+
+ return mPipeIn->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::SetEventSink(nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ if (target)
+ return net_NewTransportEventSinkProxy(getter_AddRefs(mEventSink),
+ sink, target);
+
+ mEventSink = sink;
+ return NS_OK;
+}
+
+/** nsIInputStream **/
+
+NS_IMETHODIMP
+nsInputStreamTransport::Close()
+{
+ if (mCloseWhenDone)
+ mSource->Close();
+
+ // make additional reads return early...
+ mOffset = mLimit = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Available(uint64_t *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Read(char *buf, uint32_t count, uint32_t *result)
+{
+ if (mFirstTime) {
+ mFirstTime = false;
+ if (mOffset != 0) {
+ // read from current position if offset equal to max
+ if (mOffset != -1) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mSource);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
+ }
+ // reset offset to zero so we can use it to enforce limit
+ mOffset = 0;
+ }
+ }
+
+ // limit amount read
+ uint64_t max = count;
+ if (mLimit != -1) {
+ max = mLimit - mOffset;
+ if (max == 0) {
+ *result = 0;
+ return NS_OK;
+ }
+ }
+
+ if (count > max)
+ count = static_cast<uint32_t>(max);
+
+ nsresult rv = mSource->Read(buf, count, result);
+
+ if (NS_SUCCEEDED(rv)) {
+ mOffset += *result;
+ if (mEventSink)
+ mEventSink->OnTransportStatus(this, NS_NET_STATUS_READING, mOffset,
+ mLimit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::IsNonBlocking(bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOutputStreamTransport
+//
+// Implements nsIOutputStream as a wrapper around the real input stream. This
+// allows the transport to support seeking, range-limiting, progress reporting,
+// and close-when-done semantics while utilizing NS_AsyncCopy.
+//-----------------------------------------------------------------------------
+
+class nsOutputStreamTransport : public nsITransport
+ , public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsOutputStreamTransport(nsIOutputStream *sink,
+ int64_t offset,
+ int64_t limit,
+ bool closeWhenDone)
+ : mSink(sink)
+ , mOffset(offset)
+ , mLimit(limit)
+ , mCloseWhenDone(closeWhenDone)
+ , mFirstTime(true)
+ , mInProgress(false)
+ {
+ }
+
+private:
+ virtual ~nsOutputStreamTransport()
+ {
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+
+ // while the copy is active, these members may only be accessed from the
+ // nsIOutputStream implementation.
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsIOutputStream> mSink;
+ int64_t mOffset;
+ int64_t mLimit;
+ bool mCloseWhenDone;
+ bool mFirstTime;
+
+ // this variable serves as a lock to prevent the state of the transport
+ // from being modified once the copy is in progress.
+ bool mInProgress;
+};
+
+NS_IMPL_ISUPPORTS(nsOutputStreamTransport,
+ nsITransport,
+ nsIOutputStream)
+
+/** nsITransport **/
+
+NS_IMETHODIMP
+nsOutputStreamTransport::OpenInputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream **result)
+{
+ // this transport only supports writing!
+ NS_NOTREACHED("nsOutputStreamTransport::OpenInputStream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::OpenOutputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream **result)
+{
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX if the caller requests an unbuffered stream, then perhaps
+ // we'd want to simply return mSink; however, then we would
+ // not be writing to mSink on a background thread. is this ok?
+
+ bool nonblocking = !(flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ rv = NS_NewPipe2(getter_AddRefs(pipeIn),
+ getter_AddRefs(mPipeOut),
+ true, nonblocking,
+ segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ mInProgress = true;
+
+ // startup async copy process...
+ rv = NS_AsyncCopy(pipeIn, this, target,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
+ if (NS_SUCCEEDED(rv))
+ NS_ADDREF(*result = mPipeOut);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::Close(nsresult reason)
+{
+ if (NS_SUCCEEDED(reason))
+ reason = NS_BASE_STREAM_CLOSED;
+
+ return mPipeOut->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::SetEventSink(nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ if (target)
+ return net_NewTransportEventSinkProxy(getter_AddRefs(mEventSink),
+ sink, target);
+
+ mEventSink = sink;
+ return NS_OK;
+}
+
+/** nsIOutputStream **/
+
+NS_IMETHODIMP
+nsOutputStreamTransport::Close()
+{
+ if (mCloseWhenDone)
+ mSink->Close();
+
+ // make additional writes return early...
+ mOffset = mLimit = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ if (mFirstTime) {
+ mFirstTime = false;
+ if (mOffset != 0) {
+ // write to current position if offset equal to max
+ if (mOffset != -1) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mSink);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
+ }
+ // reset offset to zero so we can use it to enforce limit
+ mOffset = 0;
+ }
+ }
+
+ // limit amount written
+ uint64_t max = count;
+ if (mLimit != -1) {
+ max = mLimit - mOffset;
+ if (max == 0) {
+ *result = 0;
+ return NS_OK;
+ }
+ }
+
+ if (count > max)
+ count = static_cast<uint32_t>(max);
+
+ nsresult rv = mSink->Write(buf, count, result);
+
+ if (NS_SUCCEEDED(rv)) {
+ mOffset += *result;
+ if (mEventSink)
+ mEventSink->OnTransportStatus(this, NS_NET_STATUS_WRITING, mOffset,
+ mLimit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::WriteSegments(nsReadSegmentFun reader, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::WriteFrom(nsIInputStream *in, uint32_t count, uint32_t *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::IsNonBlocking(bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsStreamTransportService
+//-----------------------------------------------------------------------------
+
+nsStreamTransportService::~nsStreamTransportService()
+{
+ NS_ASSERTION(!mPool, "thread pool wasn't shutdown");
+}
+
+nsresult
+nsStreamTransportService::Init()
+{
+ mPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
+ NS_ENSURE_STATE(mPool);
+
+ // Configure the pool
+ mPool->SetName(NS_LITERAL_CSTRING("StreamTrans"));
+ mPool->SetThreadLimit(25);
+ mPool->SetIdleThreadLimit(1);
+ mPool->SetIdleThreadTimeout(PR_SecondsToInterval(30));
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc)
+ obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsStreamTransportService,
+ nsIStreamTransportService,
+ nsIEventTarget,
+ nsIObserver)
+
+NS_IMETHODIMP
+nsStreamTransportService::DispatchFromScript(nsIRunnable *task, uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> event(task);
+ return Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Dispatch(already_AddRefed<nsIRunnable> task, uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> event(task); // so it gets released on failure paths
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::IsOnCurrentThread(bool *result)
+{
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->IsOnCurrentThread(result);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::CreateInputTransport(nsIInputStream *stream,
+ int64_t offset,
+ int64_t limit,
+ bool closeWhenDone,
+ nsITransport **result)
+{
+ nsInputStreamTransport *trans =
+ new nsInputStreamTransport(stream, offset, limit, closeWhenDone);
+ if (!trans)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = trans);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::CreateOutputTransport(nsIOutputStream *stream,
+ int64_t offset,
+ int64_t limit,
+ bool closeWhenDone,
+ nsITransport **result)
+{
+ nsOutputStreamTransport *trans =
+ new nsOutputStreamTransport(stream, offset, limit, closeWhenDone);
+ if (!trans)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = trans);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data)
+{
+ NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops");
+
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ mIsShutdown = true;
+ }
+
+ if (mPool) {
+ mPool->Shutdown();
+ mPool = nullptr;
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamTransportService.h b/netwerk/base/nsStreamTransportService.h
new file mode 100644
index 0000000000..7ffd1d1b9e
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamTransportService_h__
+#define nsStreamTransportService_h__
+
+#include "nsIStreamTransportService.h"
+#include "nsIEventTarget.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+class nsIThreadPool;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamTransportService final : public nsIStreamTransportService
+ , public nsIEventTarget
+ , public nsIObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET
+ NS_DECL_NSIOBSERVER
+ using nsIEventTarget::Dispatch;
+
+ nsresult Init();
+
+ nsStreamTransportService() : mShutdownLock("nsStreamTransportService.mShutdownLock"),
+ mIsShutdown(false) {}
+
+private:
+ ~nsStreamTransportService();
+
+ nsCOMPtr<nsIThreadPool> mPool;
+
+ mozilla::Mutex mShutdownLock;
+ bool mIsShutdown;
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsSyncStreamListener.cpp b/netwerk/base/nsSyncStreamListener.cpp
new file mode 100644
index 0000000000..e80e885c51
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.cpp
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsSyncStreamListener.h"
+#include "nsIPipe.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+nsresult
+nsSyncStreamListener::Init()
+{
+ return NS_NewPipe(getter_AddRefs(mPipeIn),
+ getter_AddRefs(mPipeOut),
+ nsIOService::gDefaultSegmentSize,
+ UINT32_MAX, // no size limit
+ false,
+ false);
+}
+
+nsresult
+nsSyncStreamListener::WaitForData()
+{
+ mKeepWaiting = true;
+
+ while (mKeepWaiting)
+ NS_ENSURE_STATE(NS_ProcessNextEvent(NS_GetCurrentThread()));
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsSyncStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIInputStream,
+ nsISyncStreamListener)
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsISyncStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::GetInputStream(nsIInputStream **result)
+{
+ NS_ADDREF(*result = this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnStartRequest(nsIRequest *request,
+ nsISupports *context)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnDataAvailable(nsIRequest *request,
+ nsISupports *context,
+ nsIInputStream *stream,
+ uint64_t offset,
+ uint32_t count)
+{
+ uint32_t bytesWritten;
+
+ nsresult rv = mPipeOut->WriteFrom(stream, count, &bytesWritten);
+
+ // if we get an error, then return failure. this will cause the
+ // channel to be canceled, and as a result our OnStopRequest method
+ // will be called immediately. because of this we do not need to
+ // set mStatus or mKeepWaiting here.
+ if (NS_FAILED(rv))
+ return rv;
+
+ // we expect that all data will be written to the pipe because
+ // the pipe was created to have "infinite" room.
+ NS_ASSERTION(bytesWritten == count, "did not write all data");
+
+ mKeepWaiting = false; // unblock Read
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnStopRequest(nsIRequest *request,
+ nsISupports *context,
+ nsresult status)
+{
+ mStatus = status;
+ mKeepWaiting = false; // unblock Read
+ mDone = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsIInputStream
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::Close()
+{
+ mStatus = NS_BASE_STREAM_CLOSED;
+ mDone = true;
+
+ // It'd be nice if we could explicitly cancel the request at this point,
+ // but we don't have a reference to it, so the best we can do is close the
+ // pipe so that the next OnDataAvailable event will fail.
+ if (mPipeIn) {
+ mPipeIn->Close();
+ mPipeIn = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::Available(uint64_t *result)
+{
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ mStatus = mPipeIn->Available(result);
+ if (NS_SUCCEEDED(mStatus) && (*result == 0) && !mDone) {
+ mStatus = WaitForData();
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = mPipeIn->Available(result);
+ }
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::Read(char *buf,
+ uint32_t bufLen,
+ uint32_t *result)
+{
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ uint64_t avail64;
+ if (NS_FAILED(Available(&avail64)))
+ return mStatus;
+
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)bufLen);
+ mStatus = mPipeIn->Read(buf, avail, result);
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::ReadSegments(nsWriteSegmentFun writer,
+ void *closure,
+ uint32_t count,
+ uint32_t *result)
+{
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ uint64_t avail64;
+ if (NS_FAILED(Available(&avail64)))
+ return mStatus;
+
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)count);
+ mStatus = mPipeIn->ReadSegments(writer, closure, avail, result);
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::IsNonBlocking(bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsSyncStreamListener.h b/netwerk/base/nsSyncStreamListener.h
new file mode 100644
index 0000000000..9fa9c58c3e
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSyncStreamListener_h__
+#define nsSyncStreamListener_h__
+
+#include "nsISyncStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+//-----------------------------------------------------------------------------
+
+class nsSyncStreamListener final : public nsISyncStreamListener
+ , public nsIInputStream
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISYNCSTREAMLISTENER
+ NS_DECL_NSIINPUTSTREAM
+
+ nsSyncStreamListener()
+ : mStatus(NS_OK)
+ , mKeepWaiting(false)
+ , mDone(false) {}
+
+ nsresult Init();
+
+private:
+ ~nsSyncStreamListener() {}
+
+ nsresult WaitForData();
+
+ nsCOMPtr<nsIInputStream> mPipeIn;
+ nsCOMPtr<nsIOutputStream> mPipeOut;
+ nsresult mStatus;
+ bool mKeepWaiting;
+ bool mDone;
+};
+
+#endif // nsSyncStreamListener_h__
diff --git a/netwerk/base/nsTemporaryFileInputStream.cpp b/netwerk/base/nsTemporaryFileInputStream.cpp
new file mode 100644
index 0000000000..c7c5b06486
--- /dev/null
+++ b/netwerk/base/nsTemporaryFileInputStream.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTemporaryFileInputStream.h"
+#include "nsStreamUtils.h"
+#include <algorithm>
+
+typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType;
+
+NS_IMPL_ISUPPORTS(nsTemporaryFileInputStream,
+ nsIInputStream,
+ nsISeekableStream,
+ nsIIPCSerializableInputStream)
+
+nsTemporaryFileInputStream::nsTemporaryFileInputStream(FileDescOwner* aFileDescOwner, uint64_t aStartPos, uint64_t aEndPos)
+ : mFileDescOwner(aFileDescOwner),
+ mStartPos(aStartPos),
+ mCurPos(aStartPos),
+ mEndPos(aEndPos),
+ mClosed(false)
+{
+ NS_ASSERTION(aStartPos <= aEndPos, "StartPos should less equal than EndPos!");
+}
+
+nsTemporaryFileInputStream::nsTemporaryFileInputStream()
+ : mStartPos(0),
+ mCurPos(0),
+ mEndPos(0),
+ mClosed(false)
+{
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Close()
+{
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Available(uint64_t * bytesAvailable)
+{
+ if (mClosed)
+ return NS_BASE_STREAM_CLOSED;
+
+ NS_ASSERTION(mCurPos <= mEndPos, "CurPos should less equal than EndPos!");
+
+ *bytesAvailable = mEndPos - mCurPos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Read(char* buffer, uint32_t count, uint32_t* bytesRead)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, buffer, count, bytesRead);
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::ReadSegments(nsWriteSegmentFun writer,
+ void * closure,
+ uint32_t count,
+ uint32_t * result)
+{
+ NS_ASSERTION(result, "null ptr");
+ NS_ASSERTION(mCurPos <= mEndPos, "bad stream state");
+ *result = 0;
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ mozilla::MutexAutoLock lock(mFileDescOwner->FileMutex());
+ int64_t offset = PR_Seek64(mFileDescOwner->mFD, mCurPos, PR_SEEK_SET);
+ if (offset == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ // Limit requested count to the amount remaining in our section of the file.
+ count = std::min(count, uint32_t(mEndPos - mCurPos));
+
+ char buf[4096];
+ while (*result < count) {
+ uint32_t bufCount = std::min(count - *result, (uint32_t) sizeof(buf));
+ int32_t bytesRead = PR_Read(mFileDescOwner->mFD, buf, bufCount);
+ if (bytesRead == 0) {
+ mClosed = true;
+ return NS_OK;
+ }
+
+ if (bytesRead < 0) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ int32_t bytesWritten = 0;
+ while (bytesWritten < bytesRead) {
+ uint32_t writerCount = 0;
+ nsresult rv = writer(this, closure, buf + bytesWritten, *result,
+ bytesRead - bytesWritten, &writerCount);
+ if (NS_FAILED(rv) || writerCount == 0) {
+ // nsIInputStream::ReadSegments' contract specifies that errors
+ // from writer are not propagated to ReadSegments' caller.
+ //
+ // If writer fails, leaving bytes still in buf, that's okay: we
+ // only update mCurPos to reflect successful writes, so the call
+ // to PR_Seek64 at the top will restart us at the right spot.
+ return NS_OK;
+ }
+ NS_ASSERTION(writerCount <= (uint32_t) (bytesRead - bytesWritten),
+ "writer should not write more than we asked it to write");
+ bytesWritten += writerCount;
+ *result += writerCount;
+ mCurPos += writerCount;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::IsNonBlocking(bool * nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ switch (aWhence) {
+ case nsISeekableStream::NS_SEEK_SET:
+ aOffset += mStartPos;
+ break;
+
+ case nsISeekableStream::NS_SEEK_CUR:
+ aOffset += mCurPos;
+ break;
+
+ case nsISeekableStream::NS_SEEK_END:
+ aOffset += mEndPos;
+ break;
+
+ default:
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aOffset < (int64_t)mStartPos || aOffset > (int64_t)mEndPos) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mCurPos = aOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Tell(int64_t* aPos)
+{
+ if (!aPos) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mStartPos <= mCurPos, "StartPos should less equal than CurPos!");
+ *aPos = mCurPos - mStartPos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::SetEOF()
+{
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ return Close();
+}
+
+void
+nsTemporaryFileInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ TemporaryFileInputStreamParams params;
+
+ MutexAutoLock lock(mFileDescOwner->FileMutex());
+ MOZ_ASSERT(mFileDescOwner->mFD);
+ if (!mClosed) {
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFileDescOwner->mFD));
+ NS_ASSERTION(fd, "This should never be null!");
+
+ DebugOnly<FileDescriptor*> dbgFD = aFileDescriptors.AppendElement(fd);
+ NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!");
+
+ params.fileDescriptorIndex() = aFileDescriptors.Length() - 1;
+
+ Close();
+ } else {
+ NS_WARNING("The stream is already closed. "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptorIndex() = UINT32_MAX;
+ }
+ params.startPos() = mCurPos;
+ params.endPos() = mEndPos;
+ aParams = params;
+}
+
+bool
+nsTemporaryFileInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ const TemporaryFileInputStreamParams& params = aParams.get_TemporaryFileInputStreamParams();
+
+ uint32_t fileDescriptorIndex = params.fileDescriptorIndex();
+ FileDescriptor fd;
+ if (fileDescriptorIndex < aFileDescriptors.Length()) {
+ fd = aFileDescriptors[fileDescriptorIndex];
+ NS_WARNING_ASSERTION(fd.IsValid(),
+ "Received an invalid file descriptor!");
+ } else {
+ NS_WARNING("Received a bad file descriptor index!");
+ }
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFileDescOwner = new FileDescOwner(fileDesc);
+ } else {
+ mClosed = true;
+ }
+
+ mStartPos = mCurPos = params.startPos();
+ mEndPos = params.endPos();
+ return true;
+}
+
+Maybe<uint64_t>
+nsTemporaryFileInputStream::ExpectedSerializedLength()
+{
+ return Nothing();
+}
diff --git a/netwerk/base/nsTemporaryFileInputStream.h b/netwerk/base/nsTemporaryFileInputStream.h
new file mode 100644
index 0000000000..950b26c29a
--- /dev/null
+++ b/netwerk/base/nsTemporaryFileInputStream.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTemporaryFileInputStream_h__
+#define nsTemporaryFileInputStream_h__
+
+#include "mozilla/Mutex.h"
+#include "nsAutoPtr.h"
+#include "nsIInputStream.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsISeekableStream.h"
+#include "prio.h"
+
+class nsTemporaryFileInputStream : public nsIInputStream
+ , public nsISeekableStream
+ , public nsIIPCSerializableInputStream
+{
+public:
+ //used to release a PRFileDesc
+ class FileDescOwner
+ {
+ friend class nsTemporaryFileInputStream;
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileDescOwner)
+ explicit FileDescOwner(PRFileDesc* aFD)
+ : mFD(aFD),
+ mMutex("FileDescOwner::mMutex")
+ {
+ MOZ_ASSERT(aFD);
+ }
+ private:
+ ~FileDescOwner()
+ {
+ PR_Close(mFD);
+ }
+ public:
+ mozilla::Mutex& FileMutex() { return mMutex; }
+
+ private:
+ PRFileDesc* mFD;
+ mozilla::Mutex mMutex;
+ };
+
+ nsTemporaryFileInputStream(FileDescOwner* aFileDescOwner, uint64_t aStartPos, uint64_t aEndPos);
+ nsTemporaryFileInputStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+private:
+ virtual ~nsTemporaryFileInputStream() { }
+
+ RefPtr<FileDescOwner> mFileDescOwner;
+ uint64_t mStartPos;
+ uint64_t mCurPos;
+ uint64_t mEndPos;
+ bool mClosed;
+};
+
+#endif // nsTemporaryFileInputStream_h__
diff --git a/netwerk/base/nsTransportUtils.cpp b/netwerk/base/nsTransportUtils.cpp
new file mode 100644
index 0000000000..e29bbfdab5
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.cpp
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Mutex.h"
+#include "nsTransportUtils.h"
+#include "nsITransport.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+
+using namespace mozilla;
+
+//-----------------------------------------------------------------------------
+
+class nsTransportStatusEvent;
+
+class nsTransportEventSinkProxy : public nsITransportEventSink
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ nsTransportEventSinkProxy(nsITransportEventSink *sink,
+ nsIEventTarget *target)
+ : mSink(sink)
+ , mTarget(target)
+ , mLock("nsTransportEventSinkProxy.mLock")
+ , mLastEvent(nullptr)
+ {
+ NS_ADDREF(mSink);
+ }
+
+private:
+ virtual ~nsTransportEventSinkProxy()
+ {
+ // our reference to mSink could be the last, so be sure to release
+ // it on the target thread. otherwise, we could get into trouble.
+ NS_ProxyRelease(mTarget, dont_AddRef(mSink));
+ }
+
+public:
+ nsITransportEventSink *mSink;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Mutex mLock;
+ nsTransportStatusEvent *mLastEvent;
+};
+
+class nsTransportStatusEvent : public Runnable
+{
+public:
+ nsTransportStatusEvent(nsTransportEventSinkProxy *proxy,
+ nsITransport *transport,
+ nsresult status,
+ int64_t progress,
+ int64_t progressMax)
+ : mProxy(proxy)
+ , mTransport(transport)
+ , mStatus(status)
+ , mProgress(progress)
+ , mProgressMax(progressMax)
+ {}
+
+ ~nsTransportStatusEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ // since this event is being handled, we need to clear the proxy's ref.
+ // if not coalescing all, then last event may not equal self!
+ {
+ MutexAutoLock lock(mProxy->mLock);
+ if (mProxy->mLastEvent == this)
+ mProxy->mLastEvent = nullptr;
+ }
+
+ mProxy->mSink->OnTransportStatus(mTransport, mStatus, mProgress,
+ mProgressMax);
+ return NS_OK;
+ }
+
+ RefPtr<nsTransportEventSinkProxy> mProxy;
+
+ // parameters to OnTransportStatus
+ nsCOMPtr<nsITransport> mTransport;
+ nsresult mStatus;
+ int64_t mProgress;
+ int64_t mProgressMax;
+};
+
+NS_IMPL_ISUPPORTS(nsTransportEventSinkProxy, nsITransportEventSink)
+
+NS_IMETHODIMP
+nsTransportEventSinkProxy::OnTransportStatus(nsITransport *transport,
+ nsresult status,
+ int64_t progress,
+ int64_t progressMax)
+{
+ nsresult rv = NS_OK;
+ RefPtr<nsTransportStatusEvent> event;
+ {
+ MutexAutoLock lock(mLock);
+
+ // try to coalesce events! ;-)
+ if (mLastEvent && (mLastEvent->mStatus == status)) {
+ mLastEvent->mStatus = status;
+ mLastEvent->mProgress = progress;
+ mLastEvent->mProgressMax = progressMax;
+ }
+ else {
+ event = new nsTransportStatusEvent(this, transport, status,
+ progress, progressMax);
+ if (!event)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ mLastEvent = event; // weak ref
+ }
+ }
+ if (event) {
+ rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post transport status event");
+
+ MutexAutoLock lock(mLock); // cleanup.. don't reference anymore!
+ mLastEvent = nullptr;
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+net_NewTransportEventSinkProxy(nsITransportEventSink **result,
+ nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ *result = new nsTransportEventSinkProxy(sink, target);
+ if (!*result)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsTransportUtils.h b/netwerk/base/nsTransportUtils.h
new file mode 100644
index 0000000000..4256bcad92
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.h
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTransportUtils_h__
+#define nsTransportUtils_h__
+
+#include "nsITransport.h"
+
+/**
+ * This function returns a proxy object for a transport event sink instance.
+ * The transport event sink will be called on the thread indicated by the
+ * given event target. Like events are automatically coalesced. This means
+ * that for example if the status value is the same from event to event, and
+ * the previous event has not yet been delivered, then only one event will
+ * be delivered. The progress reported will be that from the second event.
+
+ * Coalescing events can help prevent a backlog of unprocessed transport
+ * events in the case that the target thread is overworked.
+ */
+nsresult
+net_NewTransportEventSinkProxy(nsITransportEventSink **aResult,
+ nsITransportEventSink *aSink,
+ nsIEventTarget *aTarget);
+
+#endif // nsTransportUtils_h__
diff --git a/netwerk/base/nsUDPSocket.cpp b/netwerk/base/nsUDPSocket.cpp
new file mode 100644
index 0000000000..84f6b8ea56
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -0,0 +1,1613 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Telemetry.h"
+
+#include "nsSocketTransport2.h"
+#include "nsUDPSocket.h"
+#include "nsProxyRelease.h"
+#include "nsAutoPtr.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIOService.h"
+#include "prnetdb.h"
+#include "prio.h"
+#include "nsNetAddr.h"
+#include "nsNetSegmentUtils.h"
+#include "NetworkActivityMonitor.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsIPipe.h"
+#include "prerror.h"
+#include "nsThreadUtils.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "NetStatistics.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+static const uint32_t UDP_PACKET_CHUNK_SIZE = 1400;
+static NS_DEFINE_CID(kSocketTransportServiceCID2, NS_SOCKETTRANSPORTSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+typedef void (nsUDPSocket:: *nsUDPSocketFunc)(void);
+
+static nsresult
+PostEvent(nsUDPSocket *s, nsUDPSocketFunc func)
+{
+ if (!gSocketTransportService)
+ return NS_ERROR_FAILURE;
+
+ return gSocketTransportService->Dispatch(NewRunnableMethod(s, func), NS_DISPATCH_NORMAL);
+}
+
+static nsresult
+ResolveHost(const nsACString &host, nsIDNSListener *listener)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+ return dns->AsyncResolve(host, 0, listener, nullptr,
+ getter_AddRefs(tmpOutstanding));
+
+}
+
+//-----------------------------------------------------------------------------
+
+class SetSocketOptionRunnable : public Runnable
+{
+public:
+ SetSocketOptionRunnable(nsUDPSocket* aSocket, const PRSocketOptionData& aOpt)
+ : mSocket(aSocket)
+ , mOpt(aOpt)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ return mSocket->SetSocketOption(mOpt);
+ }
+
+private:
+ RefPtr<nsUDPSocket> mSocket;
+ PRSocketOptionData mOpt;
+};
+
+//-----------------------------------------------------------------------------
+// nsUDPOutputStream impl
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(nsUDPOutputStream, nsIOutputStream)
+
+nsUDPOutputStream::nsUDPOutputStream(nsUDPSocket* aSocket,
+ PRFileDesc* aFD,
+ PRNetAddr& aPrClientAddr)
+ : mSocket(aSocket)
+ , mFD(aFD)
+ , mPrClientAddr(aPrClientAddr)
+ , mIsClosed(false)
+{
+}
+
+nsUDPOutputStream::~nsUDPOutputStream()
+{
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Close()
+{
+ if (mIsClosed)
+ return NS_BASE_STREAM_CLOSED;
+
+ mIsClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Write(const char * aBuf, uint32_t aCount, uint32_t *_retval)
+{
+ if (mIsClosed)
+ return NS_BASE_STREAM_CLOSED;
+
+ *_retval = 0;
+ int32_t count = PR_SendTo(mFD, aBuf, aCount, 0, &mPrClientAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+
+ *_retval = count;
+
+ mSocket->AddOutputBytes(count);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::WriteSegments(nsReadSegmentFun aReader, void *aClosure, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::IsNonBlocking(bool *_retval)
+{
+ *_retval = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsUDPMessage impl
+//-----------------------------------------------------------------------------
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsUDPMessage)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsUDPMessage)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsUDPMessage)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsUDPMessage)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIUDPMessage)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsUDPMessage)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJsobj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsUDPMessage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsUDPMessage)
+ tmp->mJsobj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+nsUDPMessage::nsUDPMessage(NetAddr* aAddr,
+ nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>& aData)
+ : mOutputStream(aOutputStream)
+{
+ memcpy(&mAddr, aAddr, sizeof(NetAddr));
+ aData.SwapElements(mData);
+}
+
+nsUDPMessage::~nsUDPMessage()
+{
+ DropJSObjects(this);
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetFromAddr(nsINetAddr * *aFromAddr)
+{
+ NS_ENSURE_ARG_POINTER(aFromAddr);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aFromAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetData(nsACString & aData)
+{
+ aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetOutputStream(nsIOutputStream * *aOutputStream)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_IF_ADDREF(*aOutputStream = mOutputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetRawData(JSContext* cx,
+ JS::MutableHandleValue aRawData)
+{
+ if(!mJsobj){
+ mJsobj = dom::Uint8Array::Create(cx, nullptr, mData.Length(), mData.Elements());
+ HoldJSObjects(this);
+ }
+ aRawData.setObject(*mJsobj);
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>&
+nsUDPMessage::GetDataAsTArray()
+{
+ return mData;
+}
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket
+//-----------------------------------------------------------------------------
+
+nsUDPSocket::nsUDPSocket()
+ : mLock("nsUDPSocket.mLock")
+ , mFD(nullptr)
+ , mAppId(NECKO_UNKNOWN_APP_ID)
+ , mIsInIsolatedMozBrowserElement(false)
+ , mAttached(false)
+ , mByteReadCount(0)
+ , mByteWriteCount(0)
+{
+ mAddr.raw.family = PR_AF_UNSPEC;
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService)
+ {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID2);
+ }
+
+ mSts = gSocketTransportService;
+ MOZ_COUNT_CTOR(nsUDPSocket);
+}
+
+nsUDPSocket::~nsUDPSocket()
+{
+ CloseSocket();
+ MOZ_COUNT_DTOR(nsUDPSocket);
+}
+
+void
+nsUDPSocket::AddOutputBytes(uint64_t aBytes)
+{
+ mByteWriteCount += aBytes;
+ SaveNetworkStats(false);
+}
+
+void
+nsUDPSocket::OnMsgClose()
+{
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgClose [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition))
+ return;
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then socket transport service will call our
+ // OnSocketDetached method automatically. Otherwise, we have to call it
+ // (and thus close the socket) manually.
+ if (!mAttached)
+ OnSocketDetached(mFD);
+}
+
+void
+nsUDPSocket::OnMsgAttach()
+{
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgAttach [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition))
+ return;
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition))
+ {
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+}
+
+nsresult
+nsUDPSocket::TryAttach()
+{
+ nsresult rv;
+
+ if (!gSocketTransportService)
+ return NS_ERROR_FAILURE;
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket())
+ {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &nsUDPSocket::OnMsgAttach);
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+ return NS_OK;
+}
+
+namespace {
+//-----------------------------------------------------------------------------
+// UDPMessageProxy
+//-----------------------------------------------------------------------------
+class UDPMessageProxy final : public nsIUDPMessage
+{
+public:
+ UDPMessageProxy(NetAddr* aAddr,
+ nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>& aData)
+ : mOutputStream(aOutputStream)
+ {
+ memcpy(&mAddr, aAddr, sizeof(mAddr));
+ aData.SwapElements(mData);
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPMESSAGE
+
+private:
+ ~UDPMessageProxy() {}
+
+ NetAddr mAddr;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(UDPMessageProxy, nsIUDPMessage)
+
+NS_IMETHODIMP
+UDPMessageProxy::GetFromAddr(nsINetAddr * *aFromAddr)
+{
+ NS_ENSURE_ARG_POINTER(aFromAddr);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aFromAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetData(nsACString & aData)
+{
+ aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length());
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>&
+UDPMessageProxy::GetDataAsTArray()
+{
+ return mData;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetRawData(JSContext* cx,
+ JS::MutableHandleValue aRawData)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetOutputStream(nsIOutputStream * *aOutputStream)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_IF_ADDREF(*aOutputStream = mOutputStream);
+ return NS_OK;
+}
+
+} //anonymous namespace
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void
+nsUDPSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
+{
+ NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
+
+ if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL))
+ {
+ NS_WARNING("error polling on listening socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ PRNetAddr prClientAddr;
+ uint32_t count;
+ // Bug 1252755 - use 9216 bytes to allign with nICEr and transportlayer to
+ // support the maximum size of jumbo frames
+ char buff[9216];
+ count = PR_RecvFrom(mFD, buff, sizeof(buff), 0, &prClientAddr, PR_INTERVAL_NO_WAIT);
+
+ if (count < 1) {
+ NS_WARNING("error of recvfrom on UDP socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+ mByteReadCount += count;
+ SaveNetworkStats(false);
+
+ FallibleTArray<uint8_t> data;
+ if (!data.AppendElements(buff, count, fallible)) {
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+
+ uint32_t segsize = UDP_PACKET_CHUNK_SIZE;
+ uint32_t segcount = 0;
+ net_ResolveSegmentParams(segsize, segcount);
+ nsresult rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
+ true, true, segsize, segcount);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prClientAddr);
+ rv = NS_AsyncCopy(pipeIn, os, mSts,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, UDP_PACKET_CHUNK_SIZE);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ NetAddr netAddr;
+ PRNetAddrToNetAddr(&prClientAddr, &netAddr);
+ nsCOMPtr<nsIUDPMessage> message = new UDPMessageProxy(&netAddr, pipeOut, data);
+ mListener->OnPacketReceived(this, message);
+}
+
+void
+nsUDPSocket::OnSocketDetached(PRFileDesc *fd)
+{
+ // force a failure condition if none set; maybe the STS is shutting down :-/
+ if (NS_SUCCEEDED(mCondition))
+ mCondition = NS_ERROR_ABORT;
+
+ if (mFD)
+ {
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ CloseSocket();
+ }
+ SaveNetworkStats(true);
+
+ if (mListener)
+ {
+ // need to atomically clear mListener. see our Close() method.
+ RefPtr<nsIUDPSocketListener> listener = nullptr;
+ {
+ MutexAutoLock lock(mLock);
+ listener = mListener.forget();
+ }
+
+ if (listener) {
+ listener->OnStopListening(this, mCondition);
+ NS_ProxyRelease(mListenerTarget, listener.forget());
+ }
+ }
+}
+
+void
+nsUDPSocket::IsLocal(bool *aIsLocal)
+{
+ // If bound to loopback, this UDP socket only accepts local connections.
+ *aIsLocal = mAddr.raw.family == nsINetAddr::FAMILY_LOCAL;
+}
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsUDPSocket, nsIUDPSocket)
+
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsUDPSocket::Init(int32_t aPort, bool aLoopbackOnly, nsIPrincipal *aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc)
+{
+ NetAddr addr;
+
+ if (aPort < 0)
+ aPort = 0;
+
+ addr.raw.family = AF_INET;
+ addr.inet.port = htons(aPort);
+
+ if (aLoopbackOnly)
+ addr.inet.ip = htonl(INADDR_LOOPBACK);
+ else
+ addr.inet.ip = htonl(INADDR_ANY);
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Init2(const nsACString& aAddr, int32_t aPort, nsIPrincipal *aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc)
+{
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NetAddr addr;
+
+ if (aPort < 0)
+ aPort = 0;
+
+ addr.raw.family = AF_INET;
+ addr.inet.port = htons(aPort);
+ addr.inet.ip = prAddr.inet.ip;
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::InitWithAddress(const NetAddr *aAddr, nsIPrincipal *aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc)
+{
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool addressReuse = (aOptionalArgc == 1) ? aAddressReuse : true;
+
+ //
+ // configure listening socket...
+ //
+
+ mFD = PR_OpenUDPSocket(aAddr->raw.family);
+ if (!mFD)
+ {
+ NS_WARNING("unable to create UDP socket");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aPrincipal) {
+ mAppId = aPrincipal->GetAppId();
+ mIsInIsolatedMozBrowserElement =
+ aPrincipal->GetIsInIsolatedMozBrowserElement();
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ if (mAppId != NECKO_UNKNOWN_APP_ID) {
+ nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+ GetActiveNetworkInfo(activeNetworkInfo);
+ mActiveNetworkInfo =
+ new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo);
+ }
+#endif
+
+ uint16_t port;
+ if (NS_FAILED(net::GetPort(aAddr, &port))) {
+ NS_WARNING("invalid bind address");
+ goto fail;
+ }
+
+ PRSocketOptionData opt;
+
+ // Linux kernel will sometimes hand out a used port if we bind
+ // to port 0 with SO_REUSEADDR
+ if (port) {
+ opt.option = PR_SockOpt_Reuseaddr;
+ opt.value.reuse_addr = addressReuse;
+ PR_SetSocketOption(mFD, &opt);
+ }
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ PRNetAddr addr;
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &addr);
+ NetAddrToPRNetAddr(aAddr, &addr);
+
+ if (PR_Bind(mFD, &addr) != PR_SUCCESS)
+ {
+ NS_WARNING("failed to bind socket");
+ goto fail;
+ }
+
+ // get the resulting socket address, which may be different than what
+ // we passed to bind.
+ if (PR_GetSockName(mFD, &addr) != PR_SUCCESS)
+ {
+ NS_WARNING("cannot get socket name");
+ goto fail;
+ }
+
+ PRNetAddrToNetAddr(&addr, &mAddr);
+
+ // create proxy via NetworkActivityMonitor
+ NetworkActivityMonitor::AttachIOLayer(mFD);
+
+ // wait until AsyncListen is called before polling the socket for
+ // client connections.
+ return NS_OK;
+
+fail:
+ Close();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Connect(const NetAddr *aAddr)
+{
+ UDPSOCKET_LOG(("nsUDPSocket::Connect [this=%p]\n", this));
+
+ NS_ENSURE_ARG(aAddr);
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+ NS_ASSERTION(onSTSThread, "NOT ON STS THREAD");
+ if (!onSTSThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ if (PR_Connect(mFD, &prAddr, PR_INTERVAL_NO_WAIT) != PR_SUCCESS) {
+ NS_WARNING("Cannot PR_Connect");
+ return NS_ERROR_FAILURE;
+ }
+
+ // get the resulting socket address, which may have been updated.
+ PRNetAddr addr;
+ if (PR_GetSockName(mFD, &addr) != PR_SUCCESS)
+ {
+ NS_WARNING("cannot get socket name");
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddrToNetAddr(&addr, &mAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Close()
+{
+ {
+ MutexAutoLock lock(mLock);
+ // we want to proxy the close operation to the socket thread if a listener
+ // has been set. otherwise, we should just close the socket here...
+ if (!mListener)
+ {
+ // Here we want to go directly with closing the socket since some tests
+ // expects this happen synchronously.
+ CloseSocket();
+
+ SaveNetworkStats(true);
+ return NS_OK;
+ }
+ }
+ return PostEvent(this, &nsUDPSocket::OnMsgClose);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetPort(int32_t *aResult)
+{
+ // no need to enter the lock here
+ uint16_t result;
+ nsresult rv = net::GetPort(&mAddr, &result);
+ *aResult = static_cast<int32_t>(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetLocalAddr(nsINetAddr * *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aResult);
+
+ return NS_OK;
+}
+
+void
+nsUDPSocket::SaveNetworkStats(bool aEnforce)
+{
+#ifdef MOZ_WIDGET_GONK
+ if (!mActiveNetworkInfo || mAppId == NECKO_UNKNOWN_APP_ID) {
+ return;
+ }
+
+ if (mByteReadCount == 0 && mByteWriteCount == 0) {
+ return;
+ }
+
+ uint64_t total = mByteReadCount + mByteWriteCount;
+ if (aEnforce || total > NETWORK_STATS_THRESHOLD) {
+ // Create the event to save the network statistics.
+ // the event is then dispathed to the main thread.
+ RefPtr<Runnable> event =
+ new SaveNetworkStatsEvent(mAppId, mIsInIsolatedMozBrowserElement, mActiveNetworkInfo,
+ mByteReadCount, mByteWriteCount, false);
+ NS_DispatchToMainThread(event);
+
+ // Reset the counters after saving.
+ mByteReadCount = 0;
+ mByteWriteCount = 0;
+ }
+#endif
+}
+
+void
+nsUDPSocket::CloseSocket()
+{
+ if (mFD) {
+ if (gIOService->IsNetTearingDown() &&
+ ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
+ gSocketTransportService->MaxTimeForPrClosePref())) {
+ // If shutdown last to long, let the socket leak and do not close it.
+ UDPSOCKET_LOG(("Intentional leak"));
+ } else {
+
+ PRIntervalTime closeStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ closeStarted = PR_IntervalNow();
+ }
+
+ PR_Close(mFD);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ PRIntervalTime now = PR_IntervalNow();
+ if (gIOService->IsNetTearingDown()) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_SHUTDOWN,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange())
+ < 60) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange())
+ < 60) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_LINK_CHANGE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange())
+ < 60) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_OFFLINE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_NORMAL,
+ PR_IntervalToMilliseconds(now - closeStarted));
+ }
+ }
+ }
+ mFD = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetAddress(NetAddr *aResult)
+{
+ // no need to enter the lock here
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
+namespace {
+//-----------------------------------------------------------------------------
+// SocketListenerProxy
+//-----------------------------------------------------------------------------
+class SocketListenerProxy final : public nsIUDPSocketListener
+{
+ ~SocketListenerProxy() {}
+
+public:
+ explicit SocketListenerProxy(nsIUDPSocketListener* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsIUDPSocketListener>(aListener))
+ , mTargetThread(do_GetCurrentThread())
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ class OnPacketReceivedRunnable : public Runnable
+ {
+ public:
+ OnPacketReceivedRunnable(const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage)
+ : mListener(aListener)
+ , mSocket(aSocket)
+ , mMessage(aMessage)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPMessage> mMessage;
+ };
+
+ class OnStopListeningRunnable : public Runnable
+ {
+ public:
+ OnStopListeningRunnable(const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket,
+ nsresult aStatus)
+ : mListener(aListener)
+ , mSocket(aSocket)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsresult mStatus;
+ };
+
+private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+};
+
+NS_IMPL_ISUPPORTS(SocketListenerProxy,
+ nsIUDPSocketListener)
+
+NS_IMETHODIMP
+SocketListenerProxy::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage)
+{
+ RefPtr<OnPacketReceivedRunnable> r =
+ new OnPacketReceivedRunnable(mListener, aSocket, aMessage);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnStopListening(nsIUDPSocket* aSocket,
+ nsresult aStatus)
+{
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aSocket, aStatus);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnPacketReceivedRunnable::Run()
+{
+ NetAddr netAddr;
+ nsCOMPtr<nsINetAddr> nsAddr;
+ mMessage->GetFromAddr(getter_AddRefs(nsAddr));
+ nsAddr->GetNetAddr(&netAddr);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mMessage->GetOutputStream(getter_AddRefs(outputStream));
+
+ FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray();
+
+ nsCOMPtr<nsIUDPMessage> message = new nsUDPMessage(&netAddr,
+ outputStream,
+ data);
+ mListener->OnPacketReceived(mSocket, message);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnStopListeningRunnable::Run()
+{
+ mListener->OnStopListening(mSocket, mStatus);
+ return NS_OK;
+}
+
+
+class SocketListenerProxyBackground final : public nsIUDPSocketListener
+{
+ ~SocketListenerProxyBackground() {}
+
+public:
+ explicit SocketListenerProxyBackground(nsIUDPSocketListener* aListener)
+ : mListener(aListener)
+ , mTargetThread(do_GetCurrentThread())
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ class OnPacketReceivedRunnable : public Runnable
+ {
+ public:
+ OnPacketReceivedRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage)
+ : mListener(aListener)
+ , mSocket(aSocket)
+ , mMessage(aMessage)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPMessage> mMessage;
+ };
+
+ class OnStopListeningRunnable : public Runnable
+ {
+ public:
+ OnStopListeningRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket,
+ nsresult aStatus)
+ : mListener(aListener)
+ , mSocket(aSocket)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsresult mStatus;
+ };
+
+private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+};
+
+NS_IMPL_ISUPPORTS(SocketListenerProxyBackground,
+ nsIUDPSocketListener)
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage)
+{
+ RefPtr<OnPacketReceivedRunnable> r =
+ new OnPacketReceivedRunnable(mListener, aSocket, aMessage);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnStopListening(nsIUDPSocket* aSocket,
+ nsresult aStatus)
+{
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aSocket, aStatus);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnPacketReceivedRunnable::Run()
+{
+ NetAddr netAddr;
+ nsCOMPtr<nsINetAddr> nsAddr;
+ mMessage->GetFromAddr(getter_AddRefs(nsAddr));
+ nsAddr->GetNetAddr(&netAddr);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mMessage->GetOutputStream(getter_AddRefs(outputStream));
+
+ FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray();
+
+ UDPSOCKET_LOG(("%s [this=%p], len %u", __FUNCTION__, this, data.Length()));
+ nsCOMPtr<nsIUDPMessage> message = new UDPMessageProxy(&netAddr,
+ outputStream,
+ data);
+ mListener->OnPacketReceived(mSocket, message);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnStopListeningRunnable::Run()
+{
+ mListener->OnStopListening(mSocket, mStatus);
+ return NS_OK;
+}
+
+
+class PendingSend : public nsIDNSListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ PendingSend(nsUDPSocket *aSocket, uint16_t aPort,
+ FallibleTArray<uint8_t> &aData)
+ : mSocket(aSocket)
+ , mPort(aPort)
+ {
+ mData.SwapElements(aData);
+ }
+
+private:
+ virtual ~PendingSend() {}
+
+ RefPtr<nsUDPSocket> mSocket;
+ uint16_t mPort;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(PendingSend, nsIDNSListener)
+
+NS_IMETHODIMP
+PendingSend::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ if (NS_FAILED(status)) {
+ NS_WARNING("Failed to send UDP packet due to DNS lookup failure");
+ return NS_OK;
+ }
+
+ NetAddr addr;
+ if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
+ uint32_t count;
+ nsresult rv = mSocket->SendWithAddress(&addr, mData.Elements(),
+ mData.Length(), &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+class PendingSendStream : public nsIDNSListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ PendingSendStream(nsUDPSocket *aSocket, uint16_t aPort,
+ nsIInputStream *aStream)
+ : mSocket(aSocket)
+ , mPort(aPort)
+ , mStream(aStream) {}
+
+private:
+ virtual ~PendingSendStream() {}
+
+ RefPtr<nsUDPSocket> mSocket;
+ uint16_t mPort;
+ nsCOMPtr<nsIInputStream> mStream;
+};
+
+NS_IMPL_ISUPPORTS(PendingSendStream, nsIDNSListener)
+
+NS_IMETHODIMP
+PendingSendStream::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ if (NS_FAILED(status)) {
+ NS_WARNING("Failed to send UDP packet due to DNS lookup failure");
+ return NS_OK;
+ }
+
+ NetAddr addr;
+ if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
+ nsresult rv = mSocket->SendBinaryStreamWithAddress(&addr, mStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+class SendRequestRunnable: public Runnable {
+public:
+ SendRequestRunnable(nsUDPSocket *aSocket,
+ const NetAddr &aAddr,
+ FallibleTArray<uint8_t>&& aData)
+ : mSocket(aSocket)
+ , mAddr(aAddr)
+ , mData(Move(aData))
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+private:
+ RefPtr<nsUDPSocket> mSocket;
+ const NetAddr mAddr;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMETHODIMP
+SendRequestRunnable::Run()
+{
+ uint32_t count;
+ mSocket->SendWithAddress(&mAddr, mData.Elements(),
+ mData.Length(), &count);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsUDPSocket::AsyncListen(nsIUDPSocketListener *aListener)
+{
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListenerTarget = NS_GetCurrentThread();
+ if (NS_IsMainThread()) {
+ // PNecko usage
+ mListener = new SocketListenerProxy(aListener);
+ } else {
+ // PBackground usage from media/mtransport
+ mListener = new SocketListenerProxyBackground(aListener);
+ }
+ }
+ return PostEvent(this, &nsUDPSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Send(const nsACString &aHost, uint16_t aPort,
+ const uint8_t *aData, uint32_t aDataLength,
+ uint32_t *_retval)
+{
+ NS_ENSURE_ARG(aData);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = 0;
+
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, aData, aDataLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCOMPtr<nsIDNSListener> listener = new PendingSend(this, aPort, fallibleArray);
+
+ nsresult rv = ResolveHost(aHost, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = aDataLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddr(nsINetAddr *aAddr, const uint8_t *aData,
+ uint32_t aDataLength, uint32_t *_retval)
+{
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aData);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ NetAddr netAddr;
+ aAddr->GetNetAddr(&netAddr);
+ return SendWithAddress(&netAddr, aData, aDataLength, _retval);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddress(const NetAddr *aAddr, const uint8_t *aData,
+ uint32_t aDataLength, uint32_t *_retval)
+{
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aData);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = 0;
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (onSTSThread) {
+ MutexAutoLock lock(mLock);
+ if (!mFD) {
+ // socket is not initialized or has been closed
+ return NS_ERROR_FAILURE;
+ }
+ int32_t count = PR_SendTo(mFD, aData, sizeof(uint8_t) *aDataLength,
+ 0, &prAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+ this->AddOutputBytes(count);
+ *_retval = count;
+ } else {
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, aData, aDataLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = mSts->Dispatch(
+ new SendRequestRunnable(this, *aAddr, Move(fallibleArray)),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *_retval = aDataLength;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendBinaryStream(const nsACString &aHost, uint16_t aPort,
+ nsIInputStream *aStream)
+{
+ NS_ENSURE_ARG(aStream);
+
+ nsCOMPtr<nsIDNSListener> listener = new PendingSendStream(this, aPort, aStream);
+
+ return ResolveHost(aHost, listener);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendBinaryStreamWithAddress(const NetAddr *aAddr, nsIInputStream *aStream)
+{
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aStream);
+
+ PRNetAddr prAddr;
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prAddr);
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prAddr);
+ return NS_AsyncCopy(aStream, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ UDP_PACKET_CHUNK_SIZE);
+}
+
+nsresult
+nsUDPSocket::SetSocketOption(const PRSocketOptionData& aOpt)
+{
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (!onSTSThread) {
+ // Dispatch to STS thread and re-enter this method there
+ nsCOMPtr<nsIRunnable> runnable = new SetSocketOptionRunnable(this, aOpt);
+ nsresult rv = mSts->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (PR_SetSocketOption(mFD, &aOpt) != PR_SUCCESS) {
+ UDPSOCKET_LOG(("nsUDPSocket::SetSocketOption [this=%p] failed for type %d, "
+ "error %d\n", this, aOpt.option, PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticast(const nsACString& aAddr, const nsACString& aIface)
+{
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticastAddr(const NetAddr aAddr, const NetAddr* aIface)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+nsresult
+nsUDPSocket::JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface)
+{
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_AddMember;
+ opt.value.add_member.mcaddr = aAddr;
+ opt.value.add_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticast(const nsACString& aAddr, const nsACString& aIface)
+{
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticastAddr(const NetAddr aAddr, const NetAddr* aIface)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+nsresult
+nsUDPSocket::LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface)
+{
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_DropMember;
+ opt.value.drop_member.mcaddr = aAddr;
+ opt.value.drop_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastLoopback(bool* aLoopback)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastLoopback(bool aLoopback)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastLoopback;
+ opt.value.mcast_loopback = aLoopback;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetRecvBufferSize(int* size)
+{
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetRecvBufferSize(int size)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetSendBufferSize(int* size)
+{
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetSendBufferSize(int size)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterface(nsACString& aIface)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterfaceAddr(NetAddr* aIface)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterface(const nsACString& aIface)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterfaceAddr(NetAddr aIface)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ NetAddrToPRNetAddr(&aIface, &prIface);
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+nsresult
+nsUDPSocket::SetMulticastInterfaceInternal(const PRNetAddr& aIface)
+{
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastInterface;
+ opt.value.mcast_if = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsUDPSocket.h b/netwerk/base/nsUDPSocket.h
new file mode 100644
index 0000000000..4ddff42489
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.h
@@ -0,0 +1,131 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUDPSocket_h__
+#define nsUDPSocket_h__
+
+#include "nsIUDPSocket.h"
+#include "mozilla/Mutex.h"
+#include "nsIOutputStream.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsProxyRelease.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsUDPSocket final : public nsASocketHandler
+ , public nsIUDPSocket
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+
+ uint64_t ByteCountSent() override { return mByteWriteCount; }
+ uint64_t ByteCountReceived() override { return mByteReadCount; }
+
+ void AddOutputBytes(uint64_t aBytes);
+
+ nsUDPSocket();
+
+private:
+ virtual ~nsUDPSocket();
+
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ friend class SetSocketOptionRunnable;
+ nsresult SetSocketOption(const PRSocketOptionData& aOpt);
+ nsresult JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult SetMulticastInterfaceInternal(const PRNetAddr& aIface);
+
+ void SaveNetworkStats(bool aEnforce);
+
+ void CloseSocket();
+
+ // lock protects access to mListener;
+ // so mListener is not cleared while being used/locked.
+ Mutex mLock;
+ PRFileDesc *mFD;
+ NetAddr mAddr;
+ uint32_t mAppId;
+ bool mIsInIsolatedMozBrowserElement;
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached;
+ RefPtr<nsSocketTransportService> mSts;
+
+ uint64_t mByteReadCount;
+ uint64_t mByteWriteCount;
+#ifdef MOZ_WIDGET_GONK
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+};
+
+//-----------------------------------------------------------------------------
+
+class nsUDPMessage : public nsIUDPMessage
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsUDPMessage)
+ NS_DECL_NSIUDPMESSAGE
+
+ nsUDPMessage(NetAddr* aAddr,
+ nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>& aData);
+
+private:
+ virtual ~nsUDPMessage();
+
+ NetAddr mAddr;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ FallibleTArray<uint8_t> mData;
+ JS::Heap<JSObject*> mJsobj;
+};
+
+
+//-----------------------------------------------------------------------------
+
+class nsUDPOutputStream : public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsUDPOutputStream(nsUDPSocket* aSocket,
+ PRFileDesc* aFD,
+ PRNetAddr& aPrClientAddr);
+
+private:
+ virtual ~nsUDPOutputStream();
+
+ RefPtr<nsUDPSocket> mSocket;
+ PRFileDesc *mFD;
+ PRNetAddr mPrClientAddr;
+ bool mIsClosed;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsUDPSocket_h__
diff --git a/netwerk/base/nsURIHashKey.h b/netwerk/base/nsURIHashKey.h
new file mode 100644
index 0000000000..520d6c6faf
--- /dev/null
+++ b/netwerk/base/nsURIHashKey.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsURIHashKey_h__
+#define nsURIHashKey_h__
+
+#include "PLDHashTable.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsHashKeys.h"
+#include "mozilla/Unused.h"
+
+/**
+ * Hashtable key class to use with nsTHashtable/nsBaseHashtable
+ */
+class nsURIHashKey : public PLDHashEntryHdr
+{
+public:
+ typedef nsIURI* KeyType;
+ typedef const nsIURI* KeyTypePointer;
+
+ explicit nsURIHashKey(const nsIURI* aKey) :
+ mKey(const_cast<nsIURI*>(aKey)) { MOZ_COUNT_CTOR(nsURIHashKey); }
+ nsURIHashKey(const nsURIHashKey& toCopy) :
+ mKey(toCopy.mKey) { MOZ_COUNT_CTOR(nsURIHashKey); }
+ ~nsURIHashKey() { MOZ_COUNT_DTOR(nsURIHashKey); }
+
+ nsIURI* GetKey() const { return mKey; }
+
+ bool KeyEquals(const nsIURI* aKey) const {
+ bool eq;
+ if (!mKey) {
+ return !aKey;
+ }
+ if (NS_SUCCEEDED(mKey->Equals(const_cast<nsIURI*>(aKey), &eq))) {
+ return eq;
+ }
+ return false;
+ }
+
+ static const nsIURI* KeyToPointer(nsIURI* aKey) { return aKey; }
+ static PLDHashNumber HashKey(const nsIURI* aKey) {
+ if (!aKey) {
+ // If the key is null, return hash for empty string.
+ return mozilla::HashString(EmptyCString());
+ }
+ nsAutoCString spec;
+ // If GetSpec() fails, ignoring the failure and proceeding with an
+ // empty |spec| seems like the best thing to do.
+ mozilla::Unused << const_cast<nsIURI*>(aKey)->GetSpec(spec);
+ return mozilla::HashString(spec);
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+protected:
+ nsCOMPtr<nsIURI> mKey;
+};
+
+#endif // nsURIHashKey_h__
diff --git a/netwerk/base/nsURLHelper.cpp b/netwerk/base/nsURLHelper.cpp
new file mode 100644
index 0000000000..8def697dac
--- /dev/null
+++ b/netwerk/base/nsURLHelper.cpp
@@ -0,0 +1,1168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/RangedPtr.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "nsURLHelper.h"
+#include "nsIFile.h"
+#include "nsIURLParser.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "mozilla/Preferences.h"
+#include "prnetdb.h"
+#include "mozilla/Tokenizer.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+// Init/Shutdown
+//----------------------------------------------------------------------------
+
+static bool gInitialized = false;
+static nsIURLParser *gNoAuthURLParser = nullptr;
+static nsIURLParser *gAuthURLParser = nullptr;
+static nsIURLParser *gStdURLParser = nullptr;
+static int32_t gMaxLength = 1048576; // Default: 1MB
+
+static void
+InitGlobals()
+{
+ nsCOMPtr<nsIURLParser> parser;
+
+ parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'noauth' url parser");
+ if (parser) {
+ gNoAuthURLParser = parser.get();
+ NS_ADDREF(gNoAuthURLParser);
+ }
+
+ parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'auth' url parser");
+ if (parser) {
+ gAuthURLParser = parser.get();
+ NS_ADDREF(gAuthURLParser);
+ }
+
+ parser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'std' url parser");
+ if (parser) {
+ gStdURLParser = parser.get();
+ NS_ADDREF(gStdURLParser);
+ }
+
+ gInitialized = true;
+ Preferences::AddIntVarCache(&gMaxLength,
+ "network.standard-url.max-length", 1048576);
+}
+
+void
+net_ShutdownURLHelper()
+{
+ if (gInitialized) {
+ NS_IF_RELEASE(gNoAuthURLParser);
+ NS_IF_RELEASE(gAuthURLParser);
+ NS_IF_RELEASE(gStdURLParser);
+ gInitialized = false;
+ }
+}
+
+int32_t net_GetURLMaxLength()
+{
+ return gMaxLength;
+}
+
+//----------------------------------------------------------------------------
+// nsIURLParser getters
+//----------------------------------------------------------------------------
+
+nsIURLParser *
+net_GetAuthURLParser()
+{
+ if (!gInitialized)
+ InitGlobals();
+ return gAuthURLParser;
+}
+
+nsIURLParser *
+net_GetNoAuthURLParser()
+{
+ if (!gInitialized)
+ InitGlobals();
+ return gNoAuthURLParser;
+}
+
+nsIURLParser *
+net_GetStdURLParser()
+{
+ if (!gInitialized)
+ InitGlobals();
+ return gStdURLParser;
+}
+
+//---------------------------------------------------------------------------
+// GetFileFromURLSpec implementations
+//---------------------------------------------------------------------------
+nsresult
+net_GetURLSpecFromDir(nsIFile *aFile, nsACString &result)
+{
+ nsAutoCString escPath;
+ nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (escPath.Last() != '/') {
+ escPath += '/';
+ }
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetURLSpecFromFile(nsIFile *aFile, nsACString &result)
+{
+ nsAutoCString escPath;
+ nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // if this file references a directory, then we need to ensure that the
+ // URL ends with a slash. this is important since it affects the rules
+ // for relative URL resolution when this URL is used as a base URL.
+ // if the file does not exist, then we make no assumption about its type,
+ // and simply leave the URL unmodified.
+ if (escPath.Last() != '/') {
+ bool dir;
+ rv = aFile->IsDirectory(&dir);
+ if (NS_SUCCEEDED(rv) && dir)
+ escPath += '/';
+ }
+
+ result = escPath;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// file:// URL parsing
+//----------------------------------------------------------------------------
+
+nsresult
+net_ParseFileURL(const nsACString &inURL,
+ nsACString &outDirectory,
+ nsACString &outFileBaseName,
+ nsACString &outFileExtension)
+{
+ nsresult rv;
+
+ if (inURL.Length() > (uint32_t) gMaxLength) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ outDirectory.Truncate();
+ outFileBaseName.Truncate();
+ outFileExtension.Truncate();
+
+ const nsPromiseFlatCString &flatURL = PromiseFlatCString(inURL);
+ const char *url = flatURL.get();
+
+ nsAutoCString scheme;
+ rv = net_ExtractURLScheme(flatURL, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!scheme.EqualsLiteral("file")) {
+ NS_ERROR("must be a file:// url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsIURLParser *parser = net_GetNoAuthURLParser();
+ NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
+
+ uint32_t pathPos, filepathPos, directoryPos, basenamePos, extensionPos;
+ int32_t pathLen, filepathLen, directoryLen, basenameLen, extensionLen;
+
+ // invoke the parser to extract the URL path
+ rv = parser->ParseURL(url, flatURL.Length(),
+ nullptr, nullptr, // don't care about scheme
+ nullptr, nullptr, // don't care about authority
+ &pathPos, &pathLen);
+ if (NS_FAILED(rv)) return rv;
+
+ // invoke the parser to extract filepath from the path
+ rv = parser->ParsePath(url + pathPos, pathLen,
+ &filepathPos, &filepathLen,
+ nullptr, nullptr, // don't care about query
+ nullptr, nullptr); // don't care about ref
+ if (NS_FAILED(rv)) return rv;
+
+ filepathPos += pathPos;
+
+ // invoke the parser to extract the directory and filename from filepath
+ rv = parser->ParseFilePath(url + filepathPos, filepathLen,
+ &directoryPos, &directoryLen,
+ &basenamePos, &basenameLen,
+ &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) return rv;
+
+ if (directoryLen > 0)
+ outDirectory = Substring(inURL, filepathPos + directoryPos, directoryLen);
+ if (basenameLen > 0)
+ outFileBaseName = Substring(inURL, filepathPos + basenamePos, basenameLen);
+ if (extensionLen > 0)
+ outFileExtension = Substring(inURL, filepathPos + extensionPos, extensionLen);
+ // since we are using a no-auth url parser, there will never be a host
+ // XXX not strictly true... file://localhost/foo/bar.html is a valid URL
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// path manipulation functions
+//----------------------------------------------------------------------------
+
+// Replace all /./ with a / while resolving URLs
+// But only till #?
+void
+net_CoalesceDirs(netCoalesceFlags flags, char* path)
+{
+ /* Stolen from the old netlib's mkparse.c.
+ *
+ * modifies a url of the form /foo/../foo1 -> /foo1
+ * and /foo/./foo1 -> /foo/foo1
+ * and /foo/foo1/.. -> /foo/
+ */
+ char *fwdPtr = path;
+ char *urlPtr = path;
+ char *lastslash = path;
+ uint32_t traversal = 0;
+ uint32_t special_ftp_len = 0;
+
+ /* Remember if this url is a special ftp one: */
+ if (flags & NET_COALESCE_DOUBLE_SLASH_IS_ROOT)
+ {
+ /* some schemes (for example ftp) have the speciality that
+ the path can begin // or /%2F to mark the root of the
+ servers filesystem, a simple / only marks the root relative
+ to the user loging in. We remember the length of the marker */
+ if (nsCRT::strncasecmp(path,"/%2F",4) == 0)
+ special_ftp_len = 4;
+ else if (nsCRT::strncmp(path,"//",2) == 0 )
+ special_ftp_len = 2;
+ }
+
+ /* find the last slash before # or ? */
+ for(; (*fwdPtr != '\0') &&
+ (*fwdPtr != '?') &&
+ (*fwdPtr != '#'); ++fwdPtr)
+ {
+ }
+
+ /* found nothing, but go back one only */
+ /* if there is something to go back to */
+ if (fwdPtr != path && *fwdPtr == '\0')
+ {
+ --fwdPtr;
+ }
+
+ /* search the slash */
+ for(; (fwdPtr != path) &&
+ (*fwdPtr != '/'); --fwdPtr)
+ {
+ }
+ lastslash = fwdPtr;
+ fwdPtr = path;
+
+ /* replace all %2E or %2e with . in the path */
+ /* but stop at lastchar if non null */
+ for(; (*fwdPtr != '\0') &&
+ (*fwdPtr != '?') &&
+ (*fwdPtr != '#') &&
+ (*lastslash == '\0' || fwdPtr != lastslash); ++fwdPtr)
+ {
+ if (*fwdPtr == '%' && *(fwdPtr+1) == '2' &&
+ (*(fwdPtr+2) == 'E' || *(fwdPtr+2) == 'e'))
+ {
+ *urlPtr++ = '.';
+ ++fwdPtr;
+ ++fwdPtr;
+ }
+ else
+ {
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+ // Copy remaining stuff past the #?;
+ for (; *fwdPtr != '\0'; ++fwdPtr)
+ {
+ *urlPtr++ = *fwdPtr;
+ }
+ *urlPtr = '\0'; // terminate the url
+
+ // start again, this time for real
+ fwdPtr = path;
+ urlPtr = path;
+
+ for(; (*fwdPtr != '\0') &&
+ (*fwdPtr != '?') &&
+ (*fwdPtr != '#'); ++fwdPtr)
+ {
+ if (*fwdPtr == '/' && *(fwdPtr+1) == '.' && *(fwdPtr+2) == '/' )
+ {
+ // remove . followed by slash
+ ++fwdPtr;
+ }
+ else if(*fwdPtr == '/' && *(fwdPtr+1) == '.' && *(fwdPtr+2) == '.' &&
+ (*(fwdPtr+3) == '/' ||
+ *(fwdPtr+3) == '\0' || // This will take care of
+ *(fwdPtr+3) == '?' || // something like foo/bar/..#sometag
+ *(fwdPtr+3) == '#'))
+ {
+ // remove foo/..
+ // reverse the urlPtr to the previous slash if possible
+ // if url does not allow relative root then drop .. above root
+ // otherwise retain them in the path
+ if(traversal > 0 || !(flags &
+ NET_COALESCE_ALLOW_RELATIVE_ROOT))
+ {
+ if (urlPtr != path)
+ urlPtr--; // we must be going back at least by one
+ for(;*urlPtr != '/' && urlPtr != path; urlPtr--)
+ ; // null body
+ --traversal; // count back
+ // forward the fwdPtr past the ../
+ fwdPtr += 2;
+ // if we have reached the beginning of the path
+ // while searching for the previous / and we remember
+ // that it is an url that begins with /%2F then
+ // advance urlPtr again by 3 chars because /%2F already
+ // marks the root of the path
+ if (urlPtr == path && special_ftp_len > 3)
+ {
+ ++urlPtr;
+ ++urlPtr;
+ ++urlPtr;
+ }
+ // special case if we have reached the end
+ // to preserve the last /
+ if (*fwdPtr == '.' && *(fwdPtr+1) == '\0')
+ ++urlPtr;
+ }
+ else
+ {
+ // there are to much /.. in this path, just copy them instead.
+ // forward the urlPtr past the /.. and copying it
+
+ // However if we remember it is an url that starts with
+ // /%2F and urlPtr just points at the "F" of "/%2F" then do
+ // not overwrite it with the /, just copy .. and move forward
+ // urlPtr.
+ if (special_ftp_len > 3 && urlPtr == path+special_ftp_len-1)
+ ++urlPtr;
+ else
+ *urlPtr++ = *fwdPtr;
+ ++fwdPtr;
+ *urlPtr++ = *fwdPtr;
+ ++fwdPtr;
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+ else
+ {
+ // count the hierachie, but only if we do not have reached
+ // the root of some special urls with a special root marker
+ if (*fwdPtr == '/' && *(fwdPtr+1) != '.' &&
+ (special_ftp_len != 2 || *(fwdPtr+1) != '/'))
+ traversal++;
+ // copy the url incrementaly
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+
+ /*
+ * Now lets remove trailing . case
+ * /foo/foo1/. -> /foo/foo1/
+ */
+
+ if ((urlPtr > (path+1)) && (*(urlPtr-1) == '.') && (*(urlPtr-2) == '/'))
+ urlPtr--;
+
+ // Copy remaining stuff past the #?;
+ for (; *fwdPtr != '\0'; ++fwdPtr)
+ {
+ *urlPtr++ = *fwdPtr;
+ }
+ *urlPtr = '\0'; // terminate the url
+}
+
+nsresult
+net_ResolveRelativePath(const nsACString &relativePath,
+ const nsACString &basePath,
+ nsACString &result)
+{
+ nsAutoCString name;
+ nsAutoCString path(basePath);
+ bool needsDelim = false;
+
+ if ( !path.IsEmpty() ) {
+ char16_t last = path.Last();
+ needsDelim = !(last == '/');
+ }
+
+ nsACString::const_iterator beg, end;
+ relativePath.BeginReading(beg);
+ relativePath.EndReading(end);
+
+ bool stop = false;
+ char c;
+ for (; !stop; ++beg) {
+ c = (beg == end) ? '\0' : *beg;
+ //printf("%c [name=%s] [path=%s]\n", c, name.get(), path.get());
+ switch (c) {
+ case '\0':
+ case '#':
+ case '?':
+ stop = true;
+ MOZ_FALLTHROUGH;
+ case '/':
+ // delimiter found
+ if (name.EqualsLiteral("..")) {
+ // pop path
+ // If we already have the delim at end, then
+ // skip over that when searching for next one to the left
+ int32_t offset = path.Length() - (needsDelim ? 1 : 2);
+ // First check for errors
+ if (offset < 0 )
+ return NS_ERROR_MALFORMED_URI;
+ int32_t pos = path.RFind("/", false, offset);
+ if (pos >= 0)
+ path.Truncate(pos + 1);
+ else
+ path.Truncate();
+ }
+ else if (name.IsEmpty() || name.EqualsLiteral(".")) {
+ // do nothing
+ }
+ else {
+ // append name to path
+ if (needsDelim)
+ path += '/';
+ path += name;
+ needsDelim = true;
+ }
+ name.Truncate();
+ break;
+
+ default:
+ // append char to name
+ name += c;
+ }
+ }
+ // append anything left on relativePath (e.g. #..., ;..., ?...)
+ if (c != '\0')
+ path += Substring(--beg, end);
+
+ result = path;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// scheme fu
+//----------------------------------------------------------------------------
+
+static bool isAsciiAlpha(char c) {
+ return nsCRT::IsAsciiAlpha(c);
+}
+
+static bool
+net_IsValidSchemeChar(const char aChar)
+{
+ if (nsCRT::IsAsciiAlpha(aChar) || nsCRT::IsAsciiDigit(aChar) ||
+ aChar == '+' || aChar == '.' || aChar == '-') {
+ return true;
+ }
+ return false;
+}
+
+/* Extract URI-Scheme if possible */
+nsresult
+net_ExtractURLScheme(const nsACString &inURI,
+ nsACString& scheme)
+{
+ nsACString::const_iterator start, end;
+ inURI.BeginReading(start);
+ inURI.EndReading(end);
+
+ // Strip C0 and space from begining
+ while (start != end) {
+ if ((uint8_t) *start > 0x20) {
+ break;
+ }
+ start++;
+ }
+
+ Tokenizer p(Substring(start, end), "\r\n\t");
+ p.Record();
+ if (!p.CheckChar(isAsciiAlpha)) {
+ // First char must be alpha
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) {
+ // Skip valid scheme characters or \r\n\t
+ }
+
+ if (!p.CheckChar(':')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ p.Claim(scheme);
+ scheme.StripChars("\r\n\t");
+ return NS_OK;
+}
+
+bool
+net_IsValidScheme(const char *scheme, uint32_t schemeLen)
+{
+ // first char must be alpha
+ if (!nsCRT::IsAsciiAlpha(*scheme))
+ return false;
+
+ // nsCStrings may have embedded nulls -- reject those too
+ for (; schemeLen; ++scheme, --schemeLen) {
+ if (!(nsCRT::IsAsciiAlpha(*scheme) ||
+ nsCRT::IsAsciiDigit(*scheme) ||
+ *scheme == '+' ||
+ *scheme == '.' ||
+ *scheme == '-'))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+net_IsAbsoluteURL(const nsACString& uri)
+{
+ nsACString::const_iterator start, end;
+ uri.BeginReading(start);
+ uri.EndReading(end);
+
+ // Strip C0 and space from begining
+ while (start != end) {
+ if ((uint8_t) *start > 0x20) {
+ break;
+ }
+ start++;
+ }
+
+ Tokenizer p(Substring(start, end), "\r\n\t");
+
+ // First char must be alpha
+ if (!p.CheckChar(isAsciiAlpha)) {
+ return false;
+ }
+
+ while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) {
+ // Skip valid scheme characters or \r\n\t
+ }
+ if (!p.CheckChar(':')) {
+ return false;
+ }
+ p.SkipWhites();
+
+ if (!p.CheckChar('/')) {
+ return false;
+ }
+ p.SkipWhites();
+
+ if (p.CheckChar('/')) {
+ // aSpec is really absolute. Ignore aBaseURI in this case
+ return true;
+ }
+ return false;
+}
+
+void
+net_FilterURIString(const nsACString& input, nsACString& result)
+{
+ const char kCharsToStrip[] = "\r\n\t";
+
+ result.Truncate();
+
+ auto start = input.BeginReading();
+ auto end = input.EndReading();
+
+ // Trim off leading and trailing invalid chars.
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ auto newStart = std::find_if(start, end, charFilter);
+ auto newEnd = std::find_if(
+ std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(newStart)>(newStart),
+ charFilter).base();
+
+ // Check if chars need to be stripped.
+ auto itr = std::find_first_of(
+ newStart, newEnd, std::begin(kCharsToStrip), std::end(kCharsToStrip));
+ const bool needsStrip = itr != newEnd;
+
+ // Just use the passed in string rather than creating new copies if no
+ // changes are necessary.
+ if (newStart == start && newEnd == end && !needsStrip) {
+ result = input;
+ return;
+ }
+
+ result.Assign(Substring(newStart, newEnd));
+ if (needsStrip) {
+ result.StripChars(kCharsToStrip);
+ }
+}
+
+
+#if defined(XP_WIN)
+bool
+net_NormalizeFileURL(const nsACString &aURL, nsCString &aResultBuf)
+{
+ bool writing = false;
+
+ nsACString::const_iterator beginIter, endIter;
+ aURL.BeginReading(beginIter);
+ aURL.EndReading(endIter);
+
+ const char *s, *begin = beginIter.get();
+
+ for (s = begin; s != endIter.get(); ++s)
+ {
+ if (*s == '\\')
+ {
+ writing = true;
+ if (s > begin)
+ aResultBuf.Append(begin, s - begin);
+ aResultBuf += '/';
+ begin = s + 1;
+ }
+ }
+ if (writing && s > begin)
+ aResultBuf.Append(begin, s - begin);
+
+ return writing;
+}
+#endif
+
+//----------------------------------------------------------------------------
+// miscellaneous (i.e., stuff that should really be elsewhere)
+//----------------------------------------------------------------------------
+
+static inline
+void ToLower(char &c)
+{
+ if ((unsigned)(c - 'A') <= (unsigned)('Z' - 'A'))
+ c += 'a' - 'A';
+}
+
+void
+net_ToLowerCase(char *str, uint32_t length)
+{
+ for (char *end = str + length; str < end; ++str)
+ ToLower(*str);
+}
+
+void
+net_ToLowerCase(char *str)
+{
+ for (; *str; ++str)
+ ToLower(*str);
+}
+
+char *
+net_FindCharInSet(const char *iter, const char *stop, const char *set)
+{
+ for (; iter != stop && *iter; ++iter) {
+ for (const char *s = set; *s; ++s) {
+ if (*iter == *s)
+ return (char *) iter;
+ }
+ }
+ return (char *) iter;
+}
+
+char *
+net_FindCharNotInSet(const char *iter, const char *stop, const char *set)
+{
+repeat:
+ for (const char *s = set; *s; ++s) {
+ if (*iter == *s) {
+ if (++iter == stop)
+ break;
+ goto repeat;
+ }
+ }
+ return (char *) iter;
+}
+
+char *
+net_RFindCharNotInSet(const char *stop, const char *iter, const char *set)
+{
+ --iter;
+ --stop;
+
+ if (iter == stop)
+ return (char *) iter;
+
+repeat:
+ for (const char *s = set; *s; ++s) {
+ if (*iter == *s) {
+ if (--iter == stop)
+ break;
+ goto repeat;
+ }
+ }
+ return (char *) iter;
+}
+
+#define HTTP_LWS " \t"
+
+// Return the index of the closing quote of the string, if any
+static uint32_t
+net_FindStringEnd(const nsCString& flatStr,
+ uint32_t stringStart,
+ char stringDelim)
+{
+ NS_ASSERTION(stringStart < flatStr.Length() &&
+ flatStr.CharAt(stringStart) == stringDelim &&
+ (stringDelim == '"' || stringDelim == '\''),
+ "Invalid stringStart");
+
+ const char set[] = { stringDelim, '\\', '\0' };
+ do {
+ // stringStart points to either the start quote or the last
+ // escaped char (the char following a '\\')
+
+ // Write to searchStart here, so that when we get back to the
+ // top of the loop right outside this one we search from the
+ // right place.
+ uint32_t stringEnd = flatStr.FindCharInSet(set, stringStart + 1);
+ if (stringEnd == uint32_t(kNotFound))
+ return flatStr.Length();
+
+ if (flatStr.CharAt(stringEnd) == '\\') {
+ // Hit a backslash-escaped char. Need to skip over it.
+ stringStart = stringEnd + 1;
+ if (stringStart == flatStr.Length())
+ return stringStart;
+
+ // Go back to looking for the next escape or the string end
+ continue;
+ }
+
+ return stringEnd;
+
+ } while (true);
+
+ NS_NOTREACHED("How did we get here?");
+ return flatStr.Length();
+}
+
+
+static uint32_t
+net_FindMediaDelimiter(const nsCString& flatStr,
+ uint32_t searchStart,
+ char delimiter)
+{
+ do {
+ // searchStart points to the spot from which we should start looking
+ // for the delimiter.
+ const char delimStr[] = { delimiter, '"', '\0' };
+ uint32_t curDelimPos = flatStr.FindCharInSet(delimStr, searchStart);
+ if (curDelimPos == uint32_t(kNotFound))
+ return flatStr.Length();
+
+ char ch = flatStr.CharAt(curDelimPos);
+ if (ch == delimiter) {
+ // Found delimiter
+ return curDelimPos;
+ }
+
+ // We hit the start of a quoted string. Look for its end.
+ searchStart = net_FindStringEnd(flatStr, curDelimPos, ch);
+ if (searchStart == flatStr.Length())
+ return searchStart;
+
+ ++searchStart;
+
+ // searchStart now points to the first char after the end of the
+ // string, so just go back to the top of the loop and look for
+ // |delimiter| again.
+ } while (true);
+
+ NS_NOTREACHED("How did we get here?");
+ return flatStr.Length();
+}
+
+// aOffset should be added to aCharsetStart and aCharsetEnd if this
+// function sets them.
+static void
+net_ParseMediaType(const nsACString &aMediaTypeStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ int32_t aOffset,
+ bool *aHadCharset,
+ int32_t *aCharsetStart,
+ int32_t *aCharsetEnd,
+ bool aStrict)
+{
+ const nsCString& flatStr = PromiseFlatCString(aMediaTypeStr);
+ const char* start = flatStr.get();
+ const char* end = start + flatStr.Length();
+
+ // Trim LWS leading and trailing whitespace from type. We include '(' in
+ // the trailing trim set to catch media-type comments, which are not at all
+ // standard, but may occur in rare cases.
+ const char* type = net_FindCharNotInSet(start, end, HTTP_LWS);
+ const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";(");
+
+ const char* charset = "";
+ const char* charsetEnd = charset;
+ int32_t charsetParamStart = 0;
+ int32_t charsetParamEnd = 0;
+
+ uint32_t consumed = typeEnd - type;
+
+ // Iterate over parameters
+ bool typeHasCharset = false;
+ uint32_t paramStart = flatStr.FindChar(';', typeEnd - start);
+ if (paramStart != uint32_t(kNotFound)) {
+ // We have parameters. Iterate over them.
+ uint32_t curParamStart = paramStart + 1;
+ do {
+ uint32_t curParamEnd =
+ net_FindMediaDelimiter(flatStr, curParamStart, ';');
+
+ const char* paramName = net_FindCharNotInSet(start + curParamStart,
+ start + curParamEnd,
+ HTTP_LWS);
+ static const char charsetStr[] = "charset=";
+ if (PL_strncasecmp(paramName, charsetStr,
+ sizeof(charsetStr) - 1) == 0) {
+ charset = paramName + sizeof(charsetStr) - 1;
+ charsetEnd = start + curParamEnd;
+ typeHasCharset = true;
+ charsetParamStart = curParamStart - 1;
+ charsetParamEnd = curParamEnd;
+ }
+
+ consumed = curParamEnd;
+ curParamStart = curParamEnd + 1;
+ } while (curParamStart < flatStr.Length());
+ }
+
+ bool charsetNeedsQuotedStringUnescaping = false;
+ if (typeHasCharset) {
+ // Trim LWS leading and trailing whitespace from charset. We include
+ // '(' in the trailing trim set to catch media-type comments, which are
+ // not at all standard, but may occur in rare cases.
+ charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS);
+ if (*charset == '"') {
+ charsetNeedsQuotedStringUnescaping = true;
+ charsetEnd =
+ start + net_FindStringEnd(flatStr, charset - start, *charset);
+ charset++;
+ NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing");
+ } else {
+ charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";(");
+ }
+ }
+
+ // if the server sent "*/*", it is meaningless, so do not store it.
+ // also, if type is the same as aContentType, then just update the
+ // charset. however, if charset is empty and aContentType hasn't
+ // changed, then don't wipe-out an existing aContentCharset. We
+ // also want to reject a mime-type if it does not include a slash.
+ // some servers give junk after the charset parameter, which may
+ // include a comma, so this check makes us a bit more tolerant.
+
+ if (type != typeEnd &&
+ memchr(type, '/', typeEnd - type) != nullptr &&
+ (aStrict ? (net_FindCharNotInSet(start + consumed, end, HTTP_LWS) == end) :
+ (strncmp(type, "*/*", typeEnd - type) != 0))) {
+ // Common case here is that aContentType is empty
+ bool eq = !aContentType.IsEmpty() &&
+ aContentType.Equals(Substring(type, typeEnd),
+ nsCaseInsensitiveCStringComparator());
+ if (!eq) {
+ aContentType.Assign(type, typeEnd - type);
+ ToLowerCase(aContentType);
+ }
+
+ if ((!eq && *aHadCharset) || typeHasCharset) {
+ *aHadCharset = true;
+ if (charsetNeedsQuotedStringUnescaping) {
+ // parameters using the "quoted-string" syntax need
+ // backslash-escapes to be unescaped (see RFC 2616 Section 2.2)
+ aContentCharset.Truncate();
+ for (const char *c = charset; c != charsetEnd; c++) {
+ if (*c == '\\' && c + 1 != charsetEnd) {
+ // eat escape
+ c++;
+ }
+ aContentCharset.Append(*c);
+ }
+ }
+ else {
+ aContentCharset.Assign(charset, charsetEnd - charset);
+ }
+ if (typeHasCharset) {
+ *aCharsetStart = charsetParamStart + aOffset;
+ *aCharsetEnd = charsetParamEnd + aOffset;
+ }
+ }
+ // Only set a new charset position if this is a different type
+ // from the last one we had and it doesn't already have a
+ // charset param. If this is the same type, we probably want
+ // to leave the charset position on its first occurrence.
+ if (!eq && !typeHasCharset) {
+ int32_t charsetStart = int32_t(paramStart);
+ if (charsetStart == kNotFound)
+ charsetStart = flatStr.Length();
+
+ *aCharsetEnd = *aCharsetStart = charsetStart + aOffset;
+ }
+ }
+}
+
+#undef HTTP_LWS
+
+void
+net_ParseContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool *aHadCharset)
+{
+ int32_t dummy1, dummy2;
+ net_ParseContentType(aHeaderStr, aContentType, aContentCharset,
+ aHadCharset, &dummy1, &dummy2);
+}
+
+void
+net_ParseContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool *aHadCharset,
+ int32_t *aCharsetStart,
+ int32_t *aCharsetEnd)
+{
+ //
+ // Augmented BNF (from RFC 2616 section 3.7):
+ //
+ // header-value = media-type *( LWS "," LWS media-type )
+ // media-type = type "/" subtype *( LWS ";" LWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = attribute "=" value
+ // attribute = token
+ // value = token | quoted-string
+ //
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html, text/html
+ // text/html,text/html; charset=ISO-8859-1
+ // text/html,text/html; charset="ISO-8859-1"
+ // text/html;charset=ISO-8859-1, text/html
+ // text/html;charset='ISO-8859-1', text/html
+ // application/octet-stream
+ //
+
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // iterate over media-types. Note that ',' characters can happen
+ // inside quoted strings, so we need to watch out for that.
+ uint32_t curTypeStart = 0;
+ do {
+ // curTypeStart points to the start of the current media-type. We want
+ // to look for its end.
+ uint32_t curTypeEnd =
+ net_FindMediaDelimiter(flatStr, curTypeStart, ',');
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ net_ParseMediaType(Substring(flatStr, curTypeStart,
+ curTypeEnd - curTypeStart),
+ aContentType, aContentCharset, curTypeStart,
+ aHadCharset, aCharsetStart, aCharsetEnd, false);
+
+ // And let's move on to the next media-type
+ curTypeStart = curTypeEnd + 1;
+ } while (curTypeStart < flatStr.Length());
+}
+
+void
+net_ParseRequestContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool *aHadCharset)
+{
+ //
+ // Augmented BNF (from RFC 7231 section 3.1.1.1):
+ //
+ // media-type = type "/" subtype *( OWS ";" OWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = token "=" ( token / quoted-string )
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html; charset=ISO-8859-1
+ // text/html; charset="ISO-8859-1"
+ // application/octet-stream
+ //
+
+ aContentType.Truncate();
+ aContentCharset.Truncate();
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ nsAutoCString contentType, contentCharset;
+ bool hadCharset = false;
+ int32_t dummy1, dummy2;
+ uint32_t typeEnd = net_FindMediaDelimiter(flatStr, 0, ',');
+ if (typeEnd != flatStr.Length()) {
+ // We have some stuff left at the end, so this is not a valid
+ // request Content-Type header.
+ return;
+ }
+ net_ParseMediaType(flatStr, contentType, contentCharset, 0,
+ &hadCharset, &dummy1, &dummy2, true);
+
+ aContentType = contentType;
+ aContentCharset = contentCharset;
+ *aHadCharset = hadCharset;
+}
+
+bool
+net_IsValidHostName(const nsCSubstring &host)
+{
+ const char *end = host.EndReading();
+ // Use explicit whitelists to select which characters we are
+ // willing to send to lower-level DNS logic. This is more
+ // self-documenting, and can also be slightly faster than the
+ // blacklist approach, since DNS names are the common case, and
+ // the commonest characters will tend to be near the start of
+ // the list.
+
+ // Whitelist for DNS names (RFC 1035) with extra characters added
+ // for pragmatic reasons "$+_"
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=355181#c2
+ if (net_FindCharNotInSet(host.BeginReading(), end,
+ "abcdefghijklmnopqrstuvwxyz"
+ ".-0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end)
+ return true;
+
+ // Might be a valid IPv6 link-local address containing a percent sign
+ nsAutoCString strhost(host);
+ PRNetAddr addr;
+ return PR_StringToNetAddr(strhost.get(), &addr) == PR_SUCCESS;
+}
+
+bool
+net_IsValidIPv4Addr(const char *addr, int32_t addrLen)
+{
+ RangedPtr<const char> p(addr, addrLen);
+
+ int32_t octet = -1; // means no digit yet
+ int32_t dotCount = 0; // number of dots in the address
+
+ for (; addrLen; ++p, --addrLen) {
+ if (*p == '.') {
+ dotCount++;
+ if (octet == -1) {
+ // invalid octet
+ return false;
+ }
+ octet = -1;
+ } else if (*p >= '0' && *p <='9') {
+ if (octet == 0) {
+ // leading 0 is not allowed
+ return false;
+ } else if (octet == -1) {
+ octet = *p - '0';
+ } else {
+ octet *= 10;
+ octet += *p - '0';
+ if (octet > 255)
+ return false;
+ }
+ } else {
+ // invalid character
+ return false;
+ }
+ }
+
+ return (dotCount == 3 && octet != -1);
+}
+
+bool
+net_IsValidIPv6Addr(const char *addr, int32_t addrLen)
+{
+ RangedPtr<const char> p(addr, addrLen);
+
+ int32_t digits = 0; // number of digits in current block
+ int32_t colons = 0; // number of colons in a row during parsing
+ int32_t blocks = 0; // number of hexadecimal blocks
+ bool haveZeros = false; // true if double colon is present in the address
+
+ for (; addrLen; ++p, --addrLen) {
+ if (*p == ':') {
+ if (colons == 0) {
+ if (digits != 0) {
+ digits = 0;
+ blocks++;
+ }
+ } else if (colons == 1) {
+ if (haveZeros)
+ return false; // only one occurrence is allowed
+ haveZeros = true;
+ } else {
+ // too many colons in a row
+ return false;
+ }
+ colons++;
+ } else if ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') ||
+ (*p >= 'A' && *p <= 'F')) {
+ if (colons == 1 && blocks == 0) // starts with a single colon
+ return false;
+ if (digits == 4) // too many digits
+ return false;
+ colons = 0;
+ digits++;
+ } else if (*p == '.') {
+ // check valid IPv4 from the beginning of the last block
+ if (!net_IsValidIPv4Addr(p.get() - digits, addrLen + digits))
+ return false;
+ return (haveZeros && blocks < 6) || (!haveZeros && blocks == 6);
+ } else {
+ // invalid character
+ return false;
+ }
+ }
+
+ if (colons == 1) // ends with a single colon
+ return false;
+
+ if (digits) // there is a block at the end
+ blocks++;
+
+ return (haveZeros && blocks < 8) || (!haveZeros && blocks == 8);
+}
diff --git a/netwerk/base/nsURLHelper.h b/netwerk/base/nsURLHelper.h
new file mode 100644
index 0000000000..f30814c253
--- /dev/null
+++ b/netwerk/base/nsURLHelper.h
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURLHelper_h__
+#define nsURLHelper_h__
+
+#include "nsString.h"
+
+class nsIFile;
+class nsIURLParser;
+
+enum netCoalesceFlags
+{
+ NET_COALESCE_NORMAL = 0,
+
+ /**
+ * retains /../ that reach above dir root (useful for FTP
+ * servers in which the root of the FTP URL is not necessarily
+ * the root of the FTP filesystem).
+ */
+ NET_COALESCE_ALLOW_RELATIVE_ROOT = 1<<0,
+
+ /**
+ * recognizes /%2F and // as markers for the root directory
+ * and handles them properly.
+ */
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT = 1<<1
+};
+
+//----------------------------------------------------------------------------
+// This module contains some private helper functions related to URL parsing.
+//----------------------------------------------------------------------------
+
+/* shutdown frees URL parser */
+void net_ShutdownURLHelper();
+#ifdef XP_MACOSX
+void net_ShutdownURLHelperOSX();
+#endif
+
+/* access URL parsers */
+nsIURLParser * net_GetAuthURLParser();
+nsIURLParser * net_GetNoAuthURLParser();
+nsIURLParser * net_GetStdURLParser();
+
+/* convert between nsIFile and file:// URL spec
+ * net_GetURLSpecFromFile does an extra stat, so callers should
+ * avoid it if possible in favor of net_GetURLSpecFromActualFile
+ * and net_GetURLSpecFromDir */
+nsresult net_GetURLSpecFromFile(nsIFile *, nsACString &);
+nsresult net_GetURLSpecFromDir(nsIFile *, nsACString &);
+nsresult net_GetURLSpecFromActualFile(nsIFile *, nsACString &);
+nsresult net_GetFileFromURLSpec(const nsACString &, nsIFile **);
+
+/* extract file path components from file:// URL */
+nsresult net_ParseFileURL(const nsACString &inURL,
+ nsACString &outDirectory,
+ nsACString &outFileBaseName,
+ nsACString &outFileExtension);
+
+/* handle .. in dirs while resolving URLs (path is UTF-8) */
+void net_CoalesceDirs(netCoalesceFlags flags, char* path);
+
+/**
+ * Resolves a relative path string containing "." and ".."
+ * with respect to a base path (assumed to already be resolved).
+ * For example, resolving "../../foo/./bar/../baz.html" w.r.t.
+ * "/a/b/c/d/e/" yields "/a/b/c/foo/baz.html". Attempting to
+ * ascend above the base results in the NS_ERROR_MALFORMED_URI
+ * exception. If basePath is null, it treats it as "/".
+ *
+ * @param relativePath a relative URI
+ * @param basePath a base URI
+ *
+ * @return a new string, representing canonical uri
+ */
+nsresult net_ResolveRelativePath(const nsACString &relativePath,
+ const nsACString &basePath,
+ nsACString &result);
+
+/**
+ * Check if a URL is absolute
+ *
+ * @param inURL URL spec
+ * @return true if the given spec represents an absolute URL
+ */
+bool net_IsAbsoluteURL(const nsACString& inURL);
+
+/**
+ * Extract URI-Scheme if possible
+ *
+ * @param inURI URI spec
+ * @param scheme scheme copied to this buffer on return (may be null)
+ */
+nsresult net_ExtractURLScheme(const nsACString &inURI,
+ nsACString &scheme);
+
+/* check that the given scheme conforms to RFC 2396 */
+bool net_IsValidScheme(const char *scheme, uint32_t schemeLen);
+
+inline bool net_IsValidScheme(const nsAFlatCString &scheme)
+{
+ return net_IsValidScheme(scheme.get(), scheme.Length());
+}
+
+/**
+ * This function strips out all C0 controls and space at the beginning and end
+ * of the URL and filters out \r, \n, \t from the middle of the URL. This makes
+ * it safe to call on things like javascript: urls or data: urls, where we may
+ * in fact run into whitespace that is not properly encoded.
+ *
+ * @param input the URL spec we want to filter
+ * @param result the out param to write to if filtering happens
+ */
+void net_FilterURIString(const nsACString& input, nsACString& result);
+
+#if defined(XP_WIN)
+/**
+ * On Win32 and OS/2 system's a back-slash in a file:// URL is equivalent to a
+ * forward-slash. This function maps any back-slashes to forward-slashes.
+ *
+ * @param aURL
+ * The URL string to normalize (UTF-8 encoded). This can be a
+ * relative URL segment.
+ * @param aResultBuf
+ * The resulting string is appended to this string. If the input URL
+ * is already normalized, then aResultBuf is unchanged.
+ *
+ * @returns false if aURL is already normalized. Otherwise, returns true.
+ */
+bool net_NormalizeFileURL(const nsACString &aURL,
+ nsCString &aResultBuf);
+#endif
+
+/*****************************************************************************
+ * generic string routines follow (XXX move to someplace more generic).
+ */
+
+/* convert to lower case */
+void net_ToLowerCase(char* str, uint32_t length);
+void net_ToLowerCase(char* str);
+
+/**
+ * returns pointer to first character of |str| in the given set. if not found,
+ * then |end| is returned. stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char * net_FindCharInSet(const char *str, const char *end, const char *set);
+
+/**
+ * returns pointer to first character of |str| NOT in the given set. if all
+ * characters are in the given set, then |end| is returned. if '\0' is not
+ * included in |set|, then stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char * net_FindCharNotInSet(const char *str, const char *end, const char *set);
+
+/**
+ * returns pointer to last character of |str| NOT in the given set. if all
+ * characters are in the given set, then |str - 1| is returned.
+ */
+char * net_RFindCharNotInSet(const char *str, const char *end, const char *set);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ *
+ * This parsing is suitable for HTTP request. Use net_ParseContentType
+ * for parsing this header in HTTP responses.
+ */
+void net_ParseRequestContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool* aHadCharset);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ */
+void net_ParseContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool* aHadCharset);
+/**
+ * As above, but also returns the start and end indexes for the charset
+ * parameter in aHeaderStr. These are indices for the entire parameter, NOT
+ * just the value. If there is "effectively" no charset parameter (e.g. if an
+ * earlier type with one is overridden by a later type without one),
+ * *aHadCharset will be true but *aCharsetStart will be set to -1. Note that
+ * it's possible to have aContentCharset empty and *aHadCharset true when
+ * *aCharsetStart is nonnegative; this corresponds to charset="".
+ */
+void net_ParseContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool *aHadCharset,
+ int32_t *aCharsetStart,
+ int32_t *aCharsetEnd);
+
+/* inline versions */
+
+/* remember the 64-bit platforms ;-) */
+#define NET_MAX_ADDRESS (((char*)0)-1)
+
+inline char *net_FindCharInSet(const char *str, const char *set)
+{
+ return net_FindCharInSet(str, NET_MAX_ADDRESS, set);
+}
+inline char *net_FindCharNotInSet(const char *str, const char *set)
+{
+ return net_FindCharNotInSet(str, NET_MAX_ADDRESS, set);
+}
+inline char *net_RFindCharNotInSet(const char *str, const char *set)
+{
+ return net_RFindCharNotInSet(str, str + strlen(str), set);
+}
+
+/**
+ * This function returns true if the given hostname does not include any
+ * restricted characters. Otherwise, false is returned.
+ */
+bool net_IsValidHostName(const nsCSubstring &host);
+
+/**
+ * Checks whether the IPv4 address is valid according to RFC 3986 section 3.2.2.
+ */
+bool net_IsValidIPv4Addr(const char *addr, int32_t addrLen);
+
+/**
+ * Checks whether the IPv6 address is valid according to RFC 3986 section 3.2.2.
+ */
+bool net_IsValidIPv6Addr(const char *addr, int32_t addrLen);
+
+
+/**
+ * Returns the max length of a URL. The default is 1048576 (1 MB).
+ * Can be changed by pref "network.standard-url.max-length"
+ */
+int32_t net_GetURLMaxLength();
+
+#endif // !nsURLHelper_h__
diff --git a/netwerk/base/nsURLHelperOSX.cpp b/netwerk/base/nsURLHelperOSX.cpp
new file mode 100644
index 0000000000..bcc0b257fb
--- /dev/null
+++ b/netwerk/base/nsURLHelperOSX.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Mac OS X-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsReadableUtils.h"
+#include <Carbon/Carbon.h>
+
+static nsTArray<nsCString> *gVolumeList = nullptr;
+
+static bool pathBeginsWithVolName(const nsACString& path, nsACString& firstPathComponent)
+{
+ // Return whether the 1st path component in path (escaped) is equal to the name
+ // of a mounted volume. Return the 1st path component (unescaped) in any case.
+ // This needs to be done as quickly as possible, so we cache a list of volume names.
+ // XXX Register an event handler to detect drives being mounted/unmounted?
+
+ if (!gVolumeList) {
+ gVolumeList = new nsTArray<nsCString>;
+ if (!gVolumeList) {
+ return false; // out of memory
+ }
+ }
+
+ // Cache a list of volume names
+ if (!gVolumeList->Length()) {
+ OSErr err;
+ ItemCount volumeIndex = 1;
+
+ do {
+ HFSUniStr255 volName;
+ FSRef rootDirectory;
+ err = ::FSGetVolumeInfo(0, volumeIndex, nullptr, kFSVolInfoNone, nullptr,
+ &volName, &rootDirectory);
+ if (err == noErr) {
+ NS_ConvertUTF16toUTF8 volNameStr(Substring((char16_t *)volName.unicode,
+ (char16_t *)volName.unicode + volName.length));
+ gVolumeList->AppendElement(volNameStr);
+ volumeIndex++;
+ }
+ } while (err == noErr);
+ }
+
+ // Extract the first component of the path
+ nsACString::const_iterator start;
+ path.BeginReading(start);
+ start.advance(1); // path begins with '/'
+ nsACString::const_iterator directory_end;
+ path.EndReading(directory_end);
+ nsACString::const_iterator component_end(start);
+ FindCharInReadable('/', component_end, directory_end);
+
+ nsAutoCString flatComponent((Substring(start, component_end)));
+ NS_UnescapeURL(flatComponent);
+ int32_t foundIndex = gVolumeList->IndexOf(flatComponent);
+ firstPathComponent = flatComponent;
+ return (foundIndex != -1);
+}
+
+void
+net_ShutdownURLHelperOSX()
+{
+ delete gVolumeList;
+ gVolumeList = nullptr;
+}
+
+static nsresult convertHFSPathtoPOSIX(const nsACString& hfsPath, nsACString& posixPath)
+{
+ // Use CFURL to do the conversion. We don't want to do this by simply
+ // using SwapSlashColon - we need the charset mapped from MacRoman
+ // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject to change)
+ // prepended if the path is not on the boot drive.
+
+ CFStringRef pathStrRef = CFStringCreateWithCString(nullptr,
+ PromiseFlatCString(hfsPath).get(),
+ kCFStringEncodingMacRoman);
+ if (!pathStrRef)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr,
+ pathStrRef, kCFURLHFSPathStyle, true);
+ if (urlRef) {
+ UInt8 pathBuf[PATH_MAX];
+ if (CFURLGetFileSystemRepresentation(urlRef, true, pathBuf, sizeof(pathBuf))) {
+ posixPath = (char *)pathBuf;
+ rv = NS_OK;
+ }
+ }
+ CFRelease(pathStrRef);
+ if (urlRef)
+ CFRelease(urlRef);
+ return rv;
+}
+
+static void SwapSlashColon(char *s)
+{
+ while (*s) {
+ if (*s == '/')
+ *s = ':';
+ else if (*s == ':')
+ *s = '/';
+ s++;
+ }
+}
+
+nsresult
+net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result)
+{
+ // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp
+
+ nsresult rv;
+ nsAutoCString ePath;
+
+ // construct URL spec from native file path
+ rv = aFile->GetNativePath(ePath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString escPath;
+ NS_NAMED_LITERAL_CSTRING(prefix, "file://");
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), ePath.Length(), esc_Directory+esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result)
+{
+ // NOTE: See also the implementation in nsURLHelperUnix.cpp
+ // This matches it except for the HFS path handling.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+ bool bHFSPath = false;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory|esc_AlwaysCopy, path);
+
+ // The canonical form of file URLs on OSX use POSIX paths:
+ // file:///path-name.
+ // But, we still encounter file URLs that use HFS paths:
+ // file:///volume-name/path-name
+ // Determine that here and normalize HFS paths to POSIX.
+ nsAutoCString possibleVolName;
+ if (pathBeginsWithVolName(directory, possibleVolName)) {
+ // Though we know it begins with a volume name, it could still
+ // be a valid POSIX path if the boot drive is named "Mac HD"
+ // and there is a directory "Mac HD" at its root. If such a
+ // directory doesn't exist, we'll assume this is an HFS path.
+ FSRef testRef;
+ possibleVolName.Insert("/", 0);
+ if (::FSPathMakeRef((UInt8*)possibleVolName.get(), &testRef, nullptr) != noErr)
+ bHFSPath = true;
+ }
+
+ if (bHFSPath) {
+ // "%2F"s need to become slashes, while all other slashes need to
+ // become colons. If we start out by changing "%2F"s to colons, we
+ // can reply on SwapSlashColon() to do what we need
+ path.ReplaceSubstring("%2F", ":");
+ path.Cut(0, 1); // directory begins with '/'
+ SwapSlashColon((char *)path.get());
+ // At this point, path is an HFS path made using the same
+ // algorithm as nsURLHelperMac. We'll convert to POSIX below.
+ }
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName|esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension|esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get()))
+ return NS_ERROR_FILE_INVALID_PATH;
+
+ if (bHFSPath)
+ convertHFSPathtoPOSIX(path, path);
+
+ // assuming path is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLHelperUnix.cpp b/netwerk/base/nsURLHelperUnix.cpp
new file mode 100644
index 0000000000..5b36770d75
--- /dev/null
+++ b/netwerk/base/nsURLHelperUnix.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Unix-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsNativeCharsetUtils.h"
+
+nsresult
+net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result)
+{
+ nsresult rv;
+ nsAutoCString nativePath, ePath;
+ nsAutoString path;
+
+ rv = aFile->GetNativePath(nativePath);
+ if (NS_FAILED(rv)) return rv;
+
+ // Convert to unicode and back to check correct conversion to native charset
+ NS_CopyNativeToUnicode(nativePath, path);
+ NS_CopyUnicodeToNative(path, ePath);
+
+ // Use UTF8 version if conversion was successful
+ if (nativePath == ePath)
+ CopyUTF16toUTF8(path, ePath);
+ else
+ ePath = nativePath;
+
+ nsAutoCString escPath;
+ NS_NAMED_LITERAL_CSTRING(prefix, "file://");
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), -1, esc_Directory+esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result)
+{
+ // NOTE: See also the implementation in nsURLHelperOSX.cpp,
+ // which is based on this.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!directory.IsEmpty()) {
+ rv = NS_EscapeURL(directory, esc_Directory|esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ if (!fileBaseName.IsEmpty()) {
+ rv = NS_EscapeURL(fileBaseName, esc_FileBaseName|esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ rv = NS_EscapeURL(fileExtension, esc_FileExtension|esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get()))
+ return NS_ERROR_FILE_INVALID_PATH;
+
+ if (IsUTF8(path)) {
+ // speed up the start-up where UTF-8 is the native charset
+ // (e.g. on recent Linux distributions)
+ if (NS_IsNativeUTF8())
+ rv = localFile->InitWithNativePath(path);
+ else
+ rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path));
+ // XXX In rare cases, a valid UTF-8 string can be valid as a native
+ // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x).
+ // However, the chance is very low that a meaningful word in a legacy
+ // encoding is valid as UTF-8.
+ }
+ else
+ // if path is not in UTF-8, assume it is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLHelperWin.cpp b/netwerk/base/nsURLHelperWin.cpp
new file mode 100644
index 0000000000..4ec46db4ec
--- /dev/null
+++ b/netwerk/base/nsURLHelperWin.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Windows-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include <windows.h>
+
+nsresult
+net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result)
+{
+ nsresult rv;
+ nsAutoString path;
+
+ // construct URL spec from file path
+ rv = aFile->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // Replace \ with / to convert to an url
+ path.ReplaceChar(char16_t(0x5Cu), char16_t(0x2Fu));
+
+ nsAutoCString escPath;
+
+ // Windows Desktop paths begin with a drive letter, so need an 'extra'
+ // slash at the begining
+ // C:\Windows => file:///C:/Windows
+ NS_NAMED_LITERAL_CSTRING(prefix, "file:///");
+
+ // Escape the path with the directory mask
+ NS_ConvertUTF16toUTF8 ePath(path);
+ if (NS_EscapeURL(ePath.get(), -1, esc_Directory+esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result)
+{
+ nsresult rv;
+
+ if (aURL.Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsCOMPtr<nsIFile> localFile(
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Only nsIFile supported right now");
+ return rv;
+ }
+
+ localFile->SetFollowLinks(true);
+
+ const nsACString *specPtr;
+
+ nsAutoCString buf;
+ if (net_NormalizeFileURL(aURL, buf))
+ specPtr = &buf;
+ else
+ specPtr = &aURL;
+
+ nsAutoCString directory, fileBaseName, fileExtension;
+
+ rv = net_ParseFileURL(*specPtr, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString path;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory|esc_AlwaysCopy, path);
+ if (path.Length() > 2 && path.CharAt(2) == '|')
+ path.SetCharAt(':', 2);
+ path.ReplaceChar('/', '\\');
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName|esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension|esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get()))
+ return NS_ERROR_FILE_INVALID_PATH;
+
+ // remove leading '\'
+ if (path.CharAt(0) == '\\')
+ path.Cut(0, 1);
+
+ if (IsUTF8(path))
+ rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path));
+ // XXX In rare cases, a valid UTF-8 string can be valid as a native
+ // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x).
+ // However, the chance is very low that a meaningful word in a legacy
+ // encoding is valid as UTF-8.
+ else
+ // if path is not in UTF-8, assume it is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLParsers.cpp b/netwerk/base/nsURLParsers.cpp
new file mode 100644
index 0000000000..b75ee0c4d4
--- /dev/null
+++ b/netwerk/base/nsURLParsers.cpp
@@ -0,0 +1,702 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "mozilla/RangedPtr.h"
+
+#include "nsURLParsers.h"
+#include "nsURLHelper.h"
+#include "nsString.h"
+#include "nsCRT.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+
+static uint32_t
+CountConsecutiveSlashes(const char *str, int32_t len)
+{
+ RangedPtr<const char> p(str, len);
+ uint32_t count = 0;
+ while (len-- && *p++ == '/') ++count;
+ return count;
+}
+
+//----------------------------------------------------------------------------
+// nsBaseURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsAuthURLParser, nsIURLParser)
+NS_IMPL_ISUPPORTS(nsNoAuthURLParser, nsIURLParser)
+
+#define SET_RESULT(component, pos, len) \
+ PR_BEGIN_MACRO \
+ if (component ## Pos) \
+ *component ## Pos = uint32_t(pos); \
+ if (component ## Len) \
+ *component ## Len = int32_t(len); \
+ PR_END_MACRO
+
+#define OFFSET_RESULT(component, offset) \
+ PR_BEGIN_MACRO \
+ if (component ## Pos) \
+ *component ## Pos += offset; \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseURL(const char *spec, int32_t specLen,
+ uint32_t *schemePos, int32_t *schemeLen,
+ uint32_t *authorityPos, int32_t *authorityLen,
+ uint32_t *pathPos, int32_t *pathLen)
+{
+ if (NS_WARN_IF(!spec)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (specLen < 0)
+ specLen = strlen(spec);
+
+ const char *stop = nullptr;
+ const char *colon = nullptr;
+ const char *slash = nullptr;
+ const char *p = spec;
+ uint32_t offset = 0;
+ int32_t len = specLen;
+
+ // skip leading whitespace
+ while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') {
+ spec++;
+ specLen--;
+ offset++;
+
+ p++;
+ len--;
+ }
+
+ for (; len && *p && !colon && !slash; ++p, --len) {
+ switch (*p) {
+ case ':':
+ if (!colon)
+ colon = p;
+ break;
+ case '/': // start of filepath
+ case '?': // start of query
+ case '#': // start of ref
+ if (!slash)
+ slash = p;
+ break;
+ case '@': // username@hostname
+ case '[': // start of IPv6 address literal
+ if (!stop)
+ stop = p;
+ break;
+ }
+ }
+ // disregard the first colon if it follows an '@' or a '['
+ if (colon && stop && colon > stop)
+ colon = nullptr;
+
+ // if the spec only contained whitespace ...
+ if (specLen == 0) {
+ SET_RESULT(scheme, 0, -1);
+ SET_RESULT(authority, 0, 0);
+ SET_RESULT(path, 0, 0);
+ return NS_OK;
+ }
+
+ // ignore trailing whitespace and control characters
+ for (p = spec + specLen - 1; ((unsigned char) *p <= ' ') && (p != spec); --p)
+ ;
+
+ specLen = p - spec + 1;
+
+ if (colon && (colon < slash || !slash)) {
+ //
+ // spec = <scheme>:/<the-rest>
+ //
+ // or
+ //
+ // spec = <scheme>:<authority>
+ // spec = <scheme>:<path-no-slashes>
+ //
+ if (!net_IsValidScheme(spec, colon - spec) || (*(colon+1) == ':')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ SET_RESULT(scheme, offset, colon - spec);
+ if (authorityLen || pathLen) {
+ uint32_t schemeLen = colon + 1 - spec;
+ offset += schemeLen;
+ ParseAfterScheme(colon + 1, specLen - schemeLen,
+ authorityPos, authorityLen,
+ pathPos, pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ }
+ else {
+ //
+ // spec = <authority-no-port-or-password>/<path>
+ // spec = <path>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>/<path-with-colon>
+ // spec = <path-with-colon>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>
+ // spec = <path-no-slashes-or-colon>
+ //
+ SET_RESULT(scheme, 0, -1);
+ if (authorityLen || pathLen) {
+ ParseAfterScheme(spec, specLen,
+ authorityPos, authorityLen,
+ pathPos, pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0)
+ authLen = strlen(auth);
+
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, authLen);
+ if (port)
+ *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseUserInfo(const char *userinfo, int32_t userinfoLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen)
+{
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseServerInfo(const char *serverinfo, int32_t serverinfoLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ SET_RESULT(hostname, 0, -1);
+ if (port)
+ *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParsePath(const char *path, int32_t pathLen,
+ uint32_t *filepathPos, int32_t *filepathLen,
+ uint32_t *queryPos, int32_t *queryLen,
+ uint32_t *refPos, int32_t *refLen)
+{
+ if (NS_WARN_IF(!path)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (pathLen < 0)
+ pathLen = strlen(path);
+
+ // path = [/]<segment1>/<segment2>/<...>/<segmentN>?<query>#<ref>
+
+ // XXX PL_strnpbrk would be nice, but it's buggy
+
+ // search for first occurrence of either ? or #
+ const char *query_beg = 0, *query_end = 0;
+ const char *ref_beg = 0;
+ const char *p = 0;
+ for (p = path; p < path + pathLen; ++p) {
+ // only match the query string if it precedes the reference fragment
+ if (!ref_beg && !query_beg && *p == '?')
+ query_beg = p + 1;
+ else if (*p == '#') {
+ ref_beg = p + 1;
+ if (query_beg)
+ query_end = p;
+ break;
+ }
+ }
+
+ if (query_beg) {
+ if (query_end)
+ SET_RESULT(query, query_beg - path, query_end - query_beg);
+ else
+ SET_RESULT(query, query_beg - path, pathLen - (query_beg - path));
+ }
+ else
+ SET_RESULT(query, 0, -1);
+
+ if (ref_beg)
+ SET_RESULT(ref, ref_beg - path, pathLen - (ref_beg - path));
+ else
+ SET_RESULT(ref, 0, -1);
+
+ const char *end;
+ if (query_beg)
+ end = query_beg - 1;
+ else if (ref_beg)
+ end = ref_beg - 1;
+ else
+ end = path + pathLen;
+
+ // an empty file path is no file path
+ if (end != path)
+ SET_RESULT(filepath, 0, end - path);
+ else
+ SET_RESULT(filepath, 0, -1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseFilePath(const char *filepath, int32_t filepathLen,
+ uint32_t *directoryPos, int32_t *directoryLen,
+ uint32_t *basenamePos, int32_t *basenameLen,
+ uint32_t *extensionPos, int32_t *extensionLen)
+{
+ if (NS_WARN_IF(!filepath)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filepathLen < 0)
+ filepathLen = strlen(filepath);
+
+ if (filepathLen == 0) {
+ SET_RESULT(directory, 0, -1);
+ SET_RESULT(basename, 0, 0); // assume a zero length file basename
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+ }
+
+ const char *p;
+ const char *end = filepath + filepathLen;
+
+ // search backwards for filename
+ for (p = end - 1; *p != '/' && p > filepath; --p)
+ ;
+ if (*p == '/') {
+ // catch /.. and /.
+ if ((p+1 < end && *(p+1) == '.') &&
+ (p+2 == end || (*(p+2) == '.' && p+3 == end)))
+ p = end - 1;
+ // filepath = <directory><filename>.<extension>
+ SET_RESULT(directory, 0, p - filepath + 1);
+ ParseFileName(p + 1, end - (p + 1),
+ basenamePos, basenameLen,
+ extensionPos, extensionLen);
+ OFFSET_RESULT(basename, p + 1 - filepath);
+ OFFSET_RESULT(extension, p + 1 - filepath);
+ }
+ else {
+ // filepath = <filename>.<extension>
+ SET_RESULT(directory, 0, -1);
+ ParseFileName(filepath, filepathLen,
+ basenamePos, basenameLen,
+ extensionPos, extensionLen);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsBaseURLParser::ParseFileName(const char *filename, int32_t filenameLen,
+ uint32_t *basenamePos, int32_t *basenameLen,
+ uint32_t *extensionPos, int32_t *extensionLen)
+{
+ if (NS_WARN_IF(!filename)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filenameLen < 0)
+ filenameLen = strlen(filename);
+
+ // no extension if filename ends with a '.'
+ if (filename[filenameLen-1] != '.') {
+ // ignore '.' at the beginning
+ for (const char *p = filename + filenameLen - 1; p > filename; --p) {
+ if (*p == '.') {
+ // filename = <basename.extension>
+ SET_RESULT(basename, 0, p - filename);
+ SET_RESULT(extension, p + 1 - filename, filenameLen - (p - filename + 1));
+ return NS_OK;
+ }
+ }
+ }
+ // filename = <basename>
+ SET_RESULT(basename, 0, filenameLen);
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsNoAuthURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsNoAuthURLParser::ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ NS_NOTREACHED("Shouldn't parse auth in a NoAuthURL!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void
+nsNoAuthURLParser::ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen)
+{
+ NS_PRECONDITION(specLen >= 0, "unexpected");
+
+ // everything is the path
+ uint32_t pos = 0;
+ switch (CountConsecutiveSlashes(spec, specLen)) {
+ case 0:
+ case 1:
+ break;
+ case 2:
+ {
+ const char *p = nullptr;
+ if (specLen > 2) {
+ // looks like there is an authority section
+#if defined(XP_WIN)
+ // if the authority looks like a drive number then we
+ // really want to treat it as part of the path
+ // [a-zA-Z][:|]{/\}
+ // i.e one of: c: c:\foo c:/foo c| c|\foo c|/foo
+ if ((specLen > 3) && (spec[3] == ':' || spec[3] == '|') &&
+ nsCRT::IsAsciiAlpha(spec[2]) &&
+ ((specLen == 4) || (spec[4] == '/') || (spec[4] == '\\'))) {
+ pos = 1;
+ break;
+ }
+#endif
+ // Ignore apparent authority; path is everything after it
+ for (p = spec + 2; p < spec + specLen; ++p) {
+ if (*p == '/' || *p == '?' || *p == '#')
+ break;
+ }
+ }
+ SET_RESULT(auth, 0, -1);
+ if (p && p != spec+specLen)
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ else
+ SET_RESULT(path, 0, -1);
+ return;
+ }
+ default:
+ pos = 2;
+ break;
+ }
+ SET_RESULT(auth, pos, 0);
+ SET_RESULT(path, pos, specLen - pos);
+}
+
+#if defined(XP_WIN)
+NS_IMETHODIMP
+nsNoAuthURLParser::ParseFilePath(const char *filepath, int32_t filepathLen,
+ uint32_t *directoryPos, int32_t *directoryLen,
+ uint32_t *basenamePos, int32_t *basenameLen,
+ uint32_t *extensionPos, int32_t *extensionLen)
+{
+ if (NS_WARN_IF(!filepath)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filepathLen < 0)
+ filepathLen = strlen(filepath);
+
+ // look for a filepath consisting of only a drive number, which may or
+ // may not have a leading slash.
+ if (filepathLen > 1 && filepathLen < 4) {
+ const char *end = filepath + filepathLen;
+ const char *p = filepath;
+ if (*p == '/')
+ p++;
+ if ((end-p == 2) && (p[1]==':' || p[1]=='|') && nsCRT::IsAsciiAlpha(*p)) {
+ // filepath = <drive-number>:
+ SET_RESULT(directory, 0, filepathLen);
+ SET_RESULT(basename, 0, -1);
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+ }
+ }
+
+ // otherwise fallback on common implementation
+ return nsBaseURLParser::ParseFilePath(filepath, filepathLen,
+ directoryPos, directoryLen,
+ basenamePos, basenameLen,
+ extensionPos, extensionLen);
+}
+#endif
+
+//----------------------------------------------------------------------------
+// nsAuthURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ nsresult rv;
+
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0)
+ authLen = strlen(auth);
+
+ if (authLen == 0) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, 0);
+ if (port)
+ *port = -1;
+ return NS_OK;
+ }
+
+ // search backwards for @
+ const char *p = auth + authLen - 1;
+ for (; (*p != '@') && (p > auth); --p) {
+ continue;
+ }
+ if ( *p == '@' ) {
+ // auth = <user-info@server-info>
+ rv = ParseUserInfo(auth, p - auth,
+ usernamePos, usernameLen,
+ passwordPos, passwordLen);
+ if (NS_FAILED(rv)) return rv;
+ rv = ParseServerInfo(p + 1, authLen - (p - auth + 1),
+ hostnamePos, hostnameLen,
+ port);
+ if (NS_FAILED(rv)) return rv;
+ OFFSET_RESULT(hostname, p + 1 - auth);
+
+ // malformed if has a username or password
+ // but no host info, such as: http://u:p@/
+ if ((usernamePos || passwordPos) && (!hostnamePos || !*hostnameLen)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+ else {
+ // auth = <server-info>
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ rv = ParseServerInfo(auth, authLen,
+ hostnamePos, hostnameLen,
+ port);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseUserInfo(const char *userinfo, int32_t userinfoLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen)
+{
+ if (NS_WARN_IF(!userinfo)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (userinfoLen < 0)
+ userinfoLen = strlen(userinfo);
+
+ if (userinfoLen == 0) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+ }
+
+ const char *p = (const char *) memchr(userinfo, ':', userinfoLen);
+ if (p) {
+ // userinfo = <username:password>
+ if (p == userinfo) {
+ // must have a username!
+ return NS_ERROR_MALFORMED_URI;
+ }
+ SET_RESULT(username, 0, p - userinfo);
+ SET_RESULT(password, p - userinfo + 1, userinfoLen - (p - userinfo + 1));
+ }
+ else {
+ // userinfo = <username>
+ SET_RESULT(username, 0, userinfoLen);
+ SET_RESULT(password, 0, -1);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseServerInfo(const char *serverinfo, int32_t serverinfoLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ if (NS_WARN_IF(!serverinfo)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (serverinfoLen < 0)
+ serverinfoLen = strlen(serverinfo);
+
+ if (serverinfoLen == 0) {
+ SET_RESULT(hostname, 0, 0);
+ if (port)
+ *port = -1;
+ return NS_OK;
+ }
+
+ // search backwards for a ':' but stop on ']' (IPv6 address literal
+ // delimiter). check for illegal characters in the hostname.
+ const char *p = serverinfo + serverinfoLen - 1;
+ const char *colon = nullptr, *bracket = nullptr;
+ for (; p > serverinfo; --p) {
+ switch (*p) {
+ case ']':
+ bracket = p;
+ break;
+ case ':':
+ if (bracket == nullptr)
+ colon = p;
+ break;
+ case ' ':
+ // hostname must not contain a space
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ if (colon) {
+ // serverinfo = <hostname:port>
+ SET_RESULT(hostname, 0, colon - serverinfo);
+ if (port) {
+ // XXX unfortunately ToInteger is not defined for substrings
+ nsAutoCString buf(colon+1, serverinfoLen - (colon + 1 - serverinfo));
+ if (buf.Length() == 0) {
+ *port = -1;
+ }
+ else {
+ const char* nondigit = NS_strspnp("0123456789", buf.get());
+ if (nondigit && *nondigit)
+ return NS_ERROR_MALFORMED_URI;
+
+ nsresult err;
+ *port = buf.ToInteger(&err);
+ if (NS_FAILED(err) || *port < 0 || *port > std::numeric_limits<uint16_t>::max())
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+ }
+ else {
+ // serverinfo = <hostname>
+ SET_RESULT(hostname, 0, serverinfoLen);
+ if (port)
+ *port = -1;
+ }
+
+ // In case of IPv6 address check its validity
+ if (*hostnameLen > 1 && *(serverinfo + *hostnamePos) == '[' &&
+ *(serverinfo + *hostnamePos + *hostnameLen - 1) == ']' &&
+ !net_IsValidIPv6Addr(serverinfo + *hostnamePos + 1, *hostnameLen - 2))
+ return NS_ERROR_MALFORMED_URI;
+
+ return NS_OK;
+}
+
+void
+nsAuthURLParser::ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen)
+{
+ NS_PRECONDITION(specLen >= 0, "unexpected");
+
+ uint32_t nslash = CountConsecutiveSlashes(spec, specLen);
+
+ // search for the end of the authority section
+ const char *end = spec + specLen;
+ const char *p;
+ for (p = spec + nslash; p < end; ++p) {
+ if (*p == '/' || *p == '?' || *p == '#')
+ break;
+ }
+ if (p < end) {
+ // spec = [/]<auth><path>
+ SET_RESULT(auth, nslash, p - (spec + nslash));
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ }
+ else {
+ // spec = [/]<auth>
+ SET_RESULT(auth, nslash, specLen - nslash);
+ SET_RESULT(path, 0, -1);
+ }
+}
+
+//----------------------------------------------------------------------------
+// nsStdURLParser implementation
+//----------------------------------------------------------------------------
+
+void
+nsStdURLParser::ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen)
+{
+ NS_PRECONDITION(specLen >= 0, "unexpected");
+
+ uint32_t nslash = CountConsecutiveSlashes(spec, specLen);
+
+ // search for the end of the authority section
+ const char *end = spec + specLen;
+ const char *p;
+ for (p = spec + nslash; p < end; ++p) {
+ if (strchr("/?#;", *p))
+ break;
+ }
+ switch (nslash) {
+ case 0:
+ case 2:
+ if (p < end) {
+ // spec = (//)<auth><path>
+ SET_RESULT(auth, nslash, p - (spec + nslash));
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ }
+ else {
+ // spec = (//)<auth>
+ SET_RESULT(auth, nslash, specLen - nslash);
+ SET_RESULT(path, 0, -1);
+ }
+ break;
+ case 1:
+ // spec = /<path>
+ SET_RESULT(auth, 0, -1);
+ SET_RESULT(path, 0, specLen);
+ break;
+ default:
+ // spec = ///[/]<path>
+ SET_RESULT(auth, 2, 0);
+ SET_RESULT(path, 2, specLen - 2);
+ }
+}
diff --git a/netwerk/base/nsURLParsers.h b/netwerk/base/nsURLParsers.h
new file mode 100644
index 0000000000..34de99a377
--- /dev/null
+++ b/netwerk/base/nsURLParsers.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURLParsers_h__
+#define nsURLParsers_h__
+
+#include "nsIURLParser.h"
+#include "mozilla/Attributes.h"
+
+//----------------------------------------------------------------------------
+// base class for url parsers
+//----------------------------------------------------------------------------
+
+class nsBaseURLParser : public nsIURLParser
+{
+public:
+ NS_DECL_NSIURLPARSER
+
+ nsBaseURLParser() { }
+
+protected:
+ // implemented by subclasses
+ virtual void ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen) = 0;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that do not have an authority section
+//
+// eg. file:foo/bar.txt
+// file:/foo/bar.txt (treated equivalently)
+// file:///foo/bar.txt
+//
+// eg. file:////foo/bar.txt (UNC-filepath = \\foo\bar.txt)
+//
+// XXX except in this case:
+// file://foo/bar.txt (the authority "foo" is ignored)
+//----------------------------------------------------------------------------
+
+class nsNoAuthURLParser final : public nsBaseURLParser
+{
+ ~nsNoAuthURLParser() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+#if defined(XP_WIN)
+ NS_IMETHOD ParseFilePath(const char *, int32_t,
+ uint32_t *, int32_t *,
+ uint32_t *, int32_t *,
+ uint32_t *, int32_t *) override;
+#endif
+
+ NS_IMETHOD ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port) override;
+
+ void ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen) override;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that must have an authority section
+//
+// eg. http:www.foo.com/bar.html
+// http:/www.foo.com/bar.html
+// http://www.foo.com/bar.html (treated equivalently)
+// http:///www.foo.com/bar.html
+//----------------------------------------------------------------------------
+
+class nsAuthURLParser : public nsBaseURLParser
+{
+protected:
+ virtual ~nsAuthURLParser() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port) override;
+
+ NS_IMETHOD ParseUserInfo(const char *userinfo, int32_t userinfoLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen) override;
+
+ NS_IMETHOD ParseServerInfo(const char *serverinfo, int32_t serverinfoLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port) override;
+
+ void ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen) override;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that may or may not have an authority section
+//
+// eg. http:www.foo.com (www.foo.com is authority)
+// http:www.foo.com/bar.html (www.foo.com is authority)
+// http:/www.foo.com/bar.html (www.foo.com is part of file path)
+// http://www.foo.com/bar.html (www.foo.com is authority)
+// http:///www.foo.com/bar.html (www.foo.com is part of file path)
+//----------------------------------------------------------------------------
+
+class nsStdURLParser : public nsAuthURLParser
+{
+ virtual ~nsStdURLParser() {}
+
+public:
+ void ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen);
+};
+
+#endif // nsURLParsers_h__
diff --git a/netwerk/base/nsUnicharStreamLoader.cpp b/netwerk/base/nsUnicharStreamLoader.cpp
new file mode 100644
index 0000000000..115acf9ae7
--- /dev/null
+++ b/netwerk/base/nsUnicharStreamLoader.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsUnicharStreamLoader.h"
+#include "nsIInputStream.h"
+#include <algorithm>
+#include "mozilla/dom/EncodingUtils.h"
+
+// 1024 bytes is specified in
+// http://www.whatwg.org/specs/web-apps/current-work/#charset for HTML; for
+// other resource types (e.g. CSS) typically fewer bytes are fine too, since
+// they only look at things right at the beginning of the data.
+#define SNIFFING_BUFFER_SIZE 1024
+
+using namespace mozilla;
+using mozilla::dom::EncodingUtils;
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::Init(nsIUnicharStreamLoaderObserver *aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+
+ mObserver = aObserver;
+
+ if (!mRawData.SetCapacity(SNIFFING_BUFFER_SIZE, fallible))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+nsresult
+nsUnicharStreamLoader::Create(nsISupports *aOuter,
+ REFNSIID aIID,
+ void **aResult)
+{
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ nsUnicharStreamLoader* it = new nsUnicharStreamLoader();
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsUnicharStreamLoader, nsIUnicharStreamLoader,
+ nsIRequestObserver, nsIStreamListener)
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::GetChannel(nsIChannel **aChannel)
+{
+ NS_IF_ADDREF(*aChannel = mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::GetCharset(nsACString& aCharset)
+{
+ aCharset = mCharset;
+ return NS_OK;
+}
+
+/* nsIRequestObserver implementation */
+NS_IMETHODIMP
+nsUnicharStreamLoader::OnStartRequest(nsIRequest*, nsISupports*)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ if (!mObserver) {
+ NS_ERROR("nsUnicharStreamLoader::OnStopRequest called before ::Init");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mContext = aContext;
+ mChannel = do_QueryInterface(aRequest);
+
+ nsresult rv = NS_OK;
+ if (mRawData.Length() > 0 && NS_SUCCEEDED(aStatus)) {
+ MOZ_ASSERT(mBuffer.Length() == 0,
+ "should not have both decoded and raw data");
+ rv = DetermineCharset();
+ }
+
+ if (NS_FAILED(rv)) {
+ // Call the observer but pass it no data.
+ mObserver->OnStreamComplete(this, mContext, rv, EmptyString());
+ } else {
+ mObserver->OnStreamComplete(this, mContext, aStatus, mBuffer);
+ }
+
+ mObserver = nullptr;
+ mDecoder = nullptr;
+ mContext = nullptr;
+ mChannel = nullptr;
+ mCharset.Truncate();
+ mRawData.Truncate();
+ mRawBuffer.Truncate();
+ mBuffer.Truncate();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::GetRawBuffer(nsACString& aRawBuffer)
+{
+ aRawBuffer = mRawBuffer;
+ return NS_OK;
+}
+
+/* nsIStreamListener implementation */
+NS_IMETHODIMP
+nsUnicharStreamLoader::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ if (!mObserver) {
+ NS_ERROR("nsUnicharStreamLoader::OnDataAvailable called before ::Init");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mContext = aContext;
+ mChannel = do_QueryInterface(aRequest);
+
+ nsresult rv = NS_OK;
+ if (mDecoder) {
+ // process everything we've got
+ uint32_t dummy;
+ aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy);
+ } else {
+ // no decoder yet. Read up to SNIFFING_BUFFER_SIZE octets into
+ // mRawData (this is the cutoff specified in
+ // draft-abarth-mime-sniff-06). If we can get that much, then go
+ // ahead and fire charset detection and read the rest. Otherwise
+ // wait for more data.
+
+ uint32_t haveRead = mRawData.Length();
+ uint32_t toRead = std::min(SNIFFING_BUFFER_SIZE - haveRead, aCount);
+ uint32_t n;
+ char *here = mRawData.BeginWriting() + haveRead;
+
+ rv = aInputStream->Read(here, toRead, &n);
+ if (NS_SUCCEEDED(rv)) {
+ mRawData.SetLength(haveRead + n);
+ if (mRawData.Length() == SNIFFING_BUFFER_SIZE) {
+ rv = DetermineCharset();
+ if (NS_SUCCEEDED(rv)) {
+ // process what's left
+ uint32_t dummy;
+ aInputStream->ReadSegments(WriteSegmentFun, this, aCount - n, &dummy);
+ }
+ } else {
+ MOZ_ASSERT(n == aCount, "didn't read as much as was available");
+ }
+ }
+ }
+
+ mContext = nullptr;
+ mChannel = nullptr;
+ return rv;
+}
+
+nsresult
+nsUnicharStreamLoader::DetermineCharset()
+{
+ nsresult rv = mObserver->OnDetermineCharset(this, mContext,
+ mRawData, mCharset);
+ if (NS_FAILED(rv) || mCharset.IsEmpty()) {
+ // The observer told us nothing useful
+ mCharset.AssignLiteral("UTF-8");
+ }
+
+ // Sadly, nsIUnicharStreamLoader is exposed to extensions, so we can't
+ // assume mozilla::css::Loader to be the only caller. Special-casing
+ // replacement, since it's not invariant under a second label resolution
+ // operation.
+ if (mCharset.EqualsLiteral("replacement")) {
+ mDecoder = EncodingUtils::DecoderForEncoding(mCharset);
+ } else {
+ nsAutoCString charset;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(mCharset, charset)) {
+ // If we got replacement here, the caller was not mozilla::css::Loader
+ // but an extension.
+ return NS_ERROR_UCONV_NOCONV;
+ }
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ }
+
+ // Process the data into mBuffer
+ uint32_t dummy;
+ rv = WriteSegmentFun(nullptr, this,
+ mRawData.BeginReading(),
+ 0, mRawData.Length(),
+ &dummy);
+ mRawData.Truncate();
+ return rv;
+}
+
+nsresult
+nsUnicharStreamLoader::WriteSegmentFun(nsIInputStream *,
+ void *aClosure,
+ const char *aSegment,
+ uint32_t,
+ uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ nsUnicharStreamLoader* self = static_cast<nsUnicharStreamLoader*>(aClosure);
+
+ uint32_t haveRead = self->mBuffer.Length();
+ int32_t srcLen = aCount;
+ int32_t dstLen;
+
+ nsresult rv = self->mDecoder->GetMaxLength(aSegment, srcLen, &dstLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint32_t capacity = haveRead + dstLen;
+ if (!self->mBuffer.SetCapacity(capacity, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!self->mRawBuffer.Append(aSegment, aCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = self->mDecoder->Convert(aSegment,
+ &srcLen,
+ self->mBuffer.BeginWriting() + haveRead,
+ &dstLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(srcLen == static_cast<int32_t>(aCount));
+ haveRead += dstLen;
+
+ self->mBuffer.SetLength(haveRead);
+ *aWriteCount = aCount;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsUnicharStreamLoader.h b/netwerk/base/nsUnicharStreamLoader.h
new file mode 100644
index 0000000000..298fb9e110
--- /dev/null
+++ b/netwerk/base/nsUnicharStreamLoader.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUnicharStreamLoader_h__
+#define nsUnicharStreamLoader_h__
+
+#include "nsIChannel.h"
+#include "nsIUnicharStreamLoader.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsIInputStream;
+
+class nsUnicharStreamLoader : public nsIUnicharStreamLoader
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUNICHARSTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsUnicharStreamLoader() {}
+
+ static nsresult Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ virtual ~nsUnicharStreamLoader() {}
+
+ nsresult DetermineCharset();
+
+ /**
+ * callback method used for ReadSegments
+ */
+ static nsresult WriteSegmentFun(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ nsCOMPtr<nsIUnicharStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+ nsCOMPtr<nsISupports> mContext;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCString mCharset;
+
+ // This holds the first up-to-512 bytes of the raw stream.
+ // It will be passed to the OnDetermineCharset callback.
+ nsCString mRawData;
+
+ // Holds complete raw bytes as received so that SRI checks can be
+ // calculated on the raw data prior to character conversion.
+ nsCString mRawBuffer;
+
+ // This holds the complete contents of the stream so far, after
+ // decoding to UTF-16. It will be passed to the OnStreamComplete
+ // callback.
+ nsString mBuffer;
+};
+
+#endif // nsUnicharStreamLoader_h__
diff --git a/netwerk/base/rust-url-capi/.gitignore b/netwerk/base/rust-url-capi/.gitignore
new file mode 100644
index 0000000000..4fffb2f89c
--- /dev/null
+++ b/netwerk/base/rust-url-capi/.gitignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/netwerk/base/rust-url-capi/Cargo.toml b/netwerk/base/rust-url-capi/Cargo.toml
new file mode 100644
index 0000000000..ecdb53058a
--- /dev/null
+++ b/netwerk/base/rust-url-capi/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+
+name = "rust_url_capi"
+version = "0.0.1"
+authors = ["Valentin Gosu <valentin.gosu@gmail.com>"]
+
+[profile.dev]
+opt-level = 3
+debug = true
+rpath = true
+lto = true
+
+[lib]
+name = "rust_url_capi"
+
+
+[dependencies]
+libc = "0.2.0"
+url = "1.2.1"
diff --git a/netwerk/base/rust-url-capi/src/error_mapping.rs b/netwerk/base/rust-url-capi/src/error_mapping.rs
new file mode 100644
index 0000000000..f20afb263c
--- /dev/null
+++ b/netwerk/base/rust-url-capi/src/error_mapping.rs
@@ -0,0 +1,68 @@
+use url::ParseError;
+
+pub trait ErrorCode {
+ fn error_code(&self) -> i32;
+}
+
+impl<T: ErrorCode> ErrorCode for Result<(), T> {
+ fn error_code(&self) -> i32 {
+ match *self {
+ Ok(_) => 0,
+ Err(ref error) => error.error_code(),
+ }
+ }
+}
+
+impl ErrorCode for () {
+ fn error_code(&self) -> i32 {
+ return -1;
+ }
+}
+impl ErrorCode for ParseError {
+ fn error_code(&self) -> i32 {
+ return -1;
+// match *self {
+// ParseError::EmptyHost => -1,
+// ParseError::InvalidScheme => -2,
+// ParseError::InvalidPort => -3,
+// ParseError::InvalidIpv6Address => -4,
+// ParseError::InvalidDomainCharacter => -5,
+// ParseError::InvalidCharacter => -6,
+// ParseError::InvalidBackslash => -7,
+// ParseError::InvalidPercentEncoded => -8,
+// ParseError::InvalidAtSymbolInUser => -9,
+// ParseError::ExpectedTwoSlashes => -10,
+// ParseError::ExpectedInitialSlash => -11,
+// ParseError::NonUrlCodePoint => -12,
+// ParseError::RelativeUrlWithScheme => -13,
+// ParseError::RelativeUrlWithoutBase => -14,
+// ParseError::RelativeUrlWithNonRelativeBase => -15,
+// ParseError::NonAsciiDomainsNotSupportedYet => -16,
+// ParseError::CannotSetJavascriptFragment => -17,
+// ParseError::CannotSetPortWithFileLikeScheme => -18,
+// ParseError::CannotSetUsernameWithNonRelativeScheme => -19,
+// ParseError::CannotSetPasswordWithNonRelativeScheme => -20,
+// ParseError::CannotSetHostPortWithNonRelativeScheme => -21,
+// ParseError::CannotSetHostWithNonRelativeScheme => -22,
+// ParseError::CannotSetPortWithNonRelativeScheme => -23,
+// ParseError::CannotSetPathWithNonRelativeScheme => -24,
+// }
+ }
+}
+
+pub enum NSError {
+ OK,
+ InvalidArg,
+ Failure,
+}
+
+impl ErrorCode for NSError {
+ #[allow(overflowing_literals)]
+ fn error_code(&self) -> i32 {
+ match *self {
+ NSError::OK => 0,
+ NSError::InvalidArg => 0x80070057,
+ NSError::Failure => 0x80004005
+ }
+ }
+}
diff --git a/netwerk/base/rust-url-capi/src/lib.rs b/netwerk/base/rust-url-capi/src/lib.rs
new file mode 100644
index 0000000000..e2997ce469
--- /dev/null
+++ b/netwerk/base/rust-url-capi/src/lib.rs
@@ -0,0 +1,477 @@
+extern crate url;
+use url::{Url, ParseError, ParseOptions};
+use url::quirks;
+extern crate libc;
+use libc::size_t;
+
+
+use std::mem;
+use std::str;
+
+#[allow(non_camel_case_types)]
+pub type rusturl_ptr = *const libc::c_void;
+
+mod string_utils;
+pub use string_utils::*;
+
+mod error_mapping;
+use error_mapping::*;
+
+fn parser<'a>() -> ParseOptions<'a> {
+ Url::options()
+}
+
+fn default_port(scheme: &str) -> Option<u32> {
+ match scheme {
+ "ftp" => Some(21),
+ "gopher" => Some(70),
+ "http" => Some(80),
+ "https" => Some(443),
+ "ws" => Some(80),
+ "wss" => Some(443),
+ "rtsp" => Some(443),
+ "moz-anno" => Some(443),
+ "android" => Some(443),
+ _ => None,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_new(spec: *mut libc::c_char, len: size_t) -> rusturl_ptr {
+ let slice = std::slice::from_raw_parts(spec as *const libc::c_uchar, len as usize);
+ let url_spec = match str::from_utf8(slice) {
+ Ok(spec) => spec,
+ Err(_) => return 0 as rusturl_ptr
+ };
+
+ let url = match parser().parse(url_spec) {
+ Ok(url) => url,
+ Err(_) => return 0 as rusturl_ptr
+ };
+
+ let url = Box::new(url);
+ Box::into_raw(url) as rusturl_ptr
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_free(urlptr: rusturl_ptr) {
+ if urlptr.is_null() {
+ return ();
+ }
+ let url: Box<Url> = Box::from_raw(urlptr as *mut url::Url);
+ drop(url);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_spec(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ cont.assign(&url.to_string())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_scheme(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ cont.assign(&url.scheme())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_username(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ if url.cannot_be_a_base() {
+ cont.set_size(0)
+ } else {
+ cont.assign(url.username())
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_password(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ match url.password() {
+ Some(p) => cont.assign(&p.to_string()),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_host(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+
+ match url.host() {
+ Some(h) => cont.assign(&h.to_string()),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_port(urlptr: rusturl_ptr) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+
+ match url.port() {
+ Some(port) => port as i32,
+ None => -1
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_path(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ if url.cannot_be_a_base() {
+ cont.set_size(0)
+ } else {
+ cont.assign(url.path())
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_query(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ match url.query() {
+ Some(ref s) => cont.assign(s),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_fragment(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+
+ match url.fragment() {
+ Some(ref fragment) => cont.assign(fragment),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_has_fragment(urlptr: rusturl_ptr) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+
+ match url.fragment() {
+ Some(_) => return 1,
+ None => return 0
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_scheme(urlptr: rusturl_ptr, scheme: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(scheme as *const libc::c_uchar, len as usize);
+
+ let scheme_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_protocol(url, scheme_).error_code()
+}
+
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_username(urlptr: rusturl_ptr, username: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(username as *const libc::c_uchar, len as usize);
+
+ let username_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_username(url, username_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_password(urlptr: rusturl_ptr, password: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(password as *const libc::c_uchar, len as usize);
+
+ let password_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_password(url, password_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_host_and_port(urlptr: rusturl_ptr, host_and_port: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(host_and_port as *const libc::c_uchar, len as usize);
+
+ let host_and_port_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_host(url, host_and_port_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_host(urlptr: rusturl_ptr, host: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(host as *const libc::c_uchar, len as usize);
+
+ let hostname = match str::from_utf8(slice).ok() {
+ Some(h) => h,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_hostname(url, hostname).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_port(urlptr: rusturl_ptr, port: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(port as *const libc::c_uchar, len as usize);
+
+ let port_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_port(url, port_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_port_no(urlptr: rusturl_ptr, new_port: i32) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ if url.cannot_be_a_base() {
+ -100
+ } else {
+ if url.scheme() == "file" {
+ return -100;
+ }
+ match default_port(url.scheme()) {
+ Some(def_port) => if new_port == def_port as i32 {
+ let _ = url.set_port(None);
+ return NSError::OK.error_code();
+ },
+ None => {}
+ };
+ if new_port > std::u16::MAX as i32 || new_port < 0 {
+ let _ = url.set_port(None);
+ } else {
+ let _ = url.set_port(Some(new_port as u16));
+ }
+ NSError::OK.error_code()
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_path(urlptr: rusturl_ptr, path: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(path as *const libc::c_uchar, len as usize);
+
+ let path_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_pathname(url, path_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_query(urlptr: rusturl_ptr, query: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(query as *const libc::c_uchar, len as usize);
+
+ let query_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_search(url, query_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_fragment(urlptr: rusturl_ptr, fragment: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(fragment as *const libc::c_uchar, len as usize);
+
+ let fragment_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_hash(url, fragment_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_resolve(urlptr: rusturl_ptr, resolve: *mut libc::c_char, len: size_t, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &mut Url = mem::transmute(urlptr);
+
+ let slice = std::slice::from_raw_parts(resolve as *const libc::c_uchar, len as usize);
+
+ let resolve_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return NSError::Failure.error_code()
+ };
+
+ match parser().base_url(Some(&url)).parse(resolve_).ok() {
+ Some(u) => cont.assign(&u.to_string()),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_common_base_spec(urlptr1: rusturl_ptr, urlptr2: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr1.is_null() || urlptr2.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url1: &Url = mem::transmute(urlptr1);
+ let url2: &Url = mem::transmute(urlptr2);
+
+ if url1 == url2 {
+ return cont.assign(&url1.to_string());
+ }
+
+ if url1.scheme() != url2.scheme() ||
+ url1.host() != url2.host() ||
+ url1.username() != url2.username() ||
+ url1.password() != url2.password() ||
+ url1.port() != url2.port() {
+ return cont.set_size(0);
+ }
+
+ let path1 = match url1.path_segments() {
+ Some(path) => path,
+ None => return cont.set_size(0)
+ };
+ let path2 = match url2.path_segments() {
+ Some(path) => path,
+ None => return cont.set_size(0)
+ };
+
+ let mut url = url1.clone();
+ url.set_query(None);
+ let _ = url.set_host(None);
+ {
+ let mut new_segments = if let Ok(segments) = url.path_segments_mut() {
+ segments
+ } else {
+ return cont.set_size(0)
+ };
+
+ for (p1, p2) in path1.zip(path2) {
+ if p1 != p2 {
+ break;
+ } else {
+ new_segments.push(p1);
+ }
+ }
+ }
+
+ cont.assign(&url.to_string())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_relative_spec(urlptr1: rusturl_ptr, urlptr2: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr1.is_null() || urlptr2.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url1: &Url = mem::transmute(urlptr1);
+ let url2: &Url = mem::transmute(urlptr2);
+
+ if url1 == url2 {
+ return cont.set_size(0);
+ }
+
+ if url1.scheme() != url2.scheme() ||
+ url1.host() != url2.host() ||
+ url1.username() != url2.username() ||
+ url1.password() != url2.password() ||
+ url1.port() != url2.port() {
+ return cont.assign(&url2.to_string());
+ }
+
+ let mut path1 = match url1.path_segments() {
+ Some(path) => path,
+ None => return cont.assign(&url2.to_string())
+ };
+ let mut path2 = match url2.path_segments() {
+ Some(path) => path,
+ None => return cont.assign(&url2.to_string())
+ };
+
+ // TODO: file:// on WIN?
+
+ // Exhaust the part of the iterators that match
+ while let (Some(ref p1), Some(ref p2)) = (path1.next(), path2.next()) {
+ if p1 != p2 {
+ break;
+ }
+ }
+
+ let mut buffer: String = "".to_string();
+ for _ in path1 {
+ buffer = buffer + "../";
+ }
+ for p2 in path2 {
+ buffer = buffer + p2 + "/";
+ }
+
+ return cont.assign(&buffer);
+}
+
diff --git a/netwerk/base/rust-url-capi/src/rust-url-capi.h b/netwerk/base/rust-url-capi/src/rust-url-capi.h
new file mode 100644
index 0000000000..8d7a05aedc
--- /dev/null
+++ b/netwerk/base/rust-url-capi/src/rust-url-capi.h
@@ -0,0 +1,45 @@
+#ifndef __RUST_URL_CAPI
+#define __RUST_URL_CAPI
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rusturl;
+typedef struct rusturl* rusturl_ptr;
+
+rusturl_ptr rusturl_new(const char *spec, size_t src_len);
+void rusturl_free(rusturl_ptr url);
+
+int32_t rusturl_get_spec(rusturl_ptr url, void*);
+int32_t rusturl_get_scheme(rusturl_ptr url, void*);
+int32_t rusturl_get_username(rusturl_ptr url, void*);
+int32_t rusturl_get_password(rusturl_ptr url, void*);
+int32_t rusturl_get_host(rusturl_ptr url, void*);
+int32_t rusturl_get_port(rusturl_ptr url); // returns port or -1
+int32_t rusturl_get_path(rusturl_ptr url, void*);
+int32_t rusturl_get_query(rusturl_ptr url, void*);
+int32_t rusturl_get_fragment(rusturl_ptr url, void*);
+int32_t rusturl_has_fragment(rusturl_ptr url); // 1 true, 0 false, < 0 error
+
+int32_t rusturl_set_scheme(rusturl_ptr url, const char *scheme, size_t len);
+int32_t rusturl_set_username(rusturl_ptr url, const char *user, size_t len);
+int32_t rusturl_set_password(rusturl_ptr url, const char *pass, size_t len);
+int32_t rusturl_set_host_and_port(rusturl_ptr url, const char *hostport, size_t len);
+int32_t rusturl_set_host(rusturl_ptr url, const char *host, size_t len);
+int32_t rusturl_set_port(rusturl_ptr url, const char *port, size_t len);
+int32_t rusturl_set_port_no(rusturl_ptr url, const int32_t port);
+int32_t rusturl_set_path(rusturl_ptr url, const char *path, size_t len);
+int32_t rusturl_set_query(rusturl_ptr url, const char *path, size_t len);
+int32_t rusturl_set_fragment(rusturl_ptr url, const char *path, size_t len);
+
+int32_t rusturl_resolve(rusturl_ptr url, const char *relative, size_t len, void*);
+int32_t rusturl_common_base_spec(rusturl_ptr url1, rusturl_ptr url2, void*);
+int32_t rusturl_relative_spec(rusturl_ptr url1, rusturl_ptr url2, void*);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __RUST_URL_CAPI \ No newline at end of file
diff --git a/netwerk/base/rust-url-capi/src/string_utils.rs b/netwerk/base/rust-url-capi/src/string_utils.rs
new file mode 100644
index 0000000000..ae68a60dc3
--- /dev/null
+++ b/netwerk/base/rust-url-capi/src/string_utils.rs
@@ -0,0 +1,57 @@
+extern crate libc;
+use libc::size_t;
+
+extern crate std;
+use std::ptr;
+
+use error_mapping::*;
+
+extern "C" {
+ fn c_fn_set_size(user: *mut libc::c_void, size: size_t) -> i32;
+ fn c_fn_get_buffer(user: *mut libc::c_void) -> *mut libc::c_char;
+}
+
+pub trait StringContainer {
+ fn set_size(&self, size_t) -> i32;
+ fn get_buffer(&self) -> *mut libc::c_char;
+ fn assign(&self, content: &str) -> i32;
+}
+
+impl StringContainer for *mut libc::c_void {
+ fn set_size(&self, size: size_t) -> i32 {
+ if (*self).is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ unsafe {
+ c_fn_set_size(*self, size);
+ }
+
+ return NSError::OK.error_code();
+ }
+ fn get_buffer(&self) -> *mut libc::c_char {
+ if (*self).is_null() {
+ return 0 as *mut libc::c_char;
+ }
+ unsafe {
+ c_fn_get_buffer(*self)
+ }
+ }
+ fn assign(&self, content: &str) -> i32 {
+ if (*self).is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+
+ unsafe {
+ let slice = content.as_bytes();
+ c_fn_set_size(*self, slice.len());
+ let buf = c_fn_get_buffer(*self);
+ if buf.is_null() {
+ return NSError::Failure.error_code();
+ }
+
+ ptr::copy(slice.as_ptr(), buf as *mut u8, slice.len());
+ }
+
+ NSError::OK.error_code()
+ }
+}
diff --git a/netwerk/base/rust-url-capi/test/Makefile b/netwerk/base/rust-url-capi/test/Makefile
new file mode 100644
index 0000000000..a4e2fd0cfa
--- /dev/null
+++ b/netwerk/base/rust-url-capi/test/Makefile
@@ -0,0 +1,4 @@
+all:
+ cd .. && cargo build
+ g++ -Wall -o test test.cpp ../target/debug/librust*.a -ldl -lpthread -lrt -lgcc_s -lpthread -lc -lm -std=c++0x
+ ./test
diff --git a/netwerk/base/rust-url-capi/test/test.cpp b/netwerk/base/rust-url-capi/test/test.cpp
new file mode 100644
index 0000000000..6e90ea43b0
--- /dev/null
+++ b/netwerk/base/rust-url-capi/test/test.cpp
@@ -0,0 +1,141 @@
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "../src/rust-url-capi.h"
+
+class StringContainer
+{
+public:
+ StringContainer()
+ {
+ mBuffer = nullptr;
+ mLength = 0;
+ }
+
+ ~StringContainer()
+ {
+ free(mBuffer);
+ mBuffer = nullptr;
+ }
+
+ void SetSize(size_t size)
+ {
+ mLength = size;
+ if (mBuffer) {
+ mBuffer = (char *)realloc(mBuffer, size);
+ return;
+ }
+ mBuffer = (char *)malloc(size);
+ }
+
+ char * GetBuffer()
+ {
+ return mBuffer;
+ }
+
+ void CheckEquals(const char * ref) {
+ int32_t refLen = strlen(ref);
+ printf("CheckEquals: %s (len:%d)\n", ref, refLen);
+ if (refLen != mLength || strncmp(mBuffer, ref, mLength)) {
+ printf("\t--- ERROR ---\n");
+ printf("Got : ");
+ fwrite(mBuffer, mLength, 1, stdout);
+ printf(" (len:%d)\n", mLength);
+ exit(-1);
+ }
+ printf("-> OK\n");
+ }
+private:
+ int32_t mLength;
+ char * mBuffer;
+};
+
+extern "C" int32_t c_fn_set_size(void * container, size_t size)
+{
+ ((StringContainer *) container)->SetSize(size);
+ return 0;
+}
+
+extern "C" char * c_fn_get_buffer(void * container)
+{
+ return ((StringContainer *) container)->GetBuffer();
+}
+
+#define TEST_CALL(func, expected) \
+{ \
+ int32_t code = func; \
+ printf("%s -> code %d\n", #func, code); \
+ assert(code == expected); \
+ printf("-> OK\n"); \
+} \
+
+
+int main() {
+ // Create URL
+ rusturl_ptr url = rusturl_new("http://example.com/path/some/file.txt",
+ strlen("http://example.com/path/some/file.txt"));
+ assert(url); // Check we have a URL
+
+ StringContainer container;
+
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://example.com/path/some/file.txt");
+ TEST_CALL(rusturl_set_host(url, "test.com", strlen("test.com")), 0);
+ TEST_CALL(rusturl_get_host(url, &container), 0);
+ container.CheckEquals("test.com");
+ TEST_CALL(rusturl_get_path(url, &container), 0);
+ container.CheckEquals("/path/some/file.txt");
+ TEST_CALL(rusturl_set_path(url, "hello/../else.txt", strlen("hello/../else.txt")), 0);
+ TEST_CALL(rusturl_get_path(url, &container), 0);
+ container.CheckEquals("/else.txt");
+ TEST_CALL(rusturl_resolve(url, "./bla/file.txt", strlen("./bla/file.txt"), &container), 0);
+ container.CheckEquals("http://test.com/bla/file.txt");
+ TEST_CALL(rusturl_get_scheme(url, &container), 0);
+ container.CheckEquals("http");
+ TEST_CALL(rusturl_set_username(url, "user", strlen("user")), 0);
+ TEST_CALL(rusturl_get_username(url, &container), 0);
+ container.CheckEquals("user");
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://user@test.com/else.txt");
+ TEST_CALL(rusturl_set_password(url, "pass", strlen("pass")), 0);
+ TEST_CALL(rusturl_get_password(url, &container), 0);
+ container.CheckEquals("pass");
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://user:pass@test.com/else.txt");
+ TEST_CALL(rusturl_set_username(url, "", strlen("")), 0);
+ TEST_CALL(rusturl_set_password(url, "", strlen("")), 0);
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://test.com/else.txt");
+ TEST_CALL(rusturl_set_host_and_port(url, "example.org:1234", strlen("example.org:1234")), 0);
+ TEST_CALL(rusturl_get_host(url, &container), 0);
+ container.CheckEquals("example.org");
+ assert(rusturl_get_port(url) == 1234);
+ TEST_CALL(rusturl_set_port(url, "9090", strlen("9090")), 0);
+ assert(rusturl_get_port(url) == 9090);
+ TEST_CALL(rusturl_set_query(url, "x=1", strlen("x=1")), 0);
+ TEST_CALL(rusturl_get_query(url, &container), 0);
+ container.CheckEquals("x=1");
+ TEST_CALL(rusturl_set_fragment(url, "fragment", strlen("fragment")), 0);
+ TEST_CALL(rusturl_get_fragment(url, &container), 0);
+ container.CheckEquals("fragment");
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://example.org:9090/else.txt?x=1#fragment");
+
+ // Free the URL
+ rusturl_free(url);
+
+ url = rusturl_new("http://example.com/#",
+ strlen("http://example.com/#"));
+ assert(url); // Check we have a URL
+
+ assert(rusturl_has_fragment(url) == 1);
+ TEST_CALL(rusturl_set_fragment(url, "", 0), 0);
+ assert(rusturl_has_fragment(url) == 0);
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://example.com/");
+
+ rusturl_free(url);
+
+ printf("SUCCESS\n");
+ return 0;
+} \ No newline at end of file
diff --git a/netwerk/base/security-prefs.js b/netwerk/base/security-prefs.js
new file mode 100644
index 0000000000..9f42745f78
--- /dev/null
+++ b/netwerk/base/security-prefs.js
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pref("security.tls.version.min", 1);
+pref("security.tls.version.max", 3);
+pref("security.tls.version.fallback-limit", 3);
+pref("security.tls.insecure_fallback_hosts", "");
+pref("security.tls.unrestricted_rc4_fallback", false);
+pref("security.tls.enable_0rtt_data", false);
+
+pref("security.ssl.treat_unsafe_negotiation_as_broken", false);
+pref("security.ssl.require_safe_negotiation", false);
+pref("security.ssl.enable_ocsp_stapling", true);
+pref("security.ssl.enable_false_start", true);
+pref("security.ssl.false_start.require-npn", false);
+pref("security.ssl.enable_npn", true);
+pref("security.ssl.enable_alpn", true);
+
+pref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", true);
+pref("security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256", true);
+pref("security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256", true);
+pref("security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256", true);
+pref("security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384", true);
+pref("security.ssl3.ecdhe_rsa_aes_256_gcm_sha384", true);
+pref("security.ssl3.ecdhe_rsa_aes_128_sha", true);
+pref("security.ssl3.ecdhe_ecdsa_aes_128_sha", true);
+pref("security.ssl3.ecdhe_rsa_aes_256_sha", true);
+pref("security.ssl3.ecdhe_ecdsa_aes_256_sha", true);
+pref("security.ssl3.dhe_rsa_aes_128_sha", true);
+pref("security.ssl3.dhe_rsa_aes_256_sha", true);
+pref("security.ssl3.rsa_aes_128_sha", true);
+pref("security.ssl3.rsa_aes_256_sha", true);
+pref("security.ssl3.rsa_des_ede3_sha", true);
+
+pref("security.content.signature.root_hash",
+ "97:E8:BA:9C:F1:2F:B3:DE:53:CC:42:A4:E6:57:7E:D6:4D:F4:93:C2:47:B4:14:FE:A0:36:81:8D:38:23:56:0E");
+
+pref("security.default_personal_cert", "Ask Every Time");
+pref("security.remember_cert_checkbox_default_setting", true);
+pref("security.ask_for_password", 0);
+pref("security.password_lifetime", 30);
+
+// The supported values of this pref are:
+// 0: disable detecting Family Safety mode and importing the root
+// 1: only attempt to detect Family Safety mode (don't import the root)
+// 2: detect Family Safety mode and import the root
+// (This is only relevant to Windows 8.1)
+pref("security.family_safety.mode", 2);
+
+pref("security.enterprise_roots.enabled", false);
+
+pref("security.OCSP.enabled", 1);
+pref("security.OCSP.require", false);
+pref("security.OCSP.GET.enabled", false);
+
+pref("security.pki.cert_short_lifetime_in_days", 10);
+// NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
+// See the comment in CertVerifier.cpp.
+// 3 = only allow SHA-1 for certificates issued by an imported root.
+pref("security.pki.sha1_enforcement_level", 3);
+
+// security.pki.name_matching_mode controls how the platform matches hostnames
+// to name information in TLS certificates. The possible values are:
+// 0: always fall back to the subject common name if necessary (as in, if the
+// subject alternative name extension is either not present or does not
+// contain any DNS names or IP addresses)
+// 1: fall back to the subject common name for certificates valid before 23
+// August 2016 if necessary
+// 2: fall back to the subject common name for certificates valid before 23
+// August 2015 if necessary
+// 3: only use name information from the subject alternative name extension
+#ifdef RELEASE_OR_BETA
+pref("security.pki.name_matching_mode", 1);
+#else
+pref("security.pki.name_matching_mode", 2);
+#endif
+
+// security.pki.netscape_step_up_policy controls how the platform handles the
+// id-Netscape-stepUp OID in extended key usage extensions of CA certificates.
+// 0: id-Netscape-stepUp is always considered equivalent to id-kp-serverAuth
+// 1: it is considered equivalent when the notBefore is before 23 August 2016
+// 2: similarly, but for 23 August 2015
+// 3: it is never considered equivalent
+#ifdef RELEASE_OR_BETA
+pref("security.pki.netscape_step_up_policy", 1);
+#else
+pref("security.pki.netscape_step_up_policy", 2);
+#endif
+
+// Configures Certificate Transparency support mode:
+// 0: Fully disabled.
+// 1: Only collect telemetry. CT qualification checks are not performed.
+pref("security.pki.certificate_transparency.mode", 0);
+
+pref("security.webauth.u2f", false);
+pref("security.webauth.u2f_enable_softtoken", false);
+pref("security.webauth.u2f_enable_usbtoken", false);
+
+pref("security.ssl.errorReporting.enabled", true);
+pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/");
+pref("security.ssl.errorReporting.automatic", false);
+
+// Impose a maximum age on HPKP headers, to avoid sites getting permanently
+// blacking themselves out by setting a bad pin. (60 days by default)
+// https://tools.ietf.org/html/rfc7469#section-4.1
+pref("security.cert_pinning.max_max_age_seconds", 5184000);
+
+// If a request is mixed-content, send an HSTS priming request to attempt to
+// see if it is available over HTTPS.
+pref("security.mixed_content.send_hsts_priming", true);
+#ifdef RELEASE_OR_BETA
+// Don't change the order of evaluation of mixed-content and HSTS upgrades
+pref("security.mixed_content.use_hsts", false);
+#else
+// Change the order of evaluation so HSTS upgrades happen before
+// mixed-content blocking
+pref("security.mixed_content.use_hsts", true);
+#endif
diff --git a/netwerk/build/moz.build b/netwerk/build/moz.build
new file mode 100644
index 0000000000..b7f658688f
--- /dev/null
+++ b/netwerk/build/moz.build
@@ -0,0 +1,72 @@
+# -*- 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',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+ '/netwerk/cache',
+ '/netwerk/dns',
+ '/netwerk/mime',
+ '/netwerk/protocol/about',
+ '/netwerk/socket',
+ '/netwerk/streamconv',
+ '/netwerk/streamconv/converters',
+]
+
+protocols = CONFIG['NECKO_PROTOCOLS']
+LOCAL_INCLUDES += sorted([
+ '/netwerk/protocol/%s' % d for d in protocols if d != 'about'
+])
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ LOCAL_INCLUDES += [
+ '/netwerk/system/win32',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/netwerk/system/mac',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ LOCAL_INCLUDES += [
+ '/netwerk/system/android',
+ ]
+
+elif CONFIG['OS_ARCH'] == 'Linux':
+ LOCAL_INCLUDES += [
+ '/netwerk/system/linux',
+ ]
+
+if CONFIG['NECKO_COOKIES']:
+ LOCAL_INCLUDES += [
+ '/netwerk/cookie',
+ ]
+
+if CONFIG['NECKO_WIFI']:
+ LOCAL_INCLUDES += [
+ '/netwerk/wifi',
+ ]
+
+if CONFIG['MOZ_RTSP']:
+ LOCAL_INCLUDES += [
+ '/netwerk/protocol/rtsp',
+ ]
+
+LOCAL_INCLUDES += [
+ '!/netwerk/dns',
+]
diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h
new file mode 100644
index 0000000000..02ba7307eb
--- /dev/null
+++ b/netwerk/build/nsNetCID.h
@@ -0,0 +1,1079 @@
+/* -*- 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 nsNetCID_h__
+#define nsNetCID_h__
+
+
+/******************************************************************************
+ * netwerk/base/ classes
+ */
+
+// service implementing nsIIOService and nsIIOService2.
+#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} \
+}
+
+// service implementing nsIProxyAutoConfig.
+#define NS_PROXYAUTOCONFIG_CONTRACTID \
+ "@mozilla.org/network/proxy-auto-config;1"
+#define NS_PROXYAUTOCONFIG_CID \
+{ /* 63ac8c66-1dd2-11b2-b070-84d00d3eaece */ \
+ 0x63ac8c66, \
+ 0x1dd2, \
+ 0x11b2, \
+ {0xb0, 0x70, 0x84, 0xd0, 0x0d, 0x3e, 0xae, 0xce} \
+}
+
+// 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_CONTRACTID \
+ "@mozilla.org/network/simple-uri;1"
+#define NS_SIMPLEURI_CID \
+{ /* e0da1d70-2f7b-11d3-8cd0-0060b0fc14a3 */ \
+ 0xe0da1d70, \
+ 0x2f7b, \
+ 0x11d3, \
+ {0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \
+}
+
+// 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 } \
+}
+
+// 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 } \
+}
+
+// component implementing nsIStandardURL, nsIURI, nsIURL, nsISerializable,
+// and nsIClassInfo.
+#define NS_STANDARDURL_CONTRACTID \
+ "@mozilla.org/network/standard-url;1"
+#define NS_STANDARDURL_CID \
+{ /* de9472d0-8034-11d3-9399-00104ba0fd40 */ \
+ 0xde9472d0, \
+ 0x8034, \
+ 0x11d3, \
+ {0x93, 0x99, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \
+}
+
+// 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 nsIRequestObserverProxy.
+#define NS_REQUESTOBSERVERPROXY_CONTRACTID \
+ "@mozilla.org/network/request-observer-proxy;1"
+#define NS_REQUESTOBSERVERPROXY_CID \
+{ /* 51fa28c7-74c0-4b85-9c46-d03faa7b696b */ \
+ 0x51fa28c7, \
+ 0x74c0, \
+ 0x4b85, \
+ {0x9c, 0x46, 0xd0, 0x3f, 0xaa, 0x7b, 0x69, 0x6b} \
+}
+
+// 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 \
+{ /* 5BA6D920-D4E9-11d3-A1A5-0050041CAF44 */ \
+ 0x5ba6d920, \
+ 0xd4e9, \
+ 0x11d3, \
+ { 0xa1, 0xa5, 0x0, 0x50, 0x4, 0x1c, 0xaf, 0x44 } \
+}
+
+// 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 nsISyncStreamListener.
+#define NS_SYNCSTREAMLISTENER_CONTRACTID \
+ "@mozilla.org/network/sync-stream-listener;1"
+#define NS_SYNCSTREAMLISTENER_CID \
+{ /* 439400d3-6f23-43db-8b06-8aafe1869bd8 */ \
+ 0x439400d3, \
+ 0x6f23, \
+ 0x43db, \
+ {0x8b, 0x06, 0x8a, 0xaf, 0xe1, 0x86, 0x9b, 0xd8} \
+}
+
+// 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"
+
+// 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_PARTIALLOCALFILEINPUTSTREAM_CONTRACTID \
+ "@mozilla.org/network/partial-file-input-stream;1"
+#define NS_PARTIALLOCALFILEINPUTSTREAM_CID \
+{ /* 8738afd6-162a-418d-a99b-75b3a6b10a56 */ \
+ 0x8738afd6, \
+ 0x162a, \
+ 0x418d, \
+ {0xa9, 0x9b, 0x75, 0xb3, 0xa6, 0xb1, 0x0a, 0x56} \
+}
+
+#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} \
+}
+
+// component implementing nsIFileStream
+#define NS_LOCALFILESTREAM_CONTRACTID \
+ "@mozilla.org/network/file-stream;1"
+#define NS_LOCALFILESTREAM_CID \
+{ /* f8a69bd7-176c-4a60-9a05-b6d92f8f229a */ \
+ 0xf8a69bd7, \
+ 0x176c, \
+ 0x4a60, \
+ {0x9a, 0x05, 0xb6, 0xd9, 0x2f, 0x8f, 0x22, 0x9a} \
+}
+
+/**
+ * Contract ID for a service implementing nsIURIClassifier that identifies
+ * phishing and malware sites.
+ */
+#define NS_URICLASSIFIERSERVICE_CONTRACTID \
+ "@mozilla.org/uriclassifierservice"
+
+// Redirect channel registrar used for redirect to various protocols
+#define NS_REDIRECTCHANNELREGISTRAR_CONTRACTID \
+ "@mozilla.org/redirectchannelregistrar;1"
+#define NS_REDIRECTCHANNELREGISTRAR_CID \
+{ /* {b69043a6-8929-4d60-8d17-a27e44a8393e} */ \
+ 0xb69043a6, \
+ 0x8929, \
+ 0x4d60, \
+ { 0x8d, 0x17, 0xa2, 0x7e, 0x44, 0xa8, 0x39, 0x3e } \
+}
+
+// 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 } \
+}
+
+// service implementing nsIRequestContextService
+#define NS_REQUESTCONTEXTSERVICE_CONTRACTID \
+ "@mozilla.org/network/request-context-service;1"
+#define NS_REQUESTCONTEXTSERVICE_CID \
+{ /* d5499fa7-7ba8-49ff-9e30-1858b99ace69 */ \
+ 0xd5499fa7, \
+ 0x7ba8, \
+ 0x49ff, \
+ {0x93, 0x30, 0x18, 0x58, 0xb9, 0x9a, 0xce, 0x69} \
+}
+
+/******************************************************************************
+ * 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} \
+}
+
+#define NS_APPLICATIONCACHENAMESPACE_CONTRACTID \
+ "@mozilla.org/network/application-cache-namespace;1"
+#define NS_APPLICATIONCACHENAMESPACE_CID \
+{ /* b00ed78a-04e2-4f74-8e1c-d1af79dfd12f */ \
+ 0xb00ed78a, \
+ 0x04e2, \
+ 0x4f74, \
+ {0x8e, 0x1c, 0xd1, 0xaf, 0x79, 0xdf, 0xd1, 0x2f} \
+}
+
+#define NS_APPLICATIONCACHE_CONTRACTID \
+ "@mozilla.org/network/application-cache;1"
+
+#define NS_APPLICATIONCACHE_CID \
+{ /* 463440c5-baad-4f3c-9e50-0b107abe7183 */ \
+ 0x463440c5, \
+ 0xbaad, \
+ 0x4f3c, \
+ {0x9e, 0x50, 0xb, 0x10, 0x7a, 0xbe, 0x71, 0x83 } \
+}
+
+/******************************************************************************
+ * 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_HTTPCHANNELAUTHPROVIDER_CONTRACTID \
+ "@mozilla.org/network/http-channel-auth-provider;1"
+#define NS_HTTPCHANNELAUTHPROVIDER_CID \
+{ /* 02f5a8d8-4ef3-48b1-b527-8a643056abbd */ \
+ 0x02f5a8d8, \
+ 0x4ef3, \
+ 0x48b1, \
+ {0xb5, 0x27, 0x8a, 0x64, 0x30, 0x56, 0xab, 0xbd} \
+}
+
+// component implementing nsIHttpPushListener.
+#define NS_HTTPPUSHLISTENER_CONTRACTID \
+ "@mozilla.org/network/push-listener;1"
+#define NS_HTTPPUSHLISTENER_CID \
+{ \
+ 0x73cf4430, \
+ 0x5877, \
+ 0x4c6b, \
+ {0xb8, 0x78, 0x3e, 0xde, 0x5b, 0xc8, 0xef, 0xf1} \
+}
+
+
+#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 } \
+}
+
+/******************************************************************************
+ * 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/device classes
+ */
+#define NS_DEVICEPROTOCOLHANDLER_CID \
+{ /* 6b0ffe9e-d114-486b-aeb7-da62e7273ed5 */ \
+ 0x60ffe9e, \
+ 0xd114, \
+ 0x486b, \
+ {0xae, 0xb7, 0xda, 0x62, 0xe7, 0x27, 0x3e, 0xd5} \
+}
+
+/******************************************************************************
+ * 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/wyciwyg/ classes
+ */
+
+#define NS_WYCIWYGPROTOCOLHANDLER_CID \
+{ /* {0xe7509b46-2eB2-410a-9d7c-c3ce73284d01} */ \
+ 0xe7509b46, \
+ 0x2eb2, \
+ 0x410a, \
+ {0x9d, 0x7c, 0xc3, 0xce, 0x73, 0x28, 0x4d, 0x01} \
+}
+
+/******************************************************************************
+ * 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/rtsp / classes
+ */
+
+#define NS_RTSPPROTOCOLHANDLER_CID \
+{ /* {5bb4b980-7b10-11e2-b92a-0800200c9a66} */ \
+ 0x5bb4b980, \
+ 0x7b10, \
+ 0x11e2, \
+ {0xb9, 0x2a, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66} \
+}
+
+/******************************************************************************
+ * 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_SOCKETPROVIDERSERVICE_CONTRACTID \
+ "@mozilla.org/network/socket-provider-service;1"
+#define NS_SOCKETPROVIDERSERVICE_CID \
+{ /* ed394ba0-5472-11d3-bbc8-0000861d1237 */ \
+ 0xed394ba0, \
+ 0x5472, \
+ 0x11d3, \
+ { 0xbb, 0xc8, 0x00, 0x00, 0x86, 0x1d, 0x12, 0x37 } \
+}
+
+#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_SSLSOCKETPROVIDER_CONTRACTID \
+ NS_NETWORK_SOCKET_CONTRACTID_PREFIX "ssl"
+
+/* This code produces a normal socket which can be used to initiate the
+ * STARTTLS protocol by calling its nsISSLSocketControl->StartTLS()
+ */
+#define NS_STARTTLSSOCKETPROVIDER_CONTRACTID \
+ NS_NETWORK_SOCKET_CONTRACTID_PREFIX "starttls"
+
+
+#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 } \
+}
+
+#ifdef XP_WIN
+#define NS_NAMEDPIPESERVICE_CONTRACTID \
+ "@mozilla.org/network/named-pipe-service;1"
+#define NS_NAMEDPIPESERVICE_CID \
+{ \
+ 0xae298cf9, \
+ 0x91f4, \
+ 0x4337, \
+ { 0x95, 0x69, 0x61, 0x88, 0xb9, 0xd0, 0x21, 0x6e } \
+}
+#endif
+
+/******************************************************************************
+ * netwerk/cookie classes
+ */
+
+// service implementing nsICookieManager and nsICookieManager2.
+#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} \
+}
+
+/**
+ * General-purpose content sniffer component. Use with CreateInstance.
+ *
+ * Implements nsIContentSniffer
+ */
+#define NS_GENERIC_CONTENT_SNIFFER \
+ "@mozilla.org/network/content-sniffer;1"
+
+/**
+ * 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..d244a14f12
--- /dev/null
+++ b/netwerk/build/nsNetModule.cpp
@@ -0,0 +1,1190 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 "necko-config.h"
+
+#define ALLOW_LATE_HTTPLOG_H_INCLUDE 1
+#include "base/basictypes.h"
+
+#include "nsCOMPtr.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsICategoryManager.h"
+#include "nsSocketProviderService.h"
+#include "nscore.h"
+#include "nsSimpleURI.h"
+#include "nsSimpleNestedURI.h"
+#include "nsLoadGroup.h"
+#include "nsStreamLoader.h"
+#include "nsIncrementalStreamLoader.h"
+#include "nsUnicharStreamLoader.h"
+#include "nsFileStreams.h"
+#include "nsBufferedStreams.h"
+#include "nsMIMEInputStream.h"
+#include "nsSOCKSSocketProvider.h"
+#include "nsCacheService.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsApplicationCache.h"
+#include "nsApplicationCacheService.h"
+#include "nsMimeTypes.h"
+#include "nsDNSPrefetch.h"
+#include "nsAboutProtocolHandler.h"
+#include "nsXULAppAPI.h"
+#include "nsCategoryCache.h"
+#include "nsIContentSniffer.h"
+#include "Predictor.h"
+#include "nsIThreadPool.h"
+#include "mozilla/net/NeckoChild.h"
+
+#include "nsNetCID.h"
+
+#ifndef XP_MACOSX
+#define BUILD_BINHEX_DECODER 1
+#endif
+
+#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
+#define BUILD_NETWORK_INFO_SERVICE 1
+#endif
+
+typedef nsCategoryCache<nsIContentSniffer> ContentSnifferCache;
+ContentSnifferCache* gNetSniffers = nullptr;
+ContentSnifferCache* gDataSniffers = nullptr;
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "nsIOService.h"
+typedef mozilla::net::nsIOService nsIOService;
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIOService, nsIOService::GetInstance)
+
+#include "nsDNSService2.h"
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIDNSService,
+ nsDNSService::GetXPCOMSingleton)
+
+#include "nsProtocolProxyService.h"
+typedef mozilla::net::nsProtocolProxyService nsProtocolProxyService;
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsProtocolProxyService, Init)
+
+#include "nsStreamTransportService.h"
+typedef mozilla::net::nsStreamTransportService nsStreamTransportService;
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsStreamTransportService, Init)
+
+#include "nsSocketTransportService2.h"
+typedef mozilla::net::nsSocketTransportService nsSocketTransportService;
+#undef LOG
+#undef LOG_ENABLED
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSocketTransportService, Init)
+
+#include "nsServerSocket.h"
+typedef mozilla::net::nsServerSocket nsServerSocket;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsServerSocket)
+
+#include "TLSServerSocket.h"
+typedef mozilla::net::TLSServerSocket TLSServerSocket;
+NS_GENERIC_FACTORY_CONSTRUCTOR(TLSServerSocket)
+
+#include "nsUDPSocket.h"
+typedef mozilla::net::nsUDPSocket nsUDPSocket;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsUDPSocket)
+
+#include "nsUDPSocketProvider.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsUDPSocketProvider)
+
+#include "nsAsyncStreamCopier.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAsyncStreamCopier)
+
+#include "nsInputStreamPump.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsInputStreamPump)
+
+#include "nsInputStreamChannel.h"
+typedef mozilla::net::nsInputStreamChannel nsInputStreamChannel;
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsInputStreamChannel, Init)
+
+#include "nsDownloader.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloader)
+
+#include "BackgroundFileSaver.h"
+namespace mozilla {
+namespace net {
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(BackgroundFileSaverOutputStream, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(BackgroundFileSaverStreamListener, Init)
+} // namespace net
+} // namespace mozilla
+
+#include "nsSyncStreamListener.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSyncStreamListener, Init)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAtomicFileOutputStream)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeFileOutputStream)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFileStream)
+
+typedef mozilla::net::nsLoadGroup nsLoadGroup;
+NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsLoadGroup, Init)
+
+#include "ArrayBufferInputStream.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(ArrayBufferInputStream)
+
+#include "nsEffectiveTLDService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsEffectiveTLDService, Init)
+
+#include "nsSerializationHelper.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSerializationHelper)
+
+#include "RedirectChannelRegistrar.h"
+typedef mozilla::net::RedirectChannelRegistrar RedirectChannelRegistrar;
+NS_GENERIC_FACTORY_CONSTRUCTOR(RedirectChannelRegistrar)
+
+#include "CacheStorageService.h"
+typedef mozilla::net::CacheStorageService CacheStorageService;
+NS_GENERIC_FACTORY_CONSTRUCTOR(CacheStorageService)
+
+#include "LoadContextInfo.h"
+typedef mozilla::net::LoadContextInfoFactory LoadContextInfoFactory;
+NS_GENERIC_FACTORY_CONSTRUCTOR(LoadContextInfoFactory)
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "mozilla/net/CaptivePortalService.h"
+namespace mozilla {
+namespace net {
+ NS_GENERIC_FACTORY_CONSTRUCTOR(CaptivePortalService)
+} // namespace net
+} // namespace mozilla
+
+#include "RequestContextService.h"
+typedef mozilla::net::RequestContextService RequestContextService;
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(RequestContextService, Init)
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern nsresult
+net_NewIncrementalDownload(nsISupports *, const nsIID &, void **);
+
+#define NS_INCREMENTALDOWNLOAD_CID \
+{ /* a62af1ba-79b3-4896-8aaf-b148bfce4280 */ \
+ 0xa62af1ba, \
+ 0x79b3, \
+ 0x4896, \
+ {0x8a, 0xaf, 0xb1, 0x48, 0xbf, 0xce, 0x42, 0x80} \
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "nsMIMEHeaderParamImpl.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMIMEHeaderParamImpl)
+///////////////////////////////////////////////////////////////////////////////
+
+#include "nsRequestObserverProxy.h"
+#include "nsSimpleStreamListener.h"
+#include "nsDirIndexParser.h"
+#include "nsDirIndex.h"
+
+typedef mozilla::net::nsRequestObserverProxy nsRequestObserverProxy;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsRequestObserverProxy)
+typedef mozilla::net::nsSimpleStreamListener nsSimpleStreamListener;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSimpleStreamListener)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDirIndexParser, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDirIndex)
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "nsStreamListenerTee.h"
+typedef mozilla::net::nsStreamListenerTee nsStreamListenerTee;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStreamListenerTee)
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef NECKO_COOKIES
+#include "nsCookieService.h"
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsICookieService,
+ nsCookieService::GetXPCOMSingleton)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+#ifdef NECKO_WIFI
+
+#include "nsWifiMonitor.h"
+#undef LOG
+#undef LOG_ENABLED
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWifiMonitor)
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// protocols
+///////////////////////////////////////////////////////////////////////////////
+
+// about:blank is mandatory
+#include "nsAboutProtocolHandler.h"
+#include "nsAboutBlank.h"
+typedef mozilla::net::nsAboutProtocolHandler nsAboutProtocolHandler;
+typedef mozilla::net::nsSafeAboutProtocolHandler nsSafeAboutProtocolHandler;
+typedef mozilla::net::nsNestedAboutURI nsNestedAboutURI;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAboutProtocolHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeAboutProtocolHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNestedAboutURI)
+
+#ifdef NECKO_PROTOCOL_about
+// about
+#include "nsAboutCache.h"
+#include "nsAboutCacheEntry.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAboutCacheEntry)
+#endif
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsApplicationCacheService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsApplicationCacheNamespace)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsApplicationCache)
+
+#ifdef NECKO_PROTOCOL_file
+// file
+#include "nsFileProtocolHandler.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsFileProtocolHandler, Init)
+#endif
+
+#ifdef NECKO_PROTOCOL_ftp
+// ftp
+#include "nsFtpProtocolHandler.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsFtpProtocolHandler, Init)
+#endif
+
+#ifdef NECKO_PROTOCOL_http
+// http/https
+#include "nsHttpHandler.h"
+#include "Http2Compression.h"
+#undef LOG
+#undef LOG_ENABLED
+#include "nsHttpAuthManager.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsHttpBasicAuth.h"
+#include "nsHttpDigestAuth.h"
+#include "nsHttpNTLMAuth.h"
+#include "nsHttpActivityDistributor.h"
+#include "ThrottleQueue.h"
+#undef LOG
+#undef LOG_ENABLED
+namespace mozilla {
+namespace net {
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpNTLMAuth)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHttpHandler, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHttpsHandler, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHttpAuthManager, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpChannelAuthProvider)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpActivityDistributor)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpBasicAuth)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDigestAuth)
+NS_GENERIC_FACTORY_CONSTRUCTOR(ThrottleQueue)
+} // namespace net
+} // namespace mozilla
+#endif // !NECKO_PROTOCOL_http
+
+#include "mozilla/net/Dashboard.h"
+namespace mozilla {
+namespace net {
+ NS_GENERIC_FACTORY_CONSTRUCTOR(Dashboard)
+} // namespace net
+} // namespace mozilla
+
+#ifdef XP_WIN
+#include "../socket/nsNamedPipeService.h"
+namespace mozilla {
+namespace net {
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NamedPipeService, Init)
+} // namespace net
+} // namespace mozilla
+#endif
+
+#ifdef NECKO_PROTOCOL_res
+// resource
+#include "nsResProtocolHandler.h"
+#include "ExtensionProtocolHandler.h"
+#include "SubstitutingProtocolHandler.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsResProtocolHandler, Init)
+
+namespace mozilla {
+NS_GENERIC_FACTORY_CONSTRUCTOR(ExtensionProtocolHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(SubstitutingURL)
+} // namespace mozilla
+#endif
+
+#ifdef NECKO_PROTOCOL_device
+#include "nsDeviceProtocolHandler.h"
+typedef mozilla::net::nsDeviceProtocolHandler nsDeviceProtocolHandler;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceProtocolHandler)
+#endif
+
+#ifdef NECKO_PROTOCOL_viewsource
+#include "nsViewSourceHandler.h"
+typedef mozilla::net::nsViewSourceHandler nsViewSourceHandler;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsViewSourceHandler)
+#endif
+
+#ifdef NECKO_PROTOCOL_data
+#include "nsDataHandler.h"
+#endif
+
+#ifdef NECKO_PROTOCOL_wyciwyg
+#include "nsWyciwygProtocolHandler.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWyciwygProtocolHandler)
+#endif
+
+#ifdef NECKO_PROTOCOL_websocket
+#include "WebSocketChannel.h"
+#include "WebSocketChannelChild.h"
+namespace mozilla {
+namespace net {
+static BaseWebSocketChannel*
+WebSocketChannelConstructor(bool aSecure)
+{
+ if (IsNeckoChild()) {
+ return new WebSocketChannelChild(aSecure);
+ }
+
+ if (aSecure) {
+ return new WebSocketSSLChannel;
+ } else {
+ return new WebSocketChannel;
+ }
+}
+
+#define WEB_SOCKET_HANDLER_CONSTRUCTOR(type, secure) \
+static nsresult \
+type##Constructor(nsISupports *aOuter, REFNSIID aIID, \
+ void **aResult) \
+{ \
+ nsresult rv; \
+ \
+ BaseWebSocketChannel * inst; \
+ \
+ *aResult = nullptr; \
+ if (nullptr != aOuter) { \
+ rv = NS_ERROR_NO_AGGREGATION; \
+ return rv; \
+ } \
+ inst = WebSocketChannelConstructor(secure); \
+ NS_ADDREF(inst); \
+ rv = inst->QueryInterface(aIID, aResult); \
+ NS_RELEASE(inst); \
+ return rv; \
+}
+
+WEB_SOCKET_HANDLER_CONSTRUCTOR(WebSocketChannel, false)
+WEB_SOCKET_HANDLER_CONSTRUCTOR(WebSocketSSLChannel, true)
+#undef WEB_SOCKET_HANDLER_CONSTRUCTOR
+} // namespace net
+} // namespace mozilla
+#endif
+
+#ifdef NECKO_PROTOCOL_rtsp
+#include "RtspHandler.h"
+namespace mozilla {
+namespace net {
+NS_GENERIC_FACTORY_CONSTRUCTOR(RtspHandler)
+} // namespace mozilla::net
+} // namespace mozilla
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "nsURLParsers.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNoAuthURLParser)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAuthURLParser)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStdURLParser)
+
+#include "nsStandardURL.h"
+typedef mozilla::net::nsStandardURL nsStandardURL;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStandardURL)
+typedef mozilla::net::nsSimpleURI nsSimpleURI;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSimpleURI)
+
+typedef mozilla::net::nsSimpleNestedURI nsSimpleNestedURI;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSimpleNestedURI)
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "nsIDNService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsIDNService, Init)
+
+///////////////////////////////////////////////////////////////////////////////
+#if defined(XP_WIN)
+#include "nsNotifyAddrListener.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNotifyAddrListener, Init)
+#elif defined(MOZ_WIDGET_COCOA)
+#include "nsNetworkLinkService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNetworkLinkService, Init)
+#elif defined(MOZ_WIDGET_ANDROID)
+#include "nsAndroidNetworkLinkService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroidNetworkLinkService)
+#elif defined(XP_LINUX)
+#include "nsNotifyAddrListener_Linux.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNotifyAddrListener, Init)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef NECKO_PROTOCOL_ftp
+#include "nsFTPDirListingConv.h"
+nsresult NS_NewFTPDirListingConv(nsFTPDirListingConv** result);
+#endif
+
+#include "nsStreamConverterService.h"
+#include "nsMultiMixedConv.h"
+#include "nsHTTPCompressConv.h"
+#include "mozTXTToHTMLConv.h"
+#include "nsUnknownDecoder.h"
+
+#include "nsTXTToHTMLConv.h"
+namespace mozilla {
+namespace net {
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsTXTToHTMLConv, Init)
+} // namespace net
+} // namespace mozilla
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef BUILD_NETWORK_INFO_SERVICE
+#include "nsNetworkInfoService.h"
+typedef mozilla::net::nsNetworkInfoService nsNetworkInfoService;
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNetworkInfoService, Init)
+#endif // BUILD_NETWORK_INFO_SERVICE
+
+
+#include "nsIndexedToHTML.h"
+#ifdef BUILD_BINHEX_DECODER
+#include "nsBinHexDecoder.h"
+#endif
+
+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"
+#define PLAIN_TO_HTML "?from=text/plain&to=text/html"
+
+#ifdef BUILD_BINHEX_DECODER
+#define BINHEX_TO_WILD "?from=application/mac-binhex40&to=*/*"
+#endif
+
+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, "" },
+#ifdef BUILD_BINHEX_DECODER
+ { NS_ISTREAMCONVERTER_KEY, BINHEX_TO_WILD, "" },
+#endif
+ { NS_ISTREAMCONVERTER_KEY, PLAIN_TO_HTML, "" },
+ NS_BINARYDETECTOR_CATEGORYENTRY,
+ { nullptr }
+};
+
+#ifdef BUILD_BINHEX_DECODER
+typedef mozilla::net::nsBinHexDecoder nsBinHexDecoder;
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinHexDecoder)
+#endif
+
+static nsresult
+CreateNewStreamConvServiceFactory(nsISupports* aOuter, REFNSIID aIID, void **aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsStreamConverterService* inst = nullptr;
+ nsresult rv = NS_NewStreamConv(&inst);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ NS_RELEASE(inst); /* get rid of extra refcnt */
+ return rv;
+}
+
+#ifdef NECKO_PROTOCOL_ftp
+static nsresult
+CreateNewFTPDirListingConv(nsISupports* aOuter, REFNSIID aIID, void **aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsFTPDirListingConv* inst = nullptr;
+ nsresult rv = NS_NewFTPDirListingConv(&inst);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ NS_RELEASE(inst); /* get rid of extra refcnt */
+ return rv;
+}
+#endif
+
+static nsresult
+CreateNewMultiMixedConvFactory(nsISupports* aOuter, REFNSIID aIID, void **aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsMultiMixedConv* inst = nullptr;
+ nsresult rv = NS_NewMultiMixedConv(&inst);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ NS_RELEASE(inst); /* get rid of extra refcnt */
+ return rv;
+}
+
+static nsresult
+CreateNewTXTToHTMLConvFactory(nsISupports* aOuter, REFNSIID aIID, void **aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ mozTXTToHTMLConv* inst = nullptr;
+ nsresult rv = MOZ_NewTXTToHTMLConv(&inst);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ NS_RELEASE(inst); /* get rid of extra refcnt */
+ return rv;
+}
+
+static nsresult
+CreateNewHTTPCompressConvFactory (nsISupports* aOuter, REFNSIID aIID, void **aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ mozilla::net::nsHTTPCompressConv* inst = nullptr;
+ nsresult rv = NS_NewHTTPCompressConv (&inst);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ rv = inst->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ }
+ NS_RELEASE(inst); /* get rid of extra refcnt */
+ return rv;
+}
+
+static nsresult
+CreateNewUnknownDecoderFactory(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ nsresult rv;
+
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aResult = nullptr;
+
+ if (aOuter) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ nsUnknownDecoder *inst;
+
+ inst = new nsUnknownDecoder();
+ if (!inst) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ADDREF(inst);
+ rv = inst->QueryInterface(aIID, aResult);
+ NS_RELEASE(inst);
+
+ return rv;
+}
+
+static nsresult
+CreateNewBinaryDetectorFactory(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ nsresult rv;
+
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aResult = nullptr;
+
+ if (aOuter) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ auto* inst = new nsBinaryDetector();
+ if (!inst) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ADDREF(inst);
+ rv = inst->QueryInterface(aIID, aResult);
+ NS_RELEASE(inst);
+
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Module implementation for the net library
+
+// Net module startup hook
+static nsresult nsNetStartup()
+{
+ return NS_OK;
+}
+
+// Net module shutdown hook
+static void nsNetShutdown()
+{
+ // Release the url parser that the stdurl is holding.
+ 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();
+
+#ifdef NECKO_PROTOCOL_websocket
+ // Release the Websocket Admission Manager
+ mozilla::net::WebSocketChannel::Shutdown();
+#endif // NECKO_PROTOCOL_websocket
+
+#ifdef NECKO_PROTOCOL_http
+ mozilla::net::Http2CompressionCleanup();
+#endif // NECKO_PROTOCOL_http
+
+ delete gNetSniffers;
+ gNetSniffers = nullptr;
+ delete gDataSniffers;
+ gDataSniffers = nullptr;
+}
+
+NS_DEFINE_NAMED_CID(NS_IOSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_STREAMTRANSPORTSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SOCKETTRANSPORTSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SERVERSOCKET_CID);
+NS_DEFINE_NAMED_CID(NS_TLSSERVERSOCKET_CID);
+NS_DEFINE_NAMED_CID(NS_UDPSOCKET_CID);
+NS_DEFINE_NAMED_CID(NS_SOCKETPROVIDERSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_DNSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_IDNSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_EFFECTIVETLDSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SIMPLEURI_CID);
+NS_DEFINE_NAMED_CID(NS_SIMPLENESTEDURI_CID);
+NS_DEFINE_NAMED_CID(NS_ASYNCSTREAMCOPIER_CID);
+NS_DEFINE_NAMED_CID(NS_INPUTSTREAMPUMP_CID);
+NS_DEFINE_NAMED_CID(NS_INPUTSTREAMCHANNEL_CID);
+NS_DEFINE_NAMED_CID(NS_STREAMLOADER_CID);
+NS_DEFINE_NAMED_CID(NS_INCREMENTALSTREAMLOADER_CID);
+NS_DEFINE_NAMED_CID(NS_UNICHARSTREAMLOADER_CID);
+NS_DEFINE_NAMED_CID(NS_DOWNLOADER_CID);
+NS_DEFINE_NAMED_CID(NS_BACKGROUNDFILESAVEROUTPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CID);
+NS_DEFINE_NAMED_CID(NS_SYNCSTREAMLISTENER_CID);
+NS_DEFINE_NAMED_CID(NS_REQUESTOBSERVERPROXY_CID);
+NS_DEFINE_NAMED_CID(NS_SIMPLESTREAMLISTENER_CID);
+NS_DEFINE_NAMED_CID(NS_STREAMLISTENERTEE_CID);
+NS_DEFINE_NAMED_CID(NS_LOADGROUP_CID);
+NS_DEFINE_NAMED_CID(NS_LOCALFILEINPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_LOCALFILEOUTPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_PARTIALLOCALFILEINPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_ATOMICLOCALFILEOUTPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_SAFELOCALFILEOUTPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_LOCALFILESTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_INCREMENTALDOWNLOAD_CID);
+NS_DEFINE_NAMED_CID(NS_STDURLPARSER_CID);
+NS_DEFINE_NAMED_CID(NS_NOAUTHURLPARSER_CID);
+NS_DEFINE_NAMED_CID(NS_AUTHURLPARSER_CID);
+NS_DEFINE_NAMED_CID(NS_STANDARDURL_CID);
+NS_DEFINE_NAMED_CID(NS_ARRAYBUFFERINPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_BUFFEREDINPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_BUFFEREDOUTPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_MIMEINPUTSTREAM_CID);
+NS_DEFINE_NAMED_CID(NS_PROTOCOLPROXYSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_STREAMCONVERTERSERVICE_CID);
+#if defined(XP_WIN)
+NS_DEFINE_NAMED_CID(NS_NAMEDPIPESERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_DASHBOARD_CID);
+#ifdef NECKO_PROTOCOL_ftp
+NS_DEFINE_NAMED_CID(NS_FTPDIRLISTINGCONVERTER_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_NSINDEXEDTOHTMLCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_DIRINDEXPARSER_CID);
+NS_DEFINE_NAMED_CID(NS_MULTIMIXEDCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_UNKNOWNDECODER_CID);
+NS_DEFINE_NAMED_CID(NS_BINARYDETECTOR_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPCOMPRESSCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_NSTXTTOHTMLCONVERTER_CID);
+#ifdef BUILD_BINHEX_DECODER
+NS_DEFINE_NAMED_CID(NS_BINHEXDECODER_CID);
+#endif
+NS_DEFINE_NAMED_CID(MOZITXTTOHTMLCONV_CID);
+NS_DEFINE_NAMED_CID(NS_DIRINDEX_CID);
+NS_DEFINE_NAMED_CID(NS_MIMEHEADERPARAM_CID);
+#ifdef NECKO_PROTOCOL_file
+NS_DEFINE_NAMED_CID(NS_FILEPROTOCOLHANDLER_CID);
+#endif
+#ifdef NECKO_PROTOCOL_http
+NS_DEFINE_NAMED_CID(NS_HTTPPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPSPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPBASICAUTH_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPDIGESTAUTH_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPNTLMAUTH_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPAUTHMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPCHANNELAUTHPROVIDER_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPACTIVITYDISTRIBUTOR_CID);
+NS_DEFINE_NAMED_CID(NS_THROTTLEQUEUE_CID);
+#endif // !NECKO_PROTOCOL_http
+#ifdef NECKO_PROTOCOL_ftp
+NS_DEFINE_NAMED_CID(NS_FTPPROTOCOLHANDLER_CID);
+#endif
+#ifdef NECKO_PROTOCOL_res
+NS_DEFINE_NAMED_CID(NS_RESPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_EXTENSIONPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_SUBSTITUTINGURL_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_ABOUTPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_SAFEABOUTPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_ABOUT_BLANK_MODULE_CID);
+NS_DEFINE_NAMED_CID(NS_NESTEDABOUTURI_CID);
+#ifdef NECKO_PROTOCOL_about
+NS_DEFINE_NAMED_CID(NS_ABOUT_CACHE_MODULE_CID);
+NS_DEFINE_NAMED_CID(NS_ABOUT_CACHE_ENTRY_MODULE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_SOCKSSOCKETPROVIDER_CID);
+NS_DEFINE_NAMED_CID(NS_SOCKS4SOCKETPROVIDER_CID);
+NS_DEFINE_NAMED_CID(NS_UDPSOCKETPROVIDER_CID);
+NS_DEFINE_NAMED_CID(NS_CACHESERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHESERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHENAMESPACE_CID);
+NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHE_CID);
+#ifdef NECKO_COOKIES
+NS_DEFINE_NAMED_CID(NS_COOKIEMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_COOKIESERVICE_CID);
+#endif
+#ifdef NECKO_WIFI
+NS_DEFINE_NAMED_CID(NS_WIFI_MONITOR_COMPONENT_CID);
+#endif
+#ifdef NECKO_PROTOCOL_data
+NS_DEFINE_NAMED_CID(NS_DATAPROTOCOLHANDLER_CID);
+#endif
+#ifdef NECKO_PROTOCOL_device
+NS_DEFINE_NAMED_CID(NS_DEVICEPROTOCOLHANDLER_CID);
+#endif
+#ifdef NECKO_PROTOCOL_viewsource
+NS_DEFINE_NAMED_CID(NS_VIEWSOURCEHANDLER_CID);
+#endif
+#ifdef NECKO_PROTOCOL_wyciwyg
+NS_DEFINE_NAMED_CID(NS_WYCIWYGPROTOCOLHANDLER_CID);
+#endif
+#ifdef NECKO_PROTOCOL_websocket
+NS_DEFINE_NAMED_CID(NS_WEBSOCKETPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_WEBSOCKETSSLPROTOCOLHANDLER_CID);
+#endif
+#ifdef NECKO_PROTOCOL_rtsp
+NS_DEFINE_NAMED_CID(NS_RTSPPROTOCOLHANDLER_CID);
+#endif
+#if defined(XP_WIN)
+NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
+#elif defined(MOZ_WIDGET_COCOA)
+NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
+#elif defined(MOZ_WIDGET_ANDROID)
+NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
+#elif defined(XP_LINUX)
+NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID);
+NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID);
+NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_NSILOADCONTEXTINFOFACTORY_CID);
+NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID);
+NS_DEFINE_NAMED_CID(NS_CAPTIVEPORTAL_CID);
+NS_DEFINE_NAMED_CID(NS_REQUESTCONTEXTSERVICE_CID);
+#ifdef BUILD_NETWORK_INFO_SERVICE
+NS_DEFINE_NAMED_CID(NETWORKINFOSERVICE_CID);
+#endif // BUILD_NETWORK_INFO_SERVICE
+
+static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
+ { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor },
+ { &kNS_STREAMTRANSPORTSERVICE_CID, false, nullptr, nsStreamTransportServiceConstructor },
+ { &kNS_SOCKETTRANSPORTSERVICE_CID, false, nullptr, nsSocketTransportServiceConstructor },
+ { &kNS_SERVERSOCKET_CID, false, nullptr, nsServerSocketConstructor },
+ { &kNS_TLSSERVERSOCKET_CID, false, nullptr, TLSServerSocketConstructor },
+ { &kNS_UDPSOCKET_CID, false, nullptr, nsUDPSocketConstructor },
+ { &kNS_SOCKETPROVIDERSERVICE_CID, false, nullptr, nsSocketProviderService::Create },
+ { &kNS_DNSSERVICE_CID, false, nullptr, nsIDNSServiceConstructor },
+ { &kNS_IDNSERVICE_CID, false, nullptr, nsIDNServiceConstructor },
+ { &kNS_EFFECTIVETLDSERVICE_CID, false, nullptr, nsEffectiveTLDServiceConstructor },
+ { &kNS_SIMPLEURI_CID, false, nullptr, nsSimpleURIConstructor },
+ { &kNS_SIMPLENESTEDURI_CID, false, nullptr, nsSimpleNestedURIConstructor },
+ { &kNS_ASYNCSTREAMCOPIER_CID, false, nullptr, nsAsyncStreamCopierConstructor },
+ { &kNS_INPUTSTREAMPUMP_CID, false, nullptr, nsInputStreamPumpConstructor },
+ { &kNS_INPUTSTREAMCHANNEL_CID, false, nullptr, nsInputStreamChannelConstructor },
+ { &kNS_STREAMLOADER_CID, false, nullptr, mozilla::net::nsStreamLoader::Create },
+ { &kNS_INCREMENTALSTREAMLOADER_CID, false, nullptr, nsIncrementalStreamLoader::Create },
+ { &kNS_UNICHARSTREAMLOADER_CID, false, nullptr, nsUnicharStreamLoader::Create },
+ { &kNS_DOWNLOADER_CID, false, nullptr, nsDownloaderConstructor },
+ { &kNS_BACKGROUNDFILESAVEROUTPUTSTREAM_CID, false, nullptr,
+ mozilla::net::BackgroundFileSaverOutputStreamConstructor },
+ { &kNS_BACKGROUNDFILESAVERSTREAMLISTENER_CID, false, nullptr,
+ mozilla::net::BackgroundFileSaverStreamListenerConstructor },
+ { &kNS_SYNCSTREAMLISTENER_CID, false, nullptr, nsSyncStreamListenerConstructor },
+ { &kNS_REQUESTOBSERVERPROXY_CID, false, nullptr, nsRequestObserverProxyConstructor },
+ { &kNS_SIMPLESTREAMLISTENER_CID, false, nullptr, nsSimpleStreamListenerConstructor },
+ { &kNS_STREAMLISTENERTEE_CID, false, nullptr, nsStreamListenerTeeConstructor },
+ { &kNS_LOADGROUP_CID, false, nullptr, nsLoadGroupConstructor },
+ { &kNS_LOCALFILEINPUTSTREAM_CID, false, nullptr, nsFileInputStream::Create },
+ { &kNS_LOCALFILEOUTPUTSTREAM_CID, false, nullptr, nsFileOutputStream::Create },
+ { &kNS_PARTIALLOCALFILEINPUTSTREAM_CID, false, nullptr, nsPartialFileInputStream::Create },
+ { &kNS_ATOMICLOCALFILEOUTPUTSTREAM_CID, false, nullptr, nsAtomicFileOutputStreamConstructor },
+ { &kNS_SAFELOCALFILEOUTPUTSTREAM_CID, false, nullptr, nsSafeFileOutputStreamConstructor },
+ { &kNS_LOCALFILESTREAM_CID, false, nullptr, nsFileStreamConstructor },
+ { &kNS_INCREMENTALDOWNLOAD_CID, false, nullptr, net_NewIncrementalDownload },
+ { &kNS_STDURLPARSER_CID, false, nullptr, nsStdURLParserConstructor },
+ { &kNS_NOAUTHURLPARSER_CID, false, nullptr, nsNoAuthURLParserConstructor },
+ { &kNS_AUTHURLPARSER_CID, false, nullptr, nsAuthURLParserConstructor },
+ { &kNS_STANDARDURL_CID, false, nullptr, nsStandardURLConstructor },
+ { &kNS_ARRAYBUFFERINPUTSTREAM_CID, false, nullptr, ArrayBufferInputStreamConstructor },
+ { &kNS_BUFFEREDINPUTSTREAM_CID, false, nullptr, nsBufferedInputStream::Create },
+ { &kNS_BUFFEREDOUTPUTSTREAM_CID, false, nullptr, nsBufferedOutputStream::Create },
+ { &kNS_MIMEINPUTSTREAM_CID, false, nullptr, nsMIMEInputStreamConstructor },
+ { &kNS_PROTOCOLPROXYSERVICE_CID, true, nullptr, nsProtocolProxyServiceConstructor },
+ { &kNS_STREAMCONVERTERSERVICE_CID, false, nullptr, CreateNewStreamConvServiceFactory },
+#if defined (XP_WIN)
+ { &kNS_NAMEDPIPESERVICE_CID, false, NULL, mozilla::net::NamedPipeServiceConstructor },
+#endif
+ { &kNS_DASHBOARD_CID, false, nullptr, mozilla::net::DashboardConstructor },
+#ifdef NECKO_PROTOCOL_ftp
+ { &kNS_FTPDIRLISTINGCONVERTER_CID, false, nullptr, CreateNewFTPDirListingConv },
+#endif
+ { &kNS_NSINDEXEDTOHTMLCONVERTER_CID, false, nullptr, nsIndexedToHTML::Create },
+ { &kNS_DIRINDEXPARSER_CID, false, nullptr, nsDirIndexParserConstructor },
+ { &kNS_MULTIMIXEDCONVERTER_CID, false, nullptr, CreateNewMultiMixedConvFactory },
+ { &kNS_UNKNOWNDECODER_CID, false, nullptr, CreateNewUnknownDecoderFactory },
+ { &kNS_BINARYDETECTOR_CID, false, nullptr, CreateNewBinaryDetectorFactory },
+ { &kNS_HTTPCOMPRESSCONVERTER_CID, false, nullptr, CreateNewHTTPCompressConvFactory },
+ { &kNS_NSTXTTOHTMLCONVERTER_CID, false, nullptr, mozilla::net::nsTXTToHTMLConvConstructor },
+#ifdef BUILD_BINHEX_DECODER
+ { &kNS_BINHEXDECODER_CID, false, nullptr, nsBinHexDecoderConstructor },
+#endif
+ { &kMOZITXTTOHTMLCONV_CID, false, nullptr, CreateNewTXTToHTMLConvFactory },
+ { &kNS_DIRINDEX_CID, false, nullptr, nsDirIndexConstructor },
+ { &kNS_MIMEHEADERPARAM_CID, false, nullptr, nsMIMEHeaderParamImplConstructor },
+#ifdef NECKO_PROTOCOL_file
+ { &kNS_FILEPROTOCOLHANDLER_CID, false, nullptr, nsFileProtocolHandlerConstructor },
+#endif
+#ifdef NECKO_PROTOCOL_http
+ { &kNS_HTTPPROTOCOLHANDLER_CID, false, nullptr, mozilla::net::nsHttpHandlerConstructor },
+ { &kNS_HTTPSPROTOCOLHANDLER_CID, false, nullptr, mozilla::net::nsHttpsHandlerConstructor },
+ { &kNS_HTTPBASICAUTH_CID, false, nullptr, mozilla::net::nsHttpBasicAuthConstructor },
+ { &kNS_HTTPDIGESTAUTH_CID, false, nullptr, mozilla::net::nsHttpDigestAuthConstructor },
+ { &kNS_HTTPNTLMAUTH_CID, false, nullptr, mozilla::net::nsHttpNTLMAuthConstructor },
+ { &kNS_HTTPAUTHMANAGER_CID, false, nullptr, mozilla::net::nsHttpAuthManagerConstructor },
+ { &kNS_HTTPCHANNELAUTHPROVIDER_CID, false, nullptr, mozilla::net::nsHttpChannelAuthProviderConstructor },
+ { &kNS_HTTPACTIVITYDISTRIBUTOR_CID, false, nullptr, mozilla::net::nsHttpActivityDistributorConstructor },
+ { &kNS_THROTTLEQUEUE_CID, false, nullptr, mozilla::net::ThrottleQueueConstructor },
+#endif // !NECKO_PROTOCOL_http
+#ifdef NECKO_PROTOCOL_ftp
+ { &kNS_FTPPROTOCOLHANDLER_CID, false, nullptr, nsFtpProtocolHandlerConstructor },
+#endif
+#ifdef NECKO_PROTOCOL_res
+ { &kNS_RESPROTOCOLHANDLER_CID, false, nullptr, nsResProtocolHandlerConstructor },
+ { &kNS_EXTENSIONPROTOCOLHANDLER_CID, false, nullptr, mozilla::ExtensionProtocolHandlerConstructor },
+ { &kNS_SUBSTITUTINGURL_CID, false, nullptr, mozilla::SubstitutingURLConstructor },
+#endif
+ { &kNS_ABOUTPROTOCOLHANDLER_CID, false, nullptr, nsAboutProtocolHandlerConstructor },
+ { &kNS_SAFEABOUTPROTOCOLHANDLER_CID, false, nullptr, nsSafeAboutProtocolHandlerConstructor },
+ { &kNS_ABOUT_BLANK_MODULE_CID, false, nullptr, nsAboutBlank::Create },
+ { &kNS_NESTEDABOUTURI_CID, false, nullptr, nsNestedAboutURIConstructor },
+#ifdef NECKO_PROTOCOL_about
+ { &kNS_ABOUT_CACHE_MODULE_CID, false, nullptr, nsAboutCache::Create },
+ { &kNS_ABOUT_CACHE_ENTRY_MODULE_CID, false, nullptr, nsAboutCacheEntryConstructor },
+#endif
+ { &kNS_SOCKSSOCKETPROVIDER_CID, false, nullptr, nsSOCKSSocketProvider::CreateV5 },
+ { &kNS_SOCKS4SOCKETPROVIDER_CID, false, nullptr, nsSOCKSSocketProvider::CreateV4 },
+ { &kNS_UDPSOCKETPROVIDER_CID, false, nullptr, nsUDPSocketProviderConstructor },
+ { &kNS_CACHESERVICE_CID, false, nullptr, nsCacheService::Create },
+ { &kNS_APPLICATIONCACHESERVICE_CID, false, nullptr, nsApplicationCacheServiceConstructor },
+ { &kNS_APPLICATIONCACHENAMESPACE_CID, false, nullptr, nsApplicationCacheNamespaceConstructor },
+ { &kNS_APPLICATIONCACHE_CID, false, nullptr, nsApplicationCacheConstructor },
+#ifdef NECKO_COOKIES
+ { &kNS_COOKIEMANAGER_CID, false, nullptr, nsICookieServiceConstructor },
+ { &kNS_COOKIESERVICE_CID, false, nullptr, nsICookieServiceConstructor },
+#endif
+#ifdef NECKO_WIFI
+ { &kNS_WIFI_MONITOR_COMPONENT_CID, false, nullptr, nsWifiMonitorConstructor },
+#endif
+#ifdef NECKO_PROTOCOL_data
+ { &kNS_DATAPROTOCOLHANDLER_CID, false, nullptr, nsDataHandler::Create },
+#endif
+#ifdef NECKO_PROTOCOL_device
+ { &kNS_DEVICEPROTOCOLHANDLER_CID, false, nullptr, nsDeviceProtocolHandlerConstructor},
+#endif
+#ifdef NECKO_PROTOCOL_viewsource
+ { &kNS_VIEWSOURCEHANDLER_CID, false, nullptr, nsViewSourceHandlerConstructor },
+#endif
+#ifdef NECKO_PROTOCOL_wyciwyg
+ { &kNS_WYCIWYGPROTOCOLHANDLER_CID, false, nullptr, nsWyciwygProtocolHandlerConstructor },
+#endif
+#ifdef NECKO_PROTOCOL_websocket
+ { &kNS_WEBSOCKETPROTOCOLHANDLER_CID, false, nullptr,
+ mozilla::net::WebSocketChannelConstructor },
+ { &kNS_WEBSOCKETSSLPROTOCOLHANDLER_CID, false, nullptr,
+ mozilla::net::WebSocketSSLChannelConstructor },
+#endif
+#ifdef NECKO_PROTOCOL_rtsp
+ { &kNS_RTSPPROTOCOLHANDLER_CID, false, nullptr, mozilla::net::RtspHandlerConstructor },
+#endif
+#if defined(XP_WIN)
+ { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNotifyAddrListenerConstructor },
+#elif defined(MOZ_WIDGET_COCOA)
+ { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNetworkLinkServiceConstructor },
+#elif defined(MOZ_WIDGET_ANDROID)
+ { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsAndroidNetworkLinkServiceConstructor },
+#elif defined(XP_LINUX)
+ { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNotifyAddrListenerConstructor },
+#endif
+ { &kNS_SERIALIZATION_HELPER_CID, false, nullptr, nsSerializationHelperConstructor },
+ { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor },
+ { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor },
+ { &kNS_NSILOADCONTEXTINFOFACTORY_CID, false, nullptr, LoadContextInfoFactoryConstructor },
+ { &kNS_NETWORKPREDICTOR_CID, false, nullptr, mozilla::net::Predictor::Create },
+ { &kNS_CAPTIVEPORTAL_CID, false, nullptr, mozilla::net::CaptivePortalServiceConstructor },
+ { &kNS_REQUESTCONTEXTSERVICE_CID, false, nullptr, RequestContextServiceConstructor },
+#ifdef BUILD_NETWORK_INFO_SERVICE
+ { &kNETWORKINFOSERVICE_CID, false, nullptr, nsNetworkInfoServiceConstructor },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
+ { NS_IOSERVICE_CONTRACTID, &kNS_IOSERVICE_CID },
+ { NS_NETUTIL_CONTRACTID, &kNS_IOSERVICE_CID },
+ { NS_STREAMTRANSPORTSERVICE_CONTRACTID, &kNS_STREAMTRANSPORTSERVICE_CID },
+ { NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &kNS_SOCKETTRANSPORTSERVICE_CID },
+ { NS_SERVERSOCKET_CONTRACTID, &kNS_SERVERSOCKET_CID },
+ { NS_TLSSERVERSOCKET_CONTRACTID, &kNS_TLSSERVERSOCKET_CID },
+ { NS_UDPSOCKET_CONTRACTID, &kNS_UDPSOCKET_CID },
+ { NS_SOCKETPROVIDERSERVICE_CONTRACTID, &kNS_SOCKETPROVIDERSERVICE_CID },
+ { NS_DNSSERVICE_CONTRACTID, &kNS_DNSSERVICE_CID },
+ { NS_IDNSERVICE_CONTRACTID, &kNS_IDNSERVICE_CID },
+ { NS_EFFECTIVETLDSERVICE_CONTRACTID, &kNS_EFFECTIVETLDSERVICE_CID },
+ { NS_SIMPLEURI_CONTRACTID, &kNS_SIMPLEURI_CID },
+ { NS_ASYNCSTREAMCOPIER_CONTRACTID, &kNS_ASYNCSTREAMCOPIER_CID },
+ { NS_INPUTSTREAMPUMP_CONTRACTID, &kNS_INPUTSTREAMPUMP_CID },
+ { NS_INPUTSTREAMCHANNEL_CONTRACTID, &kNS_INPUTSTREAMCHANNEL_CID },
+ { NS_STREAMLOADER_CONTRACTID, &kNS_STREAMLOADER_CID },
+ { NS_INCREMENTALSTREAMLOADER_CONTRACTID, &kNS_INCREMENTALSTREAMLOADER_CID },
+ { NS_UNICHARSTREAMLOADER_CONTRACTID, &kNS_UNICHARSTREAMLOADER_CID },
+ { NS_DOWNLOADER_CONTRACTID, &kNS_DOWNLOADER_CID },
+ { NS_BACKGROUNDFILESAVEROUTPUTSTREAM_CONTRACTID, &kNS_BACKGROUNDFILESAVEROUTPUTSTREAM_CID },
+ { NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &kNS_BACKGROUNDFILESAVERSTREAMLISTENER_CID },
+ { NS_SYNCSTREAMLISTENER_CONTRACTID, &kNS_SYNCSTREAMLISTENER_CID },
+ { NS_REQUESTOBSERVERPROXY_CONTRACTID, &kNS_REQUESTOBSERVERPROXY_CID },
+ { NS_SIMPLESTREAMLISTENER_CONTRACTID, &kNS_SIMPLESTREAMLISTENER_CID },
+ { NS_STREAMLISTENERTEE_CONTRACTID, &kNS_STREAMLISTENERTEE_CID },
+ { NS_LOADGROUP_CONTRACTID, &kNS_LOADGROUP_CID },
+ { NS_LOCALFILEINPUTSTREAM_CONTRACTID, &kNS_LOCALFILEINPUTSTREAM_CID },
+ { NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &kNS_LOCALFILEOUTPUTSTREAM_CID },
+ { NS_PARTIALLOCALFILEINPUTSTREAM_CONTRACTID, &kNS_PARTIALLOCALFILEINPUTSTREAM_CID },
+ { NS_ATOMICLOCALFILEOUTPUTSTREAM_CONTRACTID, &kNS_ATOMICLOCALFILEOUTPUTSTREAM_CID },
+ { NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &kNS_SAFELOCALFILEOUTPUTSTREAM_CID },
+ { NS_LOCALFILESTREAM_CONTRACTID, &kNS_LOCALFILESTREAM_CID },
+ { NS_INCREMENTALDOWNLOAD_CONTRACTID, &kNS_INCREMENTALDOWNLOAD_CID },
+ { NS_STDURLPARSER_CONTRACTID, &kNS_STDURLPARSER_CID },
+ { NS_NOAUTHURLPARSER_CONTRACTID, &kNS_NOAUTHURLPARSER_CID },
+ { NS_AUTHURLPARSER_CONTRACTID, &kNS_AUTHURLPARSER_CID },
+ { NS_STANDARDURL_CONTRACTID, &kNS_STANDARDURL_CID },
+ { NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID, &kNS_ARRAYBUFFERINPUTSTREAM_CID },
+ { NS_BUFFEREDINPUTSTREAM_CONTRACTID, &kNS_BUFFEREDINPUTSTREAM_CID },
+ { NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &kNS_BUFFEREDOUTPUTSTREAM_CID },
+ { NS_MIMEINPUTSTREAM_CONTRACTID, &kNS_MIMEINPUTSTREAM_CID },
+ { NS_PROTOCOLPROXYSERVICE_CONTRACTID, &kNS_PROTOCOLPROXYSERVICE_CID },
+ { NS_STREAMCONVERTERSERVICE_CONTRACTID, &kNS_STREAMCONVERTERSERVICE_CID },
+#if defined(XP_WIN)
+ { NS_NAMEDPIPESERVICE_CONTRACTID, &kNS_NAMEDPIPESERVICE_CID },
+#endif
+ { NS_DASHBOARD_CONTRACTID, &kNS_DASHBOARD_CID },
+#ifdef NECKO_PROTOCOL_ftp
+ { NS_ISTREAMCONVERTER_KEY FTP_TO_INDEX, &kNS_FTPDIRLISTINGCONVERTER_CID },
+#endif
+ { NS_ISTREAMCONVERTER_KEY INDEX_TO_HTML, &kNS_NSINDEXEDTOHTMLCONVERTER_CID },
+ { NS_DIRINDEXPARSER_CONTRACTID, &kNS_DIRINDEXPARSER_CID },
+ { NS_ISTREAMCONVERTER_KEY MULTI_MIXED_X, &kNS_MULTIMIXEDCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY MULTI_BYTERANGES, &kNS_MULTIMIXEDCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY MULTI_MIXED, &kNS_MULTIMIXEDCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY UNKNOWN_CONTENT, &kNS_UNKNOWNDECODER_CID },
+ { NS_GENERIC_CONTENT_SNIFFER, &kNS_UNKNOWNDECODER_CID },
+ { NS_BINARYDETECTOR_CONTRACTID, &kNS_BINARYDETECTOR_CID },
+ { NS_ISTREAMCONVERTER_KEY GZIP_TO_UNCOMPRESSED, &kNS_HTTPCOMPRESSCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY XGZIP_TO_UNCOMPRESSED, &kNS_HTTPCOMPRESSCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY BROTLI_TO_UNCOMPRESSED, &kNS_HTTPCOMPRESSCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY COMPRESS_TO_UNCOMPRESSED, &kNS_HTTPCOMPRESSCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY XCOMPRESS_TO_UNCOMPRESSED, &kNS_HTTPCOMPRESSCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY DEFLATE_TO_UNCOMPRESSED, &kNS_HTTPCOMPRESSCONVERTER_CID },
+ { NS_ISTREAMCONVERTER_KEY PLAIN_TO_HTML, &kNS_NSTXTTOHTMLCONVERTER_CID },
+#ifdef BUILD_BINHEX_DECODER
+ { NS_ISTREAMCONVERTER_KEY BINHEX_TO_WILD, &kNS_BINHEXDECODER_CID },
+#endif
+ { MOZ_TXTTOHTMLCONV_CONTRACTID, &kMOZITXTTOHTMLCONV_CID },
+ { "@mozilla.org/dirIndex;1", &kNS_DIRINDEX_CID },
+ { NS_MIMEHEADERPARAM_CONTRACTID, &kNS_MIMEHEADERPARAM_CID },
+#ifdef NECKO_PROTOCOL_file
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file", &kNS_FILEPROTOCOLHANDLER_CID },
+#endif
+#ifdef NECKO_PROTOCOL_http
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &kNS_HTTPPROTOCOLHANDLER_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "https", &kNS_HTTPSPROTOCOLHANDLER_CID },
+ { NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX "basic", &kNS_HTTPBASICAUTH_CID },
+ { NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX "digest", &kNS_HTTPDIGESTAUTH_CID },
+ { NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX "ntlm", &kNS_HTTPNTLMAUTH_CID },
+ { NS_HTTPAUTHMANAGER_CONTRACTID, &kNS_HTTPAUTHMANAGER_CID },
+ { NS_HTTPCHANNELAUTHPROVIDER_CONTRACTID, &kNS_HTTPCHANNELAUTHPROVIDER_CID },
+ { NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &kNS_HTTPACTIVITYDISTRIBUTOR_CID },
+ { NS_THROTTLEQUEUE_CONTRACTID, &kNS_THROTTLEQUEUE_CID },
+#endif // !NECKO_PROTOCOL_http
+#ifdef NECKO_PROTOCOL_ftp
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &kNS_FTPPROTOCOLHANDLER_CID },
+#endif
+#ifdef NECKO_PROTOCOL_res
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "resource", &kNS_RESPROTOCOLHANDLER_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-extension", &kNS_EXTENSIONPROTOCOLHANDLER_CID },
+#endif
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "about", &kNS_ABOUTPROTOCOLHANDLER_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-safe-about", &kNS_SAFEABOUTPROTOCOLHANDLER_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "blank", &kNS_ABOUT_BLANK_MODULE_CID },
+#ifdef NECKO_PROTOCOL_about
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "cache", &kNS_ABOUT_CACHE_MODULE_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "cache-entry", &kNS_ABOUT_CACHE_ENTRY_MODULE_CID },
+#endif
+ { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "socks", &kNS_SOCKSSOCKETPROVIDER_CID },
+ { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "socks4", &kNS_SOCKS4SOCKETPROVIDER_CID },
+ { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "udp", &kNS_UDPSOCKETPROVIDER_CID },
+ { NS_CACHESERVICE_CONTRACTID, &kNS_CACHESERVICE_CID },
+ { NS_APPLICATIONCACHESERVICE_CONTRACTID, &kNS_APPLICATIONCACHESERVICE_CID },
+ { NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &kNS_APPLICATIONCACHENAMESPACE_CID },
+ { NS_APPLICATIONCACHE_CONTRACTID, &kNS_APPLICATIONCACHE_CID },
+#ifdef NECKO_COOKIES
+ { NS_COOKIEMANAGER_CONTRACTID, &kNS_COOKIEMANAGER_CID },
+ { NS_COOKIESERVICE_CONTRACTID, &kNS_COOKIESERVICE_CID },
+#endif
+#ifdef NECKO_WIFI
+ { NS_WIFI_MONITOR_CONTRACTID, &kNS_WIFI_MONITOR_COMPONENT_CID },
+#endif
+#ifdef NECKO_PROTOCOL_data
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "data", &kNS_DATAPROTOCOLHANDLER_CID },
+#endif
+#ifdef NECKO_PROTOCOL_device
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-device", &kNS_DEVICEPROTOCOLHANDLER_CID },
+#endif
+#ifdef NECKO_PROTOCOL_viewsource
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "view-source", &kNS_VIEWSOURCEHANDLER_CID },
+#endif
+#ifdef NECKO_PROTOCOL_wyciwyg
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "wyciwyg", &kNS_WYCIWYGPROTOCOLHANDLER_CID },
+#endif
+#ifdef NECKO_PROTOCOL_websocket
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ws", &kNS_WEBSOCKETPROTOCOLHANDLER_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "wss", &kNS_WEBSOCKETSSLPROTOCOLHANDLER_CID },
+#endif
+#ifdef NECKO_PROTOCOL_rtsp
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "rtsp", &kNS_RTSPPROTOCOLHANDLER_CID },
+#endif
+#if defined(XP_WIN)
+ { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
+#elif defined(MOZ_WIDGET_COCOA)
+ { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
+#elif defined(MOZ_WIDGET_ANDROID)
+ { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
+#elif defined(XP_LINUX)
+ { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
+#endif
+ { NS_SERIALIZATION_HELPER_CONTRACTID, &kNS_SERIALIZATION_HELPER_CID },
+ { NS_REDIRECTCHANNELREGISTRAR_CONTRACTID, &kNS_REDIRECTCHANNELREGISTRAR_CID },
+ { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID },
+ { NS_CACHE_STORAGE_SERVICE_CONTRACTID2, &kNS_CACHE_STORAGE_SERVICE_CID },
+ { NS_NSILOADCONTEXTINFOFACTORY_CONTRACTID, &kNS_NSILOADCONTEXTINFOFACTORY_CID },
+ { NS_NETWORKPREDICTOR_CONTRACTID, &kNS_NETWORKPREDICTOR_CID },
+ { NS_CAPTIVEPORTAL_CONTRACTID, &kNS_CAPTIVEPORTAL_CID },
+ { NS_REQUESTCONTEXTSERVICE_CONTRACTID, &kNS_REQUESTCONTEXTSERVICE_CID },
+#ifdef BUILD_NETWORK_INFO_SERVICE
+ { NETWORKINFOSERVICE_CONTRACT_ID, &kNETWORKINFOSERVICE_CID },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module kNeckoModule = {
+ mozilla::Module::kVersion,
+ kNeckoCIDs,
+ kNeckoContracts,
+ kNeckoCategories,
+ nullptr,
+ nsNetStartup,
+ nsNetShutdown
+};
+
+NSMODULE_DEFN(necko) = &kNeckoModule;
diff --git a/netwerk/cache/moz.build b/netwerk/cache/moz.build
new file mode 100644
index 0000000000..adf6e8bd22
--- /dev/null
+++ b/netwerk/cache/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/.
+
+XPIDL_SOURCES += [
+ 'nsICache.idl',
+ 'nsICacheEntryDescriptor.idl',
+ 'nsICacheListener.idl',
+ 'nsICacheService.idl',
+ 'nsICacheSession.idl',
+ 'nsICacheVisitor.idl',
+]
+
+XPIDL_MODULE = 'necko_cache'
+
+EXPORTS += [
+ 'nsApplicationCacheService.h',
+ 'nsCacheService.h',
+ 'nsDeleteDir.h'
+]
+
+UNIFIED_SOURCES += [
+ 'nsApplicationCacheService.cpp',
+ 'nsCache.cpp',
+ 'nsCacheEntry.cpp',
+ 'nsCacheEntryDescriptor.cpp',
+ 'nsCacheMetaData.cpp',
+ 'nsCacheService.cpp',
+ 'nsCacheSession.cpp',
+ 'nsCacheUtils.cpp',
+ 'nsDeleteDir.cpp',
+ 'nsDiskCacheBinding.cpp',
+ 'nsDiskCacheBlockFile.cpp',
+ 'nsDiskCacheDevice.cpp',
+ 'nsDiskCacheDeviceSQL.cpp',
+ 'nsDiskCacheEntry.cpp',
+ 'nsDiskCacheMap.cpp',
+ 'nsDiskCacheStreams.cpp',
+ 'nsMemoryCacheDevice.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/cache/nsApplicationCache.h b/netwerk/cache/nsApplicationCache.h
new file mode 100644
index 0000000000..fc85010057
--- /dev/null
+++ b/netwerk/cache/nsApplicationCache.h
@@ -0,0 +1,29 @@
+/* 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/. */
+
+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..17012518d4
--- /dev/null
+++ b/netwerk/cache/nsApplicationCacheService.cpp
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "mozIApplicationClearPrivateDataParams.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.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();
+}
+
+nsApplicationCacheService::~nsApplicationCacheService()
+{
+}
+
+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));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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(uint32_t *count,
+ char ***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(count, keys);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetGroupsTimeOrdered(uint32_t *count,
+ char ***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(count, keys);
+}
+
+//-----------------------------------------------------------------------------
+// AppCacheClearDataObserver: handles clearing appcache data for app uninstall
+// and clearing user data events.
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class AppCacheClearDataObserver final : public nsIObserver {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIObserver implementation.
+ NS_IMETHOD
+ Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
+ {
+ MOZ_ASSERT(!nsCRT::strcmp(aTopic, "clear-origin-attributes-data"));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return cacheService->EvictMatchingOriginAttributes(nsDependentString(aData));
+ }
+
+private:
+ ~AppCacheClearDataObserver() {}
+};
+
+NS_IMPL_ISUPPORTS(AppCacheClearDataObserver, nsIObserver)
+
+} // namespace
+
+// Instantiates and registers AppCacheClearDataObserver for notifications
+void
+nsApplicationCacheService::AppClearDataObserverInit()
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ RefPtr<AppCacheClearDataObserver> obs = new AppCacheClearDataObserver();
+ observerService->AddObserver(obs, "clear-origin-attributes-data", /*ownsWeak=*/ false);
+ }
+}
diff --git a/netwerk/cache/nsApplicationCacheService.h b/netwerk/cache/nsApplicationCacheService.h
new file mode 100644
index 0000000000..73bd32206c
--- /dev/null
+++ b/netwerk/cache/nsApplicationCacheService.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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();
+ RefPtr<nsCacheService> mCacheService;
+};
+
+#endif // _nsApplicationCacheService_h_
diff --git a/netwerk/cache/nsCache.cpp b/netwerk/cache/nsCache.cpp
new file mode 100644
index 0000000000..7f5d6a0716
--- /dev/null
+++ b/netwerk/cache/nsCache.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCache.h"
+#include "nsReadableUtils.h"
+#include "nsDependentSubstring.h"
+#include "nsString.h"
+
+
+/**
+ * Cache Service Utility Functions
+ */
+
+mozilla::LazyLogModule gCacheLog("cache");
+
+void
+CacheLogPrintPath(mozilla::LogLevel level, const char * format, nsIFile * item)
+{
+ nsAutoCString path;
+ nsresult rv = item->GetNativePath(path);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_LOG(gCacheLog, level, (format, path.get()));
+ } else {
+ MOZ_LOG(gCacheLog, level, ("GetNativePath failed: %x", rv));
+ }
+}
+
+
+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, char ** result)
+{
+ nsresult rv = NS_OK;
+ *result = nullptr;
+
+ nsReadingIterator<char> colon;
+ key.BeginReading(colon);
+
+ nsReadingIterator<char> start;
+ key.BeginReading(start);
+
+ nsReadingIterator<char> end;
+ key.EndReading(end);
+
+ if (FindCharInReadable(':', colon, end)) {
+ *result = ToNewCString( Substring(start, colon));
+ if (!*result) rv = NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ NS_ASSERTION(false, "FindCharInRead failed to find ':'");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ return rv;
+}
+
+
+nsresult
+ClientKeyFromCacheKey(const nsCString& key, nsACString &result)
+{
+ nsresult rv = NS_OK;
+
+ 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));
+ } else {
+ NS_ASSERTION(false, "FindCharInRead failed to find ':'");
+ rv = NS_ERROR_UNEXPECTED;
+ result.Truncate(0);
+ }
+ return rv;
+}
diff --git a/netwerk/cache/nsCache.h b/netwerk/cache/nsCache.h
new file mode 100644
index 0000000000..3c45131405
--- /dev/null
+++ b/netwerk/cache/nsCache.h
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+
+/**
+ * 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, char ** 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..967c5a034d
--- /dev/null
+++ b/netwerk/cache/nsCacheDevice.h
@@ -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/. */
+
+#ifndef _nsCacheDevice_h_
+#define _nsCacheDevice_h_
+
+#include "nspr.h"
+#include "nsError.h"
+#include "nsICache.h"
+
+class nsIFile;
+class nsCString;
+class nsCacheEntry;
+class nsICacheVisitor;
+class nsIInputStream;
+class nsIOutputStream;
+
+/******************************************************************************
+* nsCacheDevice
+*******************************************************************************/
+class nsCacheDevice {
+public:
+ nsCacheDevice() { MOZ_COUNT_CTOR(nsCacheDevice); }
+ virtual ~nsCacheDevice() { MOZ_COUNT_DTOR(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..6703394c7c
--- /dev/null
+++ b/netwerk/cache/nsCacheEntry.cpp
@@ -0,0 +1,508 @@
+/* -*- 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 "nsCache.h"
+#include "nspr.h"
+#include "nsCacheEntry.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheMetaData.h"
+#include "nsCacheRequest.h"
+#include "nsThreadUtils.h"
+#include "nsError.h"
+#include "nsICacheService.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),
+ mExpirationTime(nsICache::NO_EXPIRATION_TIME),
+ mFlags(0),
+ mPredictedDataSize(-1),
+ mDataSize(0),
+ mCacheDevice(nullptr),
+ mCustomDevice(nullptr),
+ mData(nullptr)
+{
+ 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, mThread);
+}
+
+
+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, mThread);
+ mData = nullptr;
+ }
+
+ if (data) {
+ NS_ADDREF(mData = data);
+ mThread = do_GetCurrentThread();
+ }
+}
+
+
+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));
+
+ NS_ADDREF(*result = descriptor);
+ 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(char ** clientID)
+{
+ NS_ENSURE_ARG_POINTER(clientID);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientIDFromCacheKey(*mCacheEntry->Key(), clientID);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetDeviceID(char ** deviceID)
+{
+ NS_ENSURE_ARG_POINTER(deviceID);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *deviceID = NS_strdup(mCacheEntry->GetDeviceID());
+ return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+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)
+{
+ 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 == 0,
+ "### 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)
+{
+ ((nsCacheEntryHashTableEntry *)to)->cacheEntry =
+ ((nsCacheEntryHashTableEntry *)from)->cacheEntry;
+}
+
+
+void
+nsCacheEntryHashTable::ClearEntry(PLDHashTable * /* table */,
+ PLDHashEntryHdr * hashEntry)
+{
+ ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = 0;
+}
diff --git a/netwerk/cache/nsCacheEntry.h b/netwerk/cache/nsCacheEntry.h
new file mode 100644
index 0000000000..bef7d9ace4
--- /dev/null
+++ b/netwerk/cache/nsCacheEntry.h
@@ -0,0 +1,302 @@
+/* -*- 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 _nsCacheEntry_h_
+#define _nsCacheEntry_h_
+
+#include "nsICache.h"
+#include "nsICacheEntryDescriptor.h"
+#include "nsIThread.h"
+#include "nsCacheMetaData.h"
+
+#include "nspr.h"
+#include "PLDHashTable.h"
+#include "nsAutoPtr.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsAString.h"
+
+class nsCacheDevice;
+class nsCacheMetaData;
+class nsCacheRequest;
+class nsCacheEntryDescriptor;
+
+/******************************************************************************
+* 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<nsIThread> mThread;
+ 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() {}
+};
+
+
+/******************************************************************************
+* nsCacheEntryHashTable
+*******************************************************************************/
+
+struct nsCacheEntryHashTableEntry : public PLDHashEntryHdr
+{
+ nsCacheEntry *cacheEntry;
+};
+
+class nsCacheEntryHashTable
+{
+public:
+ nsCacheEntryHashTable();
+ ~nsCacheEntryHashTable();
+
+ void Init();
+ void Shutdown();
+
+ nsCacheEntry *GetEntry( const nsCString * key);
+ 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..b53ee3058a
--- /dev/null
+++ b/netwerk/cache/nsCacheEntryDescriptor.cpp
@@ -0,0 +1,1475 @@
+/* -*- 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 "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>
+
+#define kMinDecompressReadBufLen 1024
+#define kMinCompressWriteBufLen 1024
+
+
+/******************************************************************************
+ * nsAsyncDoomEvent
+ *****************************************************************************/
+
+class nsAsyncDoomEvent : public mozilla::Runnable {
+public:
+ nsAsyncDoomEvent(nsCacheEntryDescriptor *descriptor,
+ nsICacheListener *listener)
+ {
+ mDescriptor = descriptor;
+ mListener = listener;
+ mThread = do_GetCurrentThread();
+ // 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) {
+ mThread->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<nsIThread> mThread;
+};
+
+
+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);
+ NS_ADDREF(nsCacheService::GlobalInstance()); // ensure it lives for the lifetime of the descriptor
+}
+
+
+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!");
+
+ nsCacheService * service = nsCacheService::GlobalInstance();
+ NS_RELEASE(service);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetClientID(char ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCLIENTID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientIDFromCacheKey(*(mCacheEntry->Key()), result);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetDeviceID(char ** aDeviceID)
+{
+ NS_ENSURE_ARG_POINTER(aDeviceID);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDEVICEID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ const char* deviceID = mCacheEntry->GetDeviceID();
+ if (!deviceID) {
+ *aDeviceID = nullptr;
+ return NS_OK;
+ }
+
+ *aDeviceID = NS_strdup(deviceID);
+ return *aDeviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+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);
+
+ nsInputStreamWrapper* cacheInput = nullptr;
+ {
+ 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);
+ }
+
+ NS_ADDREF(*result = cacheInput);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::OpenOutputStream(uint32_t offset, nsIOutputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsOutputStreamWrapper* cacheOutput = nullptr;
+ {
+ 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;
+ }
+
+ NS_ADDREF(*result = cacheOutput);
+ 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_strdup(value);
+ if (!*result) return NS_ERROR_OUT_OF_MEMORY;
+
+ 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;
+ NS_PRECONDITION(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_THREADSAFE
+
+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=%d]",
+ mDescriptor, this, mInput.get(), 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;
+ NS_PRECONDITION(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_THREADSAFE
+
+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);
+ unsigned char* newBuf;
+ newBuf = (unsigned char*)moz_xrealloc(mReadBuffer,
+ newBufLen);
+ if (newBuf) {
+ mReadBuffer = newBuf;
+ 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 = 0;
+ 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;
+ NS_PRECONDITION(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_THREADSAFE
+
+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;
+ NS_PRECONDITION(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_THREADSAFE
+
+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);
+ if (!mWriteBuffer) {
+ mWriteBufferLen = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ 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(0);
+ uncompressedLenStr.AppendInt(mUncompressedCount);
+ rv = mDescriptor->SetMetaDataElement("uncompressed-len",
+ uncompressedLenStr.get());
+ if (NS_FAILED(rv))
+ retval = rv;
+ }
+
+ if (mWriteBuffer) {
+ free(mWriteBuffer);
+ mWriteBuffer = 0;
+ 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..64f9008118
--- /dev/null
+++ b/netwerk/cache/nsCacheEntryDescriptor.h
@@ -0,0 +1,237 @@
+/* -*- 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 _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(0)
+ , mReadBufferLen(0)
+ , 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(0)
+ , mWriteBufferLen(0)
+ , 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
+ */
+ 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..0df867d839
--- /dev/null
+++ b/netwerk/cache/nsCacheMetaData.cpp
@@ -0,0 +1,162 @@
+/* -*- 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 "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;
+ }
+
+ 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..8ac19125a6
--- /dev/null
+++ b/netwerk/cache/nsCacheMetaData.h
@@ -0,0 +1,45 @@
+/* -*- 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 _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..6681dec676
--- /dev/null
+++ b/netwerk/cache/nsCacheRequest.h
@@ -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/. */
+
+#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, mThread);
+ }
+
+ /**
+ * 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<nsIThread> mThread;
+ 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..bab67e1040
--- /dev/null
+++ b/netwerk/cache/nsCacheService.cpp
@@ -0,0 +1,3209 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 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/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+
+#include "necko-config.h"
+
+#include "nsCache.h"
+#include "nsCacheService.h"
+#include "nsCacheRequest.h"
+#include "nsCacheEntry.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheDevice.h"
+#include "nsMemoryCacheDevice.h"
+#include "nsICacheVisitor.h"
+#include "nsDiskCacheDevice.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheUtils.h"
+#include "../cache2/CacheObserver.h"
+
+#include "nsIObserverService.h"
+#include "nsIPrefService.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 "nsITimer.h"
+#include "mozIStorageService.h"
+
+#include "mozilla/net/NeckoCommon.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+/******************************************************************************
+ * nsCacheProfilePrefObserver
+ *****************************************************************************/
+#define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
+#define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory"
+#define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
+ "browser.cache.disk.smart_size.first_run"
+#define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
+ "browser.cache.disk.smart_size.enabled"
+#define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
+#define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
+#define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
+#define DISK_CACHE_CAPACITY 256000
+
+#define DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF \
+ "browser.cache.disk.smart_size.use_old_max"
+
+#define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
+#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 MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
+#define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
+#define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
+
+#define CACHE_COMPRESSION_LEVEL_PREF "browser.cache.compression_level"
+#define CACHE_COMPRESSION_LEVEL 1
+
+#define SANITIZE_ON_SHUTDOWN_PREF "privacy.sanitize.sanitizeOnShutdown"
+#define CLEAR_ON_SHUTDOWN_PREF "privacy.clearOnShutdown.cache"
+
+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"
+};
+
+static const char * prefList[] = {
+ DISK_CACHE_ENABLE_PREF,
+ DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ DISK_CACHE_CAPACITY_PREF,
+ DISK_CACHE_DIR_PREF,
+ DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ OFFLINE_CACHE_ENABLE_PREF,
+ OFFLINE_CACHE_CAPACITY_PREF,
+ OFFLINE_CACHE_DIR_PREF,
+ MEMORY_CACHE_ENABLE_PREF,
+ MEMORY_CACHE_CAPACITY_PREF,
+ MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ CACHE_COMPRESSION_LEVEL_PREF,
+ SANITIZE_ON_SHUTDOWN_PREF,
+ CLEAR_ON_SHUTDOWN_PREF
+};
+
+// Cache sizes, in KB
+const int32_t DEFAULT_CACHE_SIZE = 250 * 1024; // 250 MB
+#ifdef ANDROID
+const int32_t MAX_CACHE_SIZE = 200 * 1024; // 200 MB
+const int32_t OLD_MAX_CACHE_SIZE = 200 * 1024; // 200 MB
+#else
+const int32_t MAX_CACHE_SIZE = 350 * 1024; // 350 MB
+const int32_t OLD_MAX_CACHE_SIZE = 1024 * 1024; // 1 GB
+#endif
+// Default cache size was 50 MB for many years until FF 4:
+const int32_t PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
+
+class nsCacheProfilePrefObserver : public nsIObserver
+{
+ virtual ~nsCacheProfilePrefObserver() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsCacheProfilePrefObserver()
+ : mHaveProfile(false)
+ , mDiskCacheEnabled(false)
+ , mDiskCacheCapacity(0)
+ , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
+ , mSmartSizeEnabled(false)
+ , mShouldUseOldMaxSmartSize(false)
+ , mOfflineCacheEnabled(false)
+ , mOfflineCacheCapacity(0)
+ , mMemoryCacheEnabled(true)
+ , mMemoryCacheCapacity(-1)
+ , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
+ , mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL)
+ , mSanitizeOnShutdown(false)
+ , mClearCacheOnShutdown(false)
+ {
+ }
+
+ nsresult Install();
+ void Remove();
+ nsresult ReadPrefs(nsIPrefBranch* branch);
+
+ bool DiskCacheEnabled();
+ int32_t DiskCacheCapacity() { return mDiskCacheCapacity; }
+ void SetDiskCacheCapacity(int32_t);
+ int32_t DiskCacheMaxEntrySize() { return mDiskCacheMaxEntrySize; }
+ nsIFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
+ bool SmartSizeEnabled() { return mSmartSizeEnabled; }
+
+ bool ShouldUseOldMaxSmartSize() { return mShouldUseOldMaxSmartSize; }
+ void SetUseNewMaxSmartSize(bool useNew) { mShouldUseOldMaxSmartSize = !useNew; }
+
+ bool OfflineCacheEnabled();
+ int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
+ nsIFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; }
+
+ bool MemoryCacheEnabled();
+ int32_t MemoryCacheCapacity();
+ int32_t MemoryCacheMaxEntrySize() { return mMemoryCacheMaxEntrySize; }
+
+ int32_t CacheCompressionLevel();
+
+ bool SanitizeAtShutdown() { return mSanitizeOnShutdown && mClearCacheOnShutdown; }
+
+ static uint32_t GetSmartCacheSize(const nsAString& cachePath,
+ uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize);
+
+ bool PermittedToSmartSize(nsIPrefBranch*, bool firstRun);
+
+private:
+ bool mHaveProfile;
+
+ bool mDiskCacheEnabled;
+ int32_t mDiskCacheCapacity; // in kilobytes
+ int32_t mDiskCacheMaxEntrySize; // in kilobytes
+ nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
+ bool mSmartSizeEnabled;
+
+ bool mShouldUseOldMaxSmartSize;
+
+ bool mOfflineCacheEnabled;
+ int32_t mOfflineCacheCapacity; // in kilobytes
+ nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
+
+ bool mMemoryCacheEnabled;
+ int32_t mMemoryCacheCapacity; // in kilobytes
+ int32_t mMemoryCacheMaxEntrySize; // in kilobytes
+
+ int32_t mCacheCompressionLevel;
+
+ bool mSanitizeOnShutdown;
+ bool mClearCacheOnShutdown;
+};
+
+NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
+
+class nsSetDiskSmartSizeCallback final : public nsITimerCallback
+{
+ ~nsSetDiskSmartSizeCallback() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override {
+ if (nsCacheService::gService) {
+ nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSSETDISKSMARTSIZECALLBACK_NOTIFY));
+ nsCacheService::gService->SetDiskSmartSize_Locked();
+ nsCacheService::gService->mSmartSizeTimer = nullptr;
+ }
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(nsSetDiskSmartSizeCallback, nsITimerCallback)
+
+// Runnable sent to main thread after the cache IO thread calculates available
+// disk space, so that there is no race in setting mDiskCacheCapacity.
+class nsSetSmartSizeEvent: public Runnable
+{
+public:
+ explicit nsSetSmartSizeEvent(int32_t smartSize)
+ : mSmartSize(smartSize) {}
+
+ NS_IMETHOD Run()
+ {
+ NS_ASSERTION(NS_IsMainThread(),
+ "Setting smart size data off the main thread");
+
+ // Main thread may have already called nsCacheService::Shutdown
+ if (!nsCacheService::IsInitialized())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Ensure smart sizing wasn't switched off while event was pending.
+ // It is safe to access the observer without the lock since we are
+ // on the main thread and the value changes only on the main thread.
+ if (!nsCacheService::gService->mObserver->SmartSizeEnabled())
+ return NS_OK;
+
+ nsCacheService::SetDiskCacheCapacity(mSmartSize);
+
+ nsCOMPtr<nsIPrefBranch> ps = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!ps ||
+ NS_FAILED(ps->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize)))
+ NS_WARNING("Failed to set smart size pref");
+
+ return NS_OK;
+ }
+
+private:
+ int32_t mSmartSize;
+};
+
+
+// Runnable sent from main thread to cacheIO thread
+class nsGetSmartSizeEvent: public Runnable
+{
+public:
+ nsGetSmartSizeEvent(const nsAString& cachePath, uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize)
+ : mCachePath(cachePath)
+ , mCurrentSize(currentSize)
+ , mShouldUseOldMaxSmartSize(shouldUseOldMaxSmartSize)
+ {}
+
+ // Calculates user's disk space available on a background thread and
+ // dispatches this value back to the main thread.
+ NS_IMETHOD Run() override
+ {
+ uint32_t size;
+ size = nsCacheProfilePrefObserver::GetSmartCacheSize(mCachePath,
+ mCurrentSize,
+ mShouldUseOldMaxSmartSize);
+ NS_DispatchToMainThread(new nsSetSmartSizeEvent(size));
+ return NS_OK;
+ }
+
+private:
+ nsString mCachePath;
+ uint32_t mCurrentSize;
+ bool mShouldUseOldMaxSmartSize;
+};
+
+class nsBlockOnCacheThreadEvent : public Runnable {
+public:
+ 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 (unsigned int i=0; i<ArrayLength(observerList); i++) {
+ rv = observerService->AddObserver(this, observerList[i], false);
+ if (NS_FAILED(rv))
+ rv2 = rv;
+ }
+
+ // install preferences observer
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) return NS_ERROR_FAILURE;
+
+ for (unsigned int i=0; i<ArrayLength(prefList); i++) {
+ rv = branch->AddObserver(prefList[i], this, false);
+ if (NS_FAILED(rv))
+ rv2 = rv;
+ }
+
+ // 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 (unsigned int i=0; i<ArrayLength(observerList); i++) {
+ obs->RemoveObserver(this, observerList[i]);
+ }
+ }
+
+ // remove Pref Service observers
+ nsCOMPtr<nsIPrefBranch> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return;
+ for (unsigned int i=0; i<ArrayLength(prefList); i++)
+ prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
+}
+
+void
+nsCacheProfilePrefObserver::SetDiskCacheCapacity(int32_t capacity)
+{
+ mDiskCacheCapacity = std::max(0, capacity);
+}
+
+
+NS_IMETHODIMP
+nsCacheProfilePrefObserver::Observe(nsISupports * subject,
+ const char * topic,
+ const char16_t * data_unicode)
+{
+ nsresult rv;
+ 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(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
+
+ // ignore pref changes until we're done switch profiles
+ if (!mHaveProfile)
+ return NS_OK;
+
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // which preference changed?
+ if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
+ &mDiskCacheEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+
+ } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
+
+ int32_t capacity = 0;
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
+ if (NS_FAILED(rv))
+ return rv;
+ mDiskCacheCapacity = std::max(0, capacity);
+ nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
+
+ // Update the cache capacity when smart sizing is turned on/off
+ } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
+ // Is the update because smartsizing was turned on, or off?
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ &mSmartSizeEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ int32_t newCapacity = 0;
+ if (mSmartSizeEnabled) {
+ nsCacheService::SetDiskSmartSize();
+ } else {
+ // Smart sizing switched off: use user specified size
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
+ if (NS_FAILED(rv))
+ return rv;
+ mDiskCacheCapacity = std::max(0, newCapacity);
+ nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
+ }
+ } else if (!strcmp(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, data.get())) {
+ rv = branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ &mShouldUseOldMaxSmartSize);
+ if (NS_FAILED(rv))
+ return rv;
+ } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
+ int32_t newMaxSize;
+ rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ &newMaxSize);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mDiskCacheMaxEntrySize = std::max(-1, newMaxSize);
+ nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
+
+#if 0
+ } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
+ // XXX We probaby don't want to respond to this pref except after
+ // XXX profile changes. Ideally, there should be somekind of user
+ // XXX notification that the pref change won't take effect until
+ // XXX the next time the profile changes (browser launch)
+#endif
+ } else
+
+ // which preference changed?
+ if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
+ &mOfflineCacheEnabled);
+ if (NS_FAILED(rv)) return rv;
+ nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
+
+ } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
+
+ int32_t capacity = 0;
+ rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
+ if (NS_FAILED(rv)) return rv;
+ mOfflineCacheCapacity = std::max(0, capacity);
+ nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
+#if 0
+ } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
+ // XXX We probaby don't want to respond to this pref except after
+ // XXX profile changes. Ideally, there should be some kind of user
+ // XXX notification that the pref change won't take effect until
+ // XXX the next time the profile changes (browser launch)
+#endif
+ } else
+
+ if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
+ &mMemoryCacheEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetMemoryCache();
+
+ } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
+
+ mMemoryCacheCapacity = -1;
+ (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
+ &mMemoryCacheCapacity);
+ nsCacheService::SetMemoryCache();
+ } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
+ int32_t newMaxSize;
+ rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ &newMaxSize);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mMemoryCacheMaxEntrySize = std::max(-1, newMaxSize);
+ nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
+ } else if (!strcmp(CACHE_COMPRESSION_LEVEL_PREF, data.get())) {
+ mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
+ (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
+ &mCacheCompressionLevel);
+ mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
+ mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
+ } else if (!strcmp(SANITIZE_ON_SHUTDOWN_PREF, data.get())) {
+ rv = branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
+ &mSanitizeOnShutdown);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+ } else if (!strcmp(CLEAR_ON_SHUTDOWN_PREF, data.get())) {
+ rv = branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
+ &mClearCacheOnShutdown);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+ }
+ } else if (!strcmp("last-pb-context-exited", topic)) {
+ nsCacheService::LeavePrivateBrowsing();
+ }
+
+ return NS_OK;
+}
+
+// Returns default ("smart") size (in KB) of cache, given available disk space
+// (also in KB)
+static uint32_t
+SmartCacheSize(const uint32_t availKB, bool shouldUseOldMaxSmartSize)
+{
+ uint32_t maxSize = shouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
+
+ if (availKB > 100 * 1024 * 1024)
+ return maxSize; // skip computing if we're over 100 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);
+
+ // .5% of space above 25 GB
+ if (avail10MBs > 2500) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
+ avail10MBs = 2500;
+ }
+ // 1% of space between 7GB -> 25 GB
+ if (avail10MBs > 700) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
+ avail10MBs = 700;
+ }
+ // 5% of space between 500 MB -> 7 GB
+ if (avail10MBs > 50) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
+ 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.
+
+ // 20% of space up to 500 MB (10 MB min)
+ sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
+#else
+ // 40% of space up to 500 MB (50 MB min)
+ sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
+#endif
+
+ return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
+}
+
+ /* Computes our best guess for the default size of the user's disk cache,
+ * based on the amount of space they have free on their hard drive.
+ * We use a tiered scheme: the more space available,
+ * the larger the disk cache will be. However, we do not want
+ * to enable the disk cache to grow to an unbounded size, so the larger the
+ * user's available space is, the smaller of a percentage we take. We set a
+ * lower bound of 50MB and an upper bound of 1GB.
+ *
+ *@param: None.
+ *@return: The size that the user's disk cache should default to, in kBytes.
+ */
+uint32_t
+nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath,
+ uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize)
+{
+ // Check for free space on device where cache directory lives
+ nsresult rv;
+ nsCOMPtr<nsIFile>
+ cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv) || !cacheDirectory)
+ return DEFAULT_CACHE_SIZE;
+ rv = cacheDirectory->InitWithPath(cachePath);
+ if (NS_FAILED(rv))
+ return DEFAULT_CACHE_SIZE;
+ int64_t bytesAvailable;
+ rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
+ if (NS_FAILED(rv))
+ return DEFAULT_CACHE_SIZE;
+
+ return SmartCacheSize(static_cast<uint32_t>((bytesAvailable / 1024) +
+ currentSize),
+ shouldUseOldMaxSmartSize);
+}
+
+/* Determine if we are permitted to dynamically size the user's disk cache based
+ * on their disk space available. We may do this so long as the pref
+ * smart_size.enabled is true.
+ */
+bool
+nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, bool
+ firstRun)
+{
+ nsresult rv;
+ if (firstRun) {
+ // check if user has set cache size in the past
+ bool userSet;
+ rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
+ if (NS_FAILED(rv)) userSet = true;
+ if (userSet) {
+ int32_t oldCapacity;
+ // If user explicitly set cache size to be smaller than old default
+ // of 50 MB, then keep user's value. Otherwise use smart sizing.
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
+ if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
+ mSmartSizeEnabled = false;
+ branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ mSmartSizeEnabled);
+ return mSmartSizeEnabled;
+ }
+ }
+ // Set manual setting to MAX cache size as starting val for any
+ // adjustment by user: (bug 559942 comment 65)
+ int32_t maxSize = mShouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
+ branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, maxSize);
+ }
+
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ &mSmartSizeEnabled);
+ if (NS_FAILED(rv))
+ mSmartSizeEnabled = false;
+ return mSmartSizeEnabled;
+}
+
+
+nsresult
+nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
+{
+ nsresult rv = NS_OK;
+
+ // read disk cache device prefs
+ mDiskCacheEnabled = true; // presume disk cache is enabled
+ (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
+
+ mDiskCacheCapacity = DISK_CACHE_CAPACITY;
+ (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
+ mDiskCacheCapacity = std::max(0, mDiskCacheCapacity);
+
+ (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ &mDiskCacheMaxEntrySize);
+ mDiskCacheMaxEntrySize = std::max(-1, mDiskCacheMaxEntrySize);
+
+ (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(mDiskCacheParentDirectory));
+
+ (void) branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ &mShouldUseOldMaxSmartSize);
+
+ if (!mDiskCacheParentDirectory) {
+ nsCOMPtr<nsIFile> directory;
+
+ // try to get the disk cache parent directory
+ rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ if (NS_FAILED(rv)) {
+ // 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,
+ "Cache");
+ }
+ }
+ // use file cache in build tree only if asked, to avoid cache dir litter
+ if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(directory));
+ }
+ if (directory)
+ mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
+ }
+ if (mDiskCacheParentDirectory) {
+ bool firstSmartSizeRun;
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
+ &firstSmartSizeRun);
+ if (NS_FAILED(rv))
+ firstSmartSizeRun = false;
+ if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
+ // Avoid evictions: use previous cache size until smart size event
+ // updates mDiskCacheCapacity
+ rv = branch->GetIntPref(firstSmartSizeRun ?
+ DISK_CACHE_CAPACITY_PREF :
+ DISK_CACHE_SMART_SIZE_PREF,
+ &mDiskCacheCapacity);
+ if (NS_FAILED(rv))
+ mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
+ }
+
+ if (firstSmartSizeRun) {
+ // It is no longer our first run
+ rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
+ false);
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed setting first_run pref in ReadPrefs.");
+ }
+ }
+
+ // read offline cache device prefs
+ mOfflineCacheEnabled = true; // presume offline cache is enabled
+ (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
+ &mOfflineCacheEnabled);
+
+ 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) {
+ nsCOMPtr<nsIFile> directory;
+
+ // try to get the offline cache parent directory
+ rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ if (NS_FAILED(rv)) {
+ // 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,
+ "OfflineCache");
+ }
+ }
+#if DEBUG
+ if (!directory) {
+ // use current process directory during development
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(directory));
+ }
+#endif
+ if (directory)
+ mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
+ }
+
+ // read memory cache device prefs
+ (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
+
+ mMemoryCacheCapacity = -1;
+ (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
+ &mMemoryCacheCapacity);
+
+ (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ &mMemoryCacheMaxEntrySize);
+ mMemoryCacheMaxEntrySize = std::max(-1, mMemoryCacheMaxEntrySize);
+
+ // read cache compression level pref
+ mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
+ (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
+ &mCacheCompressionLevel);
+ mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
+ mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
+
+ // read cache shutdown sanitization prefs
+ (void) branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
+ &mSanitizeOnShutdown);
+ (void) branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
+ &mClearCacheOnShutdown);
+
+ return rv;
+}
+
+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::DiskCacheEnabled()
+{
+ if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return false;
+ return mDiskCacheEnabled && (!mSanitizeOnShutdown || !mClearCacheOnShutdown);
+}
+
+
+bool
+nsCacheProfilePrefObserver::OfflineCacheEnabled()
+{
+ if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
+ return false;
+
+ return mOfflineCacheEnabled;
+}
+
+
+bool
+nsCacheProfilePrefObserver::MemoryCacheEnabled()
+{
+ if (mMemoryCacheCapacity == 0) return false;
+ return mMemoryCacheEnabled;
+}
+
+
+/**
+ * MemoryCacheCapacity
+ *
+ * If the browser.cache.memory.capacity preference is positive, we use that
+ * value for the amount of memory available for the cache.
+ *
+ * If browser.cache.memory.capacity is zero, the memory cache is disabled.
+ *
+ * If browser.cache.memory.capacity is negative or not present, we use a
+ * formula that grows less than linearly with the amount of system memory,
+ * with an upper limit on the cache size. No matter how much physical RAM is
+ * present, the default cache size would not exceed 32 MB. This maximum would
+ * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
+ *
+ * RAM Cache
+ * --- -----
+ * 32 Mb 2 Mb
+ * 64 Mb 4 Mb
+ * 128 Mb 6 Mb
+ * 256 Mb 10 Mb
+ * 512 Mb 14 Mb
+ * 1024 Mb 18 Mb
+ * 2048 Mb 24 Mb
+ * 4096 Mb 30 Mb
+ *
+ * The equation for this is (for cache size C and memory size K (kbytes)):
+ * x = log2(K) - 14
+ * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
+ * if (C > 32) C = 32
+ */
+
+int32_t
+nsCacheProfilePrefObserver::MemoryCacheCapacity()
+{
+ int32_t capacity = mMemoryCacheCapacity;
+ if (capacity >= 0) {
+ CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
+ return capacity;
+ }
+
+ static uint64_t bytes = PR_GetPhysicalMemorySize();
+ CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes));
+
+ // 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;
+ if (x > 0) {
+ capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
+ if (capacity > 32)
+ capacity = 32;
+ capacity *= 1024;
+ } else {
+ capacity = 0;
+ }
+
+ return capacity;
+}
+
+int32_t
+nsCacheProfilePrefObserver::CacheCompressionLevel()
+{
+ return mCacheCompressionLevel;
+}
+
+/******************************************************************************
+ * nsProcessRequestEvent
+ *****************************************************************************/
+
+class nsProcessRequestEvent : public Runnable {
+public:
+ explicit nsProcessRequestEvent(nsCacheRequest *aRequest)
+ {
+ 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() {}
+
+private:
+ nsCacheRequest *mRequest;
+};
+
+/******************************************************************************
+ * nsDoomEvent
+ *****************************************************************************/
+
+class nsDoomEvent : public Runnable {
+public:
+ nsDoomEvent(nsCacheSession *session,
+ const nsACString &key,
+ nsICacheListener *listener)
+ {
+ mKey = *session->ClientID();
+ mKey.Append(':');
+ mKey.Append(key);
+ mStoragePolicy = session->StoragePolicy();
+ mListener = listener;
+ mThread = do_GetCurrentThread();
+ // 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) {
+ mThread->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<nsIThread> mThread;
+};
+
+/******************************************************************************
+ * nsCacheService
+ *****************************************************************************/
+nsCacheService * nsCacheService::gService = nullptr;
+
+NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal,
+ nsIMemoryReporter)
+
+nsCacheService::nsCacheService()
+ : mObserver(nullptr),
+ mLock("nsCacheService.mLock"),
+ mCondVar(mLock, "nsCacheService.mCondVar"),
+ mNotified(false),
+ mTimeStampLock("nsCacheService.mTimeStampLock"),
+ mInitialized(false),
+ mClearingEntries(false),
+ mEnableMemoryDevice(true),
+ mEnableDiskDevice(true),
+ mMemoryDevice(nullptr),
+ mDiskDevice(nullptr),
+ mOfflineDevice(nullptr),
+ 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_RUNTIMEABORT("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();
+ }
+
+ mEnableDiskDevice = mObserver->DiskCacheEnabled();
+ mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
+ mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
+
+ RegisterWeakMemoryReporter(this);
+
+ 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()) {
+ NS_RUNTIMEABORT("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();
+
+ UnregisterWeakMemoryReporter(this);
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+ NS_ASSERTION(mInitialized, "Bad state");
+
+ mInitialized = false;
+
+ // Clear entries
+ ClearDoomList();
+
+ if (mSmartSizeTimer) {
+ mSmartSizeTimer->Cancel();
+ mSmartSizeTimer = nullptr;
+ }
+
+ // 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();
+
+ // deallocate memory and disk caches
+ delete mMemoryDevice;
+ mMemoryDevice = nullptr;
+
+ delete mDiskDevice;
+ mDiskDevice = nullptr;
+
+ 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(NS_LITERAL_CSTRING("Cache"));
+ 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;
+
+ nsCacheService * cacheService = new nsCacheService();
+ if (cacheService == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(cacheService);
+ rv = cacheService->Init();
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheService->QueryInterface(aIID, aResult);
+ }
+ NS_RELEASE(cacheService);
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheService::CreateSession(const char * clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased,
+ nsICacheSession **result)
+{
+ *result = nullptr;
+
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return CreateSessionInternal(clientID, storagePolicy, streamBased, result);
+}
+
+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)
+ : 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;
+
+ if (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_ON_DISK) {
+
+ if (mEnableDiskDevice) {
+ nsresult rv = NS_OK;
+ if (!mDiskDevice)
+ rv = CreateDiskDevice();
+ if (mDiskDevice)
+ rv = mDiskDevice->EvictEntries(clientID);
+ if (NS_FAILED(rv))
+ res = rv;
+ }
+ }
+
+ // 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;
+ }
+ }
+
+ if (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_IN_MEMORY) {
+ // If there is no memory device, there is no need to evict it...
+ if (mMemoryDevice) {
+ nsresult rv = mMemoryDevice->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 = gService->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->mEnableMemoryDevice &&
+ (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_IN_MEMORY)) {
+ return true;
+ }
+ if (gService->mEnableDiskDevice &&
+ (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_ON_DISK)) {
+ return true;
+ }
+ if (gService->mEnableOfflineDevice &&
+ storagePolicy == nsICache::STORE_OFFLINE) {
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
+{
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return VisitEntriesInternal(visitor);
+}
+
+nsresult nsCacheService::VisitEntriesInternal(nsICacheVisitor *visitor)
+{
+ NS_ENSURE_ARG_POINTER(visitor);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
+
+ if (!(mEnableDiskDevice || mEnableMemoryDevice))
+ 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 there is no memory device, there are then also no entries to visit...
+ if (mMemoryDevice) {
+ rv = mMemoryDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (mEnableDiskDevice) {
+ if (!mDiskDevice) {
+ rv = CreateDiskDevice();
+ if (NS_FAILED(rv)) return rv;
+ }
+ rv = mDiskDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ 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)
+{
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return EvictEntriesInternal(storagePolicy);
+}
+
+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(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) {
+ NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
+ 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::CreateDiskDevice()
+{
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
+ if (mDiskDevice) return NS_OK;
+
+ mDiskDevice = new nsDiskCacheDevice;
+ if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY;
+
+ // set the preferences
+ mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
+ mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
+ mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
+
+ nsresult rv = mDiskDevice->Init();
+ if (NS_FAILED(rv)) {
+#if DEBUG
+ printf("###\n");
+ printf("### mDiskDevice->Init() failed (0x%.8x)\n",
+ static_cast<uint32_t>(rv));
+ printf("### - disabling disk cache for this session.\n");
+ printf("###\n");
+#endif
+ mEnableDiskDevice = false;
+ delete mDiskDevice;
+ mDiskDevice = nullptr;
+ return rv;
+ }
+
+ NS_ASSERTION(!mSmartSizeTimer, "Smartsize timer was already fired!");
+
+ // Disk device is usually created during the startup. Delay smart size
+ // calculation to avoid possible massive IO caused by eviction of entries
+ // in case the new smart size is smaller than current cache usage.
+ mSmartSizeTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mSmartSizeTimer->InitWithCallback(new nsSetDiskSmartSizeCallback(),
+ 1000*60*3,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to post smart size timer");
+ mSmartSizeTimer = nullptr;
+ }
+ } else {
+ NS_WARNING("Can't create smart size timer");
+ }
+ // Ignore state of the timer and return success since the purpose of the
+ // method (create the disk-device) has been fulfilled
+
+ return NS_OK;
+}
+
+// Runnable sent from cache thread to main thread
+class nsDisableOldMaxSmartSizePrefEvent: public Runnable
+{
+public:
+ nsDisableOldMaxSmartSizePrefEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ // Main thread may have already called nsCacheService::Shutdown
+ if (!nsCacheService::IsInitialized())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = branch->SetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to disable old max smart size");
+ return rv;
+ }
+
+ // It is safe to call SetDiskSmartSize_Locked() without holding the lock
+ // when we are on main thread and nsCacheService is initialized.
+ nsCacheService::gService->SetDiskSmartSize_Locked();
+
+ if (nsCacheService::gService->mObserver->PermittedToSmartSize(branch, false)) {
+ rv = branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to set cache capacity pref");
+ }
+ }
+
+ return NS_OK;
+ }
+};
+
+void
+nsCacheService::MarkStartingFresh()
+{
+ if (!gService || !gService->mObserver->ShouldUseOldMaxSmartSize()) {
+ // Already using new max, nothing to do here
+ return;
+ }
+
+ gService->mObserver->SetUseNewMaxSmartSize(true);
+
+ // We always dispatch an event here because we don't want to deal with lock
+ // reentrance issues.
+ NS_DispatchToMainThread(new nsDisableOldMaxSmartSizePrefEvent());
+}
+
+nsresult
+nsCacheService::GetOfflineDevice(nsOfflineCacheDevice **aDevice)
+{
+ if (!mOfflineDevice) {
+ nsresult rv = CreateOfflineDevice();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ADDREF(*aDevice = mOfflineDevice);
+ 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);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (*aDevice)->SetAutoShutdown();
+ mCustomOfflineDevices.Put(profilePath, *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;
+ }
+
+ nsresult rv = CreateCustomOfflineDevice(
+ mObserver->OfflineCacheParentDirectory(),
+ mObserver->OfflineCacheCapacity(),
+ &mOfflineDevice);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::CreateCustomOfflineDevice(nsIFile *aProfileDir,
+ int32_t aQuota,
+ nsOfflineCacheDevice **aDevice)
+{
+ NS_ENSURE_ARG(aProfileDir);
+
+ if (MOZ_LOG_TEST(gCacheLog, LogLevel::Info)) {
+ nsAutoCString profilePath;
+ aProfileDir->GetNativePath(profilePath);
+ CACHE_LOG_INFO(("Creating custom offline device, %s, %d",
+ profilePath.BeginReading(), aQuota));
+ }
+
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
+
+ *aDevice = new nsOfflineCacheDevice;
+
+ NS_ADDREF(*aDevice);
+
+ // set the preferences
+ (*aDevice)->SetCacheParentDirectory(aProfileDir);
+ (*aDevice)->SetCapacity(aQuota);
+
+ nsresult rv = (*aDevice)->InitWithSqlite(mStorageService);
+ if (NS_FAILED(rv)) {
+ CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8x)\n", rv));
+ CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
+
+ NS_RELEASE(*aDevice);
+ }
+ return rv;
+}
+
+nsresult
+nsCacheService::CreateMemoryDevice()
+{
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
+ if (mMemoryDevice) return NS_OK;
+
+ mMemoryDevice = new nsMemoryCacheDevice;
+ if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY;
+
+ // set preference
+ int32_t capacity = mObserver->MemoryCacheCapacity();
+ CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
+ mMemoryDevice->SetCapacity(capacity);
+ mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
+
+ nsresult rv = mMemoryDevice->Init();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Initialization of Memory Cache failed.");
+ delete mMemoryDevice;
+ mMemoryDevice = nullptr;
+ }
+
+ 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)->mThread = do_GetCurrentThread();
+
+ return NS_OK;
+}
+
+
+class nsCacheListenerEvent : public Runnable
+{
+public:
+ nsCacheListenerEvent(nsICacheListener *listener,
+ nsICacheEntryDescriptor *descriptor,
+ nsCacheAccessMode accessGranted,
+ nsresult status)
+ : 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->mThread, "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->mThread->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(1) { // Activate entry loop
+ rv = ActivateEntry(request, &entry, &doomedEntry); // get the entry for this request
+ if (NS_FAILED(rv)) break;
+
+ while(1) { // 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 (!mEnableMemoryDevice && !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;
+
+ CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
+
+ *collision = false;
+ if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
+ // If there is no memory device, then there is nothing to search...
+ if (mMemoryDevice) {
+ entry = mMemoryDevice->FindEntry(key, collision);
+ CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
+ "collision: %d\n", key->get(), entry, collision));
+ }
+ }
+
+ if (!entry &&
+ ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
+
+ if (mEnableDiskDevice) {
+ if (!mDiskDevice) {
+ nsresult rv = CreateDiskDevice();
+ if (NS_FAILED(rv))
+ return nullptr;
+ }
+
+ entry = mDiskDevice->FindEntry(key, collision);
+ }
+ }
+
+ if (!entry && (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;
+
+ int64_t predictedDataSize = entry->PredictedDataSize();
+ if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
+ // this is the default
+ if (!mDiskDevice) {
+ (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
+ }
+
+ if (mDiskDevice) {
+ // Bypass the cache if Content-Length says the entry will be too big
+ if (predictedDataSize != -1 &&
+ mDiskDevice->EntryIsTooBig(predictedDataSize)) {
+ DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return nullptr;
+ }
+
+ entry->MarkBinding(); // enter state of binding
+ nsresult rv = mDiskDevice->BindEntry(entry);
+ entry->ClearBinding(); // exit state of binding
+ if (NS_SUCCEEDED(rv))
+ device = mDiskDevice;
+ }
+ }
+
+ // if we can't use mDiskDevice, try mMemoryDevice
+ if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {
+ if (!mMemoryDevice) {
+ (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead)
+ }
+ if (mMemoryDevice) {
+ // Bypass the cache if Content-Length says entry will be too big
+ if (predictedDataSize != -1 &&
+ mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
+ DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return nullptr;
+ }
+
+ entry->MarkBinding(); // enter state of binding
+ nsresult rv = mMemoryDevice->BindEntry(entry);
+ entry->ClearBinding(); // exit state of binding
+ if (NS_SUCCEEDED(rv))
+ device = mMemoryDevice;
+ }
+ }
+
+ 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->mDiskDevice && gService->mEnableDiskDevice) {
+ gService->mDiskDevice->Shutdown();
+ }
+ gService->mEnableDiskDevice = false;
+
+ 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;
+
+ if (gService->mMemoryDevice) {
+ // clear memory cache
+ gService->mMemoryDevice->EvictEntries(nullptr);
+ }
+
+ gService->mClearingEntries = false;
+}
+
+
+void
+nsCacheService::OnProfileChanged()
+{
+ if (!gService) return;
+
+ CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
+
+ gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
+ gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
+ gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
+ gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
+
+ // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
+ nsresult rv = gService->mDiskDevice->Init();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
+ gService->mEnableDiskDevice = false;
+ // XXX delete mDiskDevice?
+ }
+ }
+
+ 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?
+ }
+ }
+
+ // If memoryDevice exists, reset its size to the new profile
+ if (gService->mMemoryDevice) {
+ if (gService->mEnableMemoryDevice) {
+ // make sure that capacity is reset to the right value
+ int32_t capacity = gService->mObserver->MemoryCacheCapacity();
+ CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
+ capacity));
+ gService->mMemoryDevice->SetCapacity(capacity);
+ } else {
+ // tell memory device to evict everything
+ CACHE_LOG_DEBUG(("memory device disabled\n"));
+ gService->mMemoryDevice->SetCapacity(0);
+ // Don't delete memory device, because some entries may be active still...
+ }
+ }
+}
+
+
+void
+nsCacheService::SetDiskCacheEnabled(bool enabled)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEENABLED));
+ gService->mEnableDiskDevice = enabled;
+}
+
+
+void
+nsCacheService::SetDiskCacheCapacity(int32_t capacity)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHECAPACITY));
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetCapacity(capacity);
+ }
+
+ gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
+}
+
+void
+nsCacheService::SetDiskCacheMaxEntrySize(int32_t maxSize)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEMAXENTRYSIZE));
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetMaxEntrySize(maxSize);
+ }
+}
+
+void
+nsCacheService::SetMemoryCacheMaxEntrySize(int32_t maxSize)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHEMAXENTRYSIZE));
+
+ if (gService->mMemoryDevice) {
+ gService->mMemoryDevice->SetMaxEntrySize(maxSize);
+ }
+}
+
+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();
+}
+
+
+void
+nsCacheService::SetMemoryCache()
+{
+ if (!gService) return;
+
+ CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHE));
+
+ gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
+
+ if (gService->mEnableMemoryDevice) {
+ if (gService->mMemoryDevice) {
+ int32_t capacity = gService->mObserver->MemoryCacheCapacity();
+ // make sure that capacity is reset to the right value
+ CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
+ capacity));
+ gService->mMemoryDevice->SetCapacity(capacity);
+ }
+ } else {
+ if (gService->mMemoryDevice) {
+ // tell memory device to evict everything
+ CACHE_LOG_DEBUG(("memory device disabled\n"));
+ gService->mMemoryDevice->SetCapacity(0);
+ // Don't delete memory device, because some entries may be active still...
+ }
+ }
+}
+
+
+/******************************************************************************
+ * 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::ID mainThreadLockerID)
+{
+ mozilla::Telemetry::ID lockerID;
+ mozilla::Telemetry::ID 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;
+ doomed.SwapElements(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(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(NS_LITERAL_CSTRING("Cache"));
+ 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(NS_LITERAL_CSTRING("OfflineCache"));
+ if (NS_FAILED(rv))
+ return;
+
+ directory.forget(result);
+}
+
+
+void
+nsCacheService::LogCacheStatistics()
+{
+ uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
+ ((double)(mCacheHits + mCacheMisses))) * 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));
+}
+
+nsresult
+nsCacheService::SetDiskSmartSize()
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKSMARTSIZE));
+
+ if (!gService) return NS_ERROR_NOT_AVAILABLE;
+
+ return gService->SetDiskSmartSize_Locked();
+}
+
+nsresult
+nsCacheService::SetDiskSmartSize_Locked()
+{
+ nsresult rv;
+
+ if (mozilla::net::CacheObserver::UseNewCache()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!mObserver->DiskCacheParentDirectory())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mDiskDevice)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mObserver->SmartSizeEnabled())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoString cachePath;
+ rv = mObserver->DiskCacheParentDirectory()->GetPath(cachePath);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsGetSmartSizeEvent(cachePath, mDiskDevice->getCacheSize(),
+ mObserver->ShouldUseOldMaxSmartSize());
+ DispatchToCacheIOThread(event);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+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;
+
+ nsAutoCString newPath;
+ rv = aNewCacheSubdir->GetNativePath(newPath);
+ if (NS_FAILED(rv))
+ return;
+
+ 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) {
+ nsAutoCString oldPath;
+ rv = aOldCacheSubdir->GetNativePath(oldPath);
+ if (NS_FAILED(rv))
+ return;
+ if (rename(oldPath.get(), newPath.get()) == 0)
+ 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);
+
+ if (gService->mMemoryDevice) {
+ // clear memory cache
+ gService->mMemoryDevice->EvictPrivateEntries();
+ }
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(DiskCacheDeviceMallocSizeOf)
+
+NS_IMETHODIMP
+nsCacheService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ size_t disk = 0;
+ if (mDiskDevice) {
+ nsCacheServiceAutoLock
+ lock(LOCK_TELEM(NSCACHESERVICE_DISKDEVICEHEAPSIZE));
+ disk = mDiskDevice->SizeOfIncludingThis(DiskCacheDeviceMallocSizeOf);
+ }
+
+ size_t memory = mMemoryDevice ? mMemoryDevice->TotalSize() : 0;
+
+ MOZ_COLLECT_REPORT(
+ "explicit/network/disk-cache", KIND_HEAP, UNITS_BYTES, disk,
+ "Memory used by the network disk cache.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/network/memory-cache", KIND_HEAP, UNITS_BYTES, memory,
+ "Memory used by the network memory cache.");
+
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsCacheService.h b/netwerk/cache/nsCacheService.h
new file mode 100644
index 0000000000..1751d4875f
--- /dev/null
+++ b/netwerk/cache/nsCacheService.h
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 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/. */
+
+#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 "nsIMemoryReporter.h"
+
+#include "prthread.h"
+#include "nsIObserver.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 nsDiskCacheDevice;
+class nsMemoryCacheDevice;
+class nsOfflineCacheDevice;
+class nsCacheServiceAutoLock;
+class nsITimer;
+class mozIStorageService;
+
+
+/******************************************************************************
+ * nsNotifyDoomListener
+ *****************************************************************************/
+
+class nsNotifyDoomListener : public mozilla::Runnable {
+public:
+ nsNotifyDoomListener(nsICacheListener *listener,
+ nsresult status)
+ : 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,
+ public nsIMemoryReporter
+{
+ virtual ~nsCacheService();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHESERVICE
+ NS_DECL_NSICACHESERVICEINTERNAL
+ NS_DECL_NSIMEMORYREPORTER
+
+ 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);
+
+ /**
+ * Called by disk cache to notify us to use the new max smart size
+ */
+ static void MarkStartingFresh();
+
+ /**
+ * 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 SetDiskCacheEnabled(bool enabled);
+ // Sets the disk cache capacity (in kilobytes)
+ static void SetDiskCacheCapacity(int32_t capacity);
+ // Set max size for a disk-cache entry (in KB). -1 disables limit up to
+ // 1/8th of disk cache size
+ static void SetDiskCacheMaxEntrySize(int32_t maxSize);
+ // Set max size for a memory-cache entry (in kilobytes). -1 disables
+ // limit up to 90% of memory cache size
+ static void SetMemoryCacheMaxEntrySize(int32_t maxSize);
+
+ static void SetOfflineCacheEnabled(bool enabled);
+ // Sets the offline cache capacity (in kilobytes)
+ static void SetOfflineCacheCapacity(int32_t capacity);
+
+ static void SetMemoryCache();
+
+ static void SetCacheCompressionLevel(int32_t level);
+
+ // Starts smart cache size computation if disk device is available
+ static nsresult SetDiskSmartSize();
+
+ 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 nsSetSmartSizeEvent;
+ friend class nsBlockOnCacheThreadEvent;
+ friend class nsSetDiskSmartSizeCallback;
+ friend class nsDoomEvent;
+ friend class nsDisableOldMaxSmartSizePrefEvent;
+ friend class nsDiskCacheMap;
+ friend class nsAsyncDoomEvent;
+ friend class nsCacheEntryDescriptor;
+
+ /**
+ * Internal Methods
+ */
+
+ static void Lock();
+ static void Lock(::mozilla::Telemetry::ID 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();
+
+ nsresult SetDiskSmartSize_Locked();
+
+ /**
+ * 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;
+ nsCOMPtr<nsITimer> mSmartSizeTimer;
+
+ bool mInitialized;
+ bool mClearingEntries;
+
+ bool mEnableMemoryDevice;
+ bool mEnableDiskDevice;
+ bool mEnableOfflineDevice;
+
+ nsMemoryCacheDevice * mMemoryDevice;
+ nsDiskCacheDevice * mDiskDevice;
+ 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::ID 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..d82203aec1
--- /dev/null
+++ b/netwerk/cache/nsCacheSession.cpp
@@ -0,0 +1,147 @@
+/* -*- 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 "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)
+{
+ if (mProfileDir)
+ NS_ADDREF(*profileDir = mProfileDir);
+ else
+ *profileDir = nullptr;
+
+ 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..04b031902e
--- /dev/null
+++ b/netwerk/cache/nsCacheSession.h
@@ -0,0 +1,67 @@
+/* -*- 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 _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..7cb5622747
--- /dev/null
+++ b/netwerk/cache/nsCacheUtils.cpp
@@ -0,0 +1,89 @@
+/* -*- 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)
+ : mThread(thread)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+nsShutdownThread::nsShutdownThread(nsIThread *aThread)
+ : mMonitor("nsShutdownThread.mMonitor")
+ , mShuttingDown(false)
+ , mThread(aThread)
+{
+}
+
+nsShutdownThread::~nsShutdownThread()
+{
+}
+
+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..65ae8f82b1
--- /dev/null
+++ b/netwerk/cache/nsCacheUtils.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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();
+
+ NS_IMETHOD Run();
+
+/**
+ * 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..1f3f3934e9
--- /dev/null
+++ b/netwerk/cache/nsDeleteDir.cpp
@@ -0,0 +1,454 @@
+/* -*- 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 "nsISimpleEnumerator.h"
+#include "nsAutoPtr.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() {}
+ 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<nsIThread> thread;
+ {
+ 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;
+ }
+
+ thread.swap(gInstance->mThread);
+ if (thread) {
+ // 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 = thread->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed dispatching block-event");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ gInstance->mNotified = false;
+ while (!gInstance->mNotified) {
+ gInstance->mCondVar.Wait();
+ }
+ nsShutdownThread::BlockingShutdown(thread);
+ }
+ }
+
+ delete gInstance;
+
+ for (int32_t i = 0; i < dirsToRemove.Count(); i++)
+ dirsToRemove[i]->Remove(true);
+
+ return NS_OK;
+}
+
+nsresult
+nsDeleteDir::InitThread()
+{
+ if (mThread)
+ return NS_OK;
+
+ nsresult rv = NS_NewNamedThread("Cache Deleter", getter_AddRefs(mThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create background thread");
+ return rv;
+ }
+
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread);
+ if (p) {
+ p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+ return NS_OK;
+}
+
+void
+nsDeleteDir::DestroyThread()
+{
+ if (!mThread)
+ return;
+
+ if (mTimers.Count())
+ // more work to do, so don't delete thread.
+ return;
+
+ nsShutdownThread::Shutdown(mThread);
+ mThread = 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);
+ }
+
+ nsAutoPtr<nsCOMArray<nsIFile> > dirList;
+ dirList = 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);
+ }
+
+ nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>);
+ arg->AppendObject(trash);
+
+ rv = gInstance->PostTimer(arg, delay);
+ if (NS_FAILED(rv))
+ return rv;
+
+ arg.forget();
+ 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<nsISimpleEnumerator> iter;
+ rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool more;
+ nsCOMPtr<nsISupports> elem;
+ nsAutoPtr<nsCOMArray<nsIFile> > dirList;
+
+ while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+ rv = iter->GetNext(getter_AddRefs(elem));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
+ if (!file)
+ continue;
+
+ 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 = new nsCOMArray<nsIFile>;
+ dirList->AppendObject(file);
+ }
+ }
+
+ if (dirList) {
+ rv = gInstance->PostTimer(dirList, 90000);
+ if (NS_FAILED(rv))
+ return rv;
+
+ dirList.forget();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDeleteDir::PostTimer(void *arg, uint32_t delay)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+
+ MutexAutoLock lock(mLock);
+
+ rv = InitThread();
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = timer->SetTarget(mThread);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = timer->InitWithFuncCallback(TimerCallback, arg, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ 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<nsISimpleEnumerator> iter;
+ rv = file->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool more;
+ nsCOMPtr<nsISupports> elem;
+ while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+ rv = iter->GetNext(getter_AddRefs(elem));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem);
+ if (!file2) {
+ NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
+ continue;
+ }
+
+ 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..6426efd262
--- /dev/null
+++ b/netwerk/cache/nsDeleteDir.h
@@ -0,0 +1,80 @@
+/* -*- 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 nsIThread;
+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<nsIThread> mThread;
+ 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..cc91553fa8
--- /dev/null
+++ b/netwerk/cache/nsDiskCache.h
@@ -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/. */
+
+
+#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/nsDiskCacheBinding.cpp b/netwerk/cache/nsDiskCacheBinding.cpp
new file mode 100644
index 0000000000..cdc2606e6c
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheBinding.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/MemoryReporting.h"
+#include "nsCache.h"
+#include <limits.h>
+
+#include "nscore.h"
+#include "nsDiskCacheBinding.h"
+#include "nsCacheService.h"
+
+using namespace mozilla;
+
+/******************************************************************************
+ * static hash table callback functions
+ *
+ *****************************************************************************/
+struct HashTableEntry : PLDHashEntryHdr {
+ nsDiskCacheBinding * mBinding;
+};
+
+
+static PLDHashNumber
+HashKey(const void *key)
+{
+ return (PLDHashNumber) NS_PTR_TO_INT32(key);
+}
+
+
+static bool
+MatchEntry(const PLDHashEntryHdr * header,
+ const void * key)
+{
+ HashTableEntry * hashEntry = (HashTableEntry *) header;
+ return (hashEntry->mBinding->mRecord.HashNumber() == (PLDHashNumber) NS_PTR_TO_INT32(key));
+}
+
+static void
+MoveEntry(PLDHashTable * /* table */,
+ const PLDHashEntryHdr * src,
+ PLDHashEntryHdr * dst)
+{
+ ((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding;
+}
+
+
+static void
+ClearEntry(PLDHashTable * /* table */,
+ PLDHashEntryHdr * header)
+{
+ ((HashTableEntry *)header)->mBinding = nullptr;
+}
+
+
+/******************************************************************************
+ * Utility Functions
+ *****************************************************************************/
+nsDiskCacheBinding *
+GetCacheEntryBinding(nsCacheEntry * entry)
+{
+ return (nsDiskCacheBinding *) entry->Data();
+}
+
+
+/******************************************************************************
+ * nsDiskCacheBinding
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS0(nsDiskCacheBinding)
+
+nsDiskCacheBinding::nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record)
+ : mCacheEntry(entry)
+ , mStreamIO(nullptr)
+ , mDeactivateEvent(nullptr)
+{
+ NS_ASSERTION(record->ValidRecord(), "bad record");
+ PR_INIT_CLIST(this);
+ mRecord = *record;
+ mDoomed = entry->IsDoomed();
+ mGeneration = record->Generation(); // 0 == uninitialized, or data & meta using block files
+}
+
+nsDiskCacheBinding::~nsDiskCacheBinding()
+{
+ // Grab the cache lock since the binding is stored in nsCacheEntry::mData
+ // and it is released using nsCacheService::ReleaseObject_Locked() which
+ // releases the object outside the cache lock.
+ nsCacheServiceAutoLock lock;
+
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "binding deleted while still on list");
+ if (!PR_CLIST_IS_EMPTY(this))
+ PR_REMOVE_LINK(this); // XXX why are we still on a list?
+
+ // sever streamIO/binding link
+ if (mStreamIO) {
+ if (NS_FAILED(mStreamIO->ClearBinding()))
+ nsCacheService::DoomEntry(mCacheEntry);
+ NS_RELEASE(mStreamIO);
+ }
+}
+
+nsresult
+nsDiskCacheBinding::EnsureStreamIO()
+{
+ if (!mStreamIO) {
+ mStreamIO = new nsDiskCacheStreamIO(this);
+ if (!mStreamIO) return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(mStreamIO);
+ }
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsDiskCacheBindery
+ *
+ * Keeps track of bound disk cache entries to detect for collisions.
+ *
+ *****************************************************************************/
+
+const PLDHashTableOps nsDiskCacheBindery::ops =
+{
+ HashKey,
+ MatchEntry,
+ MoveEntry,
+ ClearEntry
+};
+
+
+nsDiskCacheBindery::nsDiskCacheBindery()
+ : table(&ops, sizeof(HashTableEntry), kInitialTableLength)
+ , initialized(false)
+{
+}
+
+
+nsDiskCacheBindery::~nsDiskCacheBindery()
+{
+ Reset();
+}
+
+
+void
+nsDiskCacheBindery::Init()
+{
+ table.ClearAndPrepareForLength(kInitialTableLength);
+ initialized = true;
+}
+
+void
+nsDiskCacheBindery::Reset()
+{
+ if (initialized) {
+ table.ClearAndPrepareForLength(kInitialTableLength);
+ initialized = false;
+ }
+}
+
+
+nsDiskCacheBinding *
+nsDiskCacheBindery::CreateBinding(nsCacheEntry * entry,
+ nsDiskCacheRecord * record)
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ nsCOMPtr<nsISupports> data = entry->Data();
+ if (data) {
+ NS_ERROR("cache entry already has bind data");
+ return nullptr;
+ }
+
+ nsDiskCacheBinding * binding = new nsDiskCacheBinding(entry, record);
+ if (!binding) return nullptr;
+
+ // give ownership of the binding to the entry
+ entry->SetData(binding);
+
+ // add binding to collision detection system
+ nsresult rv = AddBinding(binding);
+ if (NS_FAILED(rv)) {
+ entry->SetData(nullptr);
+ return nullptr;
+ }
+
+ return binding;
+}
+
+
+/**
+ * FindActiveEntry : to find active colliding entry so we can doom it
+ */
+nsDiskCacheBinding *
+nsDiskCacheBindery::FindActiveBinding(uint32_t hashNumber)
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ // find hash entry for key
+ auto hashEntry = static_cast<HashTableEntry*>
+ (table.Search((void*)(uintptr_t)hashNumber));
+ if (!hashEntry) return nullptr;
+
+ // walk list looking for active entry
+ NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding");
+ nsDiskCacheBinding * binding = hashEntry->mBinding;
+ while (binding->mCacheEntry->IsDoomed()) {
+ binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
+ if (binding == hashEntry->mBinding) return nullptr;
+ }
+ return binding;
+}
+
+
+/**
+ * AddBinding
+ *
+ * Called from FindEntry() if we read an entry off of disk
+ * - it may already have a generation number
+ * - a generation number conflict is an error
+ *
+ * Called from BindEntry()
+ * - a generation number needs to be assigned
+ */
+nsresult
+nsDiskCacheBindery::AddBinding(nsDiskCacheBinding * binding)
+{
+ NS_ENSURE_ARG_POINTER(binding);
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+
+ // find hash entry for key
+ auto hashEntry = static_cast<HashTableEntry*>
+ (table.Add((void*)(uintptr_t)binding->mRecord.HashNumber(), fallible));
+ if (!hashEntry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (hashEntry->mBinding == nullptr) {
+ hashEntry->mBinding = binding;
+ if (binding->mGeneration == 0)
+ binding->mGeneration = 1; // if generation uninitialized, set it to 1
+
+ return NS_OK;
+ }
+
+
+ // insert binding in generation order
+ nsDiskCacheBinding * p = hashEntry->mBinding;
+ bool calcGeneration = (binding->mGeneration == 0); // do we need to calculate generation?
+ if (calcGeneration) binding->mGeneration = 1; // initialize to 1 if uninitialized
+ while (1) {
+
+ if (binding->mGeneration < p->mGeneration) {
+ // here we are
+ PR_INSERT_BEFORE(binding, p);
+ if (hashEntry->mBinding == p)
+ hashEntry->mBinding = binding;
+ break;
+ }
+
+ if (binding->mGeneration == p->mGeneration) {
+ if (calcGeneration) ++binding->mGeneration; // try the next generation
+ else {
+ NS_ERROR("### disk cache: generations collide!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ p = (nsDiskCacheBinding *)PR_NEXT_LINK(p);
+ if (p == hashEntry->mBinding) {
+ // end of line: insert here or die
+ p = (nsDiskCacheBinding *)PR_PREV_LINK(p); // back up and check generation
+ if (p->mGeneration == 255) {
+ NS_WARNING("### disk cache: generation capacity at full");
+ return NS_ERROR_UNEXPECTED;
+ }
+ PR_INSERT_BEFORE(binding, hashEntry->mBinding);
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+
+/**
+ * RemoveBinding : remove binding from collision detection on deactivation
+ */
+void
+nsDiskCacheBindery::RemoveBinding(nsDiskCacheBinding * binding)
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ if (!initialized) return;
+
+ void* key = (void *)(uintptr_t)binding->mRecord.HashNumber();
+ auto hashEntry =
+ static_cast<HashTableEntry*>(table.Search((void*)(uintptr_t) key));
+ if (!hashEntry) {
+ NS_WARNING("### disk cache: binding not in hashtable!");
+ return;
+ }
+
+ if (binding == hashEntry->mBinding) {
+ if (PR_CLIST_IS_EMPTY(binding)) {
+ // remove this hash entry
+ table.Remove((void*)(uintptr_t) binding->mRecord.HashNumber());
+ return;
+
+ } else {
+ // promote next binding to head, and unlink this binding
+ hashEntry->mBinding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
+ }
+ }
+ PR_REMOVE_AND_INIT_LINK(binding);
+}
+
+/**
+ * ActiveBindings: return true if any bindings have open descriptors.
+ */
+bool
+nsDiskCacheBindery::ActiveBindings()
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ if (!initialized) return false;
+
+ for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<HashTableEntry*>(iter.Get());
+ nsDiskCacheBinding* binding = entry->mBinding;
+ nsDiskCacheBinding* head = binding;
+ do {
+ if (binding->IsActive()) {
+ return true;
+ }
+ binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
+ } while (binding != head);
+ }
+
+ return false;
+}
+
+/**
+ * SizeOfExcludingThis: return the amount of heap memory (bytes) being used by
+ * the bindery.
+ */
+size_t
+nsDiskCacheBindery::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ if (!initialized) return 0;
+
+ size_t size = 0;
+
+ for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<HashTableEntry*>(iter.Get());
+ nsDiskCacheBinding* binding = entry->mBinding;
+
+ nsDiskCacheBinding* head = binding;
+ do {
+ size += aMallocSizeOf(binding);
+ if (binding->mStreamIO) {
+ size += binding->mStreamIO->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // No good way to get at mDeactivateEvent internals for proper
+ // size, so we use this as an estimate.
+ if (binding->mDeactivateEvent) {
+ size += aMallocSizeOf(binding->mDeactivateEvent);
+ }
+ binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
+ } while (binding != head);
+ }
+
+ return size;
+}
diff --git a/netwerk/cache/nsDiskCacheBinding.h b/netwerk/cache/nsDiskCacheBinding.h
new file mode 100644
index 0000000000..63c2094ce6
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheBinding.h
@@ -0,0 +1,123 @@
+/* -*- 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 _nsDiskCacheBinding_h_
+#define _nsDiskCacheBinding_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nspr.h"
+#include "PLDHashTable.h"
+
+#include "nsISupports.h"
+#include "nsCacheEntry.h"
+
+#include "nsDiskCacheMap.h"
+#include "nsDiskCacheStreams.h"
+
+
+/******************************************************************************
+ * nsDiskCacheBinding
+ *
+ * Created for disk cache specific data and stored in nsCacheEntry.mData as
+ * an nsISupports. Also stored in nsDiskCacheHashTable, with collisions
+ * linked by the PRCList.
+ *
+ *****************************************************************************/
+
+class nsDiskCacheDeviceDeactivateEntryEvent;
+
+class nsDiskCacheBinding : public nsISupports, public PRCList {
+ virtual ~nsDiskCacheBinding();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record);
+
+ nsresult EnsureStreamIO();
+ bool IsActive() { return mCacheEntry != nullptr;}
+
+// XXX make friends
+public:
+ nsCacheEntry* mCacheEntry; // back pointer to parent nsCacheEntry
+ nsDiskCacheRecord mRecord;
+ nsDiskCacheStreamIO* mStreamIO; // strong reference
+ bool mDoomed; // record is not stored in cache map
+ uint8_t mGeneration; // possibly just reservation
+
+ // If set, points to a pending event which will deactivate |mCacheEntry|.
+ // If not set then either |mCacheEntry| is not deactivated, or it has been
+ // deactivated but the device returned it from FindEntry() before the event
+ // fired. In both two latter cases this binding is to be considered valid.
+ nsDiskCacheDeviceDeactivateEntryEvent *mDeactivateEvent;
+};
+
+
+/******************************************************************************
+ * Utility Functions
+ *****************************************************************************/
+
+nsDiskCacheBinding * GetCacheEntryBinding(nsCacheEntry * entry);
+
+
+
+/******************************************************************************
+ * nsDiskCacheBindery
+ *
+ * Used to keep track of nsDiskCacheBinding associated with active/bound (and
+ * possibly doomed) entries. Lookups on 4 byte disk hash to find collisions
+ * (which need to be doomed, instead of just evicted. Collisions are linked
+ * using a PRCList to keep track of current generation number.
+ *
+ * Used to detect hash number collisions, and find available generation numbers.
+ *
+ * Not all nsDiskCacheBinding have a generation number.
+ *
+ * Generation numbers may be aquired late, or lost (when data fits in block file)
+ *
+ * Collisions can occur:
+ * BindEntry() - hashnumbers collide (possibly different keys)
+ *
+ * Generation number required:
+ * DeactivateEntry() - metadata written to disk, may require file
+ * GetFileForEntry() - force data to require file
+ * writing to stream - data size may require file
+ *
+ * Binding can be kept in PRCList in order of generation numbers.
+ * Binding with no generation number can be Appended to PRCList (last).
+ *
+ *****************************************************************************/
+
+class nsDiskCacheBindery {
+public:
+ nsDiskCacheBindery();
+ ~nsDiskCacheBindery();
+
+ void Init();
+ void Reset();
+
+ nsDiskCacheBinding * CreateBinding(nsCacheEntry * entry,
+ nsDiskCacheRecord * record);
+
+ nsDiskCacheBinding * FindActiveBinding(uint32_t hashNumber);
+ void RemoveBinding(nsDiskCacheBinding * binding);
+ bool ActiveBindings();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+private:
+ nsresult AddBinding(nsDiskCacheBinding * binding);
+
+ // member variables
+ static const PLDHashTableOps ops;
+ PLDHashTable table;
+ bool initialized;
+
+ static const uint32_t kInitialTableLength = 0;
+};
+
+#endif /* _nsDiskCacheBinding_h_ */
diff --git a/netwerk/cache/nsDiskCacheBlockFile.cpp b/netwerk/cache/nsDiskCacheBlockFile.cpp
new file mode 100644
index 0000000000..6a1f4182f2
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheBlockFile.cpp
@@ -0,0 +1,404 @@
+/* -*- 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 "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheBlockFile.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+/******************************************************************************
+ * nsDiskCacheBlockFile -
+ *****************************************************************************/
+
+/******************************************************************************
+ * Open
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::Open(nsIFile * blockFile,
+ uint32_t blockSize,
+ uint32_t bitMapSize,
+ nsDiskCache::CorruptCacheInfo * corruptInfo)
+{
+ NS_ENSURE_ARG_POINTER(corruptInfo);
+ *corruptInfo = nsDiskCache::kUnexpectedError;
+
+ if (bitMapSize % 32) {
+ *corruptInfo = nsDiskCache::kInvalidArgPointer;
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mBlockSize = blockSize;
+ mBitMapWords = bitMapSize / 32;
+ uint32_t bitMapBytes = mBitMapWords * 4;
+
+ // open the file - restricted to user, the data could be confidential
+ nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD);
+ if (NS_FAILED(rv)) {
+ *corruptInfo = nsDiskCache::kCouldNotCreateBlockFile;
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open "
+ "[this=%p] unable to open or create file: %d",
+ this, rv));
+ return rv; // unable to open or create file
+ }
+
+ // allocate bit map buffer
+ mBitMap = new uint32_t[mBitMapWords];
+
+ // check if we just creating the file
+ mFileSize = PR_Available(mFD);
+ if (mFileSize < 0) {
+ // XXX an error occurred. We could call PR_GetError(), but how would that help?
+ *corruptInfo = nsDiskCache::kBlockFileSizeError;
+ rv = NS_ERROR_UNEXPECTED;
+ goto error_exit;
+ }
+ if (mFileSize == 0) {
+ // initialize bit map and write it
+ memset(mBitMap, 0, bitMapBytes);
+ if (!Write(0, mBitMap, bitMapBytes)) {
+ *corruptInfo = nsDiskCache::kBlockFileBitMapWriteError;
+ goto error_exit;
+ }
+
+ } else if ((uint32_t)mFileSize < bitMapBytes) {
+ *corruptInfo = nsDiskCache::kBlockFileSizeLessThanBitMap;
+ rv = NS_ERROR_UNEXPECTED; // XXX NS_ERROR_CACHE_INVALID;
+ goto error_exit;
+
+ } else {
+ // read the bit map
+ const int32_t bytesRead = PR_Read(mFD, mBitMap, bitMapBytes);
+ if ((bytesRead < 0) || ((uint32_t)bytesRead < bitMapBytes)) {
+ *corruptInfo = nsDiskCache::kBlockFileBitMapReadError;
+ rv = NS_ERROR_UNEXPECTED;
+ goto error_exit;
+ }
+#if defined(IS_LITTLE_ENDIAN)
+ // Swap from network format
+ for (unsigned int i = 0; i < mBitMapWords; ++i)
+ mBitMap[i] = ntohl(mBitMap[i]);
+#endif
+ // validate block file size
+ // Because not whole blocks are written, the size may be a
+ // little bit smaller than used blocks times blocksize,
+ // because the last block will generally not be 'whole'.
+ const uint32_t estimatedSize = CalcBlockFileSize();
+ if ((uint32_t)mFileSize + blockSize < estimatedSize) {
+ *corruptInfo = nsDiskCache::kBlockFileEstimatedSizeError;
+ rv = NS_ERROR_UNEXPECTED;
+ goto error_exit;
+ }
+ }
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded",
+ this));
+ return NS_OK;
+
+error_exit:
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with "
+ "error %d", this, rv));
+ Close(false);
+ return rv;
+}
+
+
+/******************************************************************************
+ * Close
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::Close(bool flush)
+{
+ nsresult rv = NS_OK;
+
+ if (mFD) {
+ if (flush)
+ rv = FlushBitMap();
+ PRStatus err = PR_Close(mFD);
+ if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS))
+ rv = NS_ERROR_UNEXPECTED;
+ mFD = nullptr;
+ }
+
+ if (mBitMap) {
+ delete [] mBitMap;
+ mBitMap = nullptr;
+ }
+
+ return rv;
+}
+
+
+/******************************************************************************
+ * AllocateBlocks
+ *
+ * Allocates 1-4 blocks, using a first fit strategy,
+ * so that no group of blocks spans a quad block boundary.
+ *
+ * Returns block number of first block allocated or -1 on failure.
+ *
+ *****************************************************************************/
+int32_t
+nsDiskCacheBlockFile::AllocateBlocks(int32_t numBlocks)
+{
+ const int maxPos = 32 - numBlocks;
+ const uint32_t mask = (0x01 << numBlocks) - 1;
+ for (unsigned int i = 0; i < mBitMapWords; ++i) {
+ uint32_t mapWord = ~mBitMap[i]; // flip bits so free bits are 1
+ if (mapWord) { // At least one free bit
+ // Binary search for first free bit in word
+ int bit = 0;
+ if ((mapWord & 0x0FFFF) == 0) { bit |= 16; mapWord >>= 16; }
+ if ((mapWord & 0x000FF) == 0) { bit |= 8; mapWord >>= 8; }
+ if ((mapWord & 0x0000F) == 0) { bit |= 4; mapWord >>= 4; }
+ if ((mapWord & 0x00003) == 0) { bit |= 2; mapWord >>= 2; }
+ if ((mapWord & 0x00001) == 0) { bit |= 1; mapWord >>= 1; }
+ // Find first fit for mask
+ for (; bit <= maxPos; ++bit) {
+ // all bits selected by mask are 1, so free
+ if ((mask & mapWord) == mask) {
+ mBitMap[i] |= mask << bit;
+ mBitMapDirty = true;
+ return (int32_t)i * 32 + bit;
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
+
+/******************************************************************************
+ * DeallocateBlocks
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::DeallocateBlocks( int32_t startBlock, int32_t numBlocks)
+{
+ if (!mFD) return NS_ERROR_NOT_AVAILABLE;
+
+ if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) ||
+ (numBlocks < 1) || (numBlocks > 4))
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ const int32_t startWord = startBlock >> 5; // Divide by 32
+ const uint32_t startBit = startBlock & 31; // Modulo by 32
+
+ // make sure requested deallocation doesn't span a word boundary
+ if (startBit + numBlocks > 32) return NS_ERROR_UNEXPECTED;
+ uint32_t mask = ((0x01 << numBlocks) - 1) << startBit;
+
+ // make sure requested deallocation is currently allocated
+ if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_ABORT;
+
+ mBitMap[startWord] ^= mask; // flips the bits off;
+ mBitMapDirty = true;
+ // XXX rv = FlushBitMap(); // coherency vs. performance
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * WriteBlocks
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::WriteBlocks( void * buffer,
+ uint32_t size,
+ int32_t numBlocks,
+ int32_t * startBlock)
+{
+ // presume buffer != nullptr and startBlock != nullptr
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_AVAILABLE);
+
+ // allocate some blocks in the cache block file
+ *startBlock = AllocateBlocks(numBlocks);
+ if (*startBlock < 0)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // seek to block position
+ int32_t blockPos = mBitMapWords * 4 + *startBlock * mBlockSize;
+
+ // write the blocks
+ return Write(blockPos, buffer, size) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+
+/******************************************************************************
+ * ReadBlocks
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::ReadBlocks( void * buffer,
+ int32_t startBlock,
+ int32_t numBlocks,
+ int32_t * bytesRead)
+{
+ // presume buffer != nullptr and bytesRead != bytesRead
+
+ if (!mFD) return NS_ERROR_NOT_AVAILABLE;
+ nsresult rv = VerifyAllocation(startBlock, numBlocks);
+ if (NS_FAILED(rv)) return rv;
+
+ // seek to block position
+ int32_t blockPos = mBitMapWords * 4 + startBlock * mBlockSize;
+ int32_t filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET);
+ if (filePos != blockPos) return NS_ERROR_UNEXPECTED;
+
+ // read the blocks
+ int32_t bytesToRead = *bytesRead;
+ if ((bytesToRead <= 0) || ((uint32_t)bytesToRead > mBlockSize * numBlocks)) {
+ bytesToRead = mBlockSize * numBlocks;
+ }
+ *bytesRead = PR_Read(mFD, buffer, bytesToRead);
+
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Read [this=%p] "
+ "returned %d / %d bytes", this, *bytesRead, bytesToRead));
+
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * FlushBitMap
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::FlushBitMap()
+{
+ if (!mBitMapDirty) return NS_OK;
+
+#if defined(IS_LITTLE_ENDIAN)
+ uint32_t *bitmap = new uint32_t[mBitMapWords];
+ // Copy and swap to network format
+ uint32_t *p = bitmap;
+ for (unsigned int i = 0; i < mBitMapWords; ++i, ++p)
+ *p = htonl(mBitMap[i]);
+#else
+ uint32_t *bitmap = mBitMap;
+#endif
+
+ // write bitmap
+ bool written = Write(0, bitmap, mBitMapWords * 4);
+#if defined(IS_LITTLE_ENDIAN)
+ delete [] bitmap;
+#endif
+ if (!written)
+ return NS_ERROR_UNEXPECTED;
+
+ PRStatus err = PR_Sync(mFD);
+ if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
+
+ mBitMapDirty = false;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * VerifyAllocation
+ *
+ * Return values:
+ * NS_OK if all bits are marked allocated
+ * NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints
+ * NS_ERROR_FAILURE if some or all the bits are marked unallocated
+ *
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::VerifyAllocation( int32_t startBlock, int32_t numBlocks)
+{
+ if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) ||
+ (numBlocks < 1) || (numBlocks > 4))
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ const int32_t startWord = startBlock >> 5; // Divide by 32
+ const uint32_t startBit = startBlock & 31; // Modulo by 32
+
+ // make sure requested deallocation doesn't span a word boundary
+ if (startBit + numBlocks > 32) return NS_ERROR_ILLEGAL_VALUE;
+ uint32_t mask = ((0x01 << numBlocks) - 1) << startBit;
+
+ // check if all specified blocks are currently allocated
+ if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * CalcBlockFileSize
+ *
+ * Return size of the block file according to the bits set in mBitmap
+ *
+ *****************************************************************************/
+uint32_t
+nsDiskCacheBlockFile::CalcBlockFileSize()
+{
+ // search for last byte in mBitMap with allocated bits
+ uint32_t estimatedSize = mBitMapWords * 4;
+ int32_t i = mBitMapWords;
+ while (--i >= 0) {
+ if (mBitMap[i]) break;
+ }
+
+ if (i >= 0) {
+ // binary search to find last allocated bit in byte
+ uint32_t mapWord = mBitMap[i];
+ uint32_t lastBit = 31;
+ if ((mapWord & 0xFFFF0000) == 0) { lastBit ^= 16; mapWord <<= 16; }
+ if ((mapWord & 0xFF000000) == 0) { lastBit ^= 8; mapWord <<= 8; }
+ if ((mapWord & 0xF0000000) == 0) { lastBit ^= 4; mapWord <<= 4; }
+ if ((mapWord & 0xC0000000) == 0) { lastBit ^= 2; mapWord <<= 2; }
+ if ((mapWord & 0x80000000) == 0) { lastBit ^= 1; mapWord <<= 1; }
+ estimatedSize += (i * 32 + lastBit + 1) * mBlockSize;
+ }
+
+ return estimatedSize;
+}
+
+/******************************************************************************
+ * Write
+ *
+ * Wrapper around PR_Write that grows file in larger chunks to combat fragmentation
+ *
+ *****************************************************************************/
+bool
+nsDiskCacheBlockFile::Write(int32_t offset, const void *buf, int32_t amount)
+{
+ /* Grow the file to 4mb right away, then double it until the file grows to 20mb.
+ 20mb is a magic threshold because OSX stops autodefragging files bigger than that.
+ Beyond 20mb grow in 4mb chunks.
+ */
+ const int32_t upTo = offset + amount;
+ // Use a conservative definition of 20MB
+ const int32_t minPreallocate = 4*1024*1024;
+ const int32_t maxPreallocate = 20*1000*1000;
+ if (mFileSize < upTo) {
+ // maximal file size
+ const int32_t maxFileSize = mBitMapWords * 4 * (mBlockSize * 8 + 1);
+ if (upTo > maxPreallocate) {
+ // grow the file as a multiple of minPreallocate
+ mFileSize = ((upTo + minPreallocate - 1) / minPreallocate) * minPreallocate;
+ } else {
+ // Grow quickly between 1MB to 20MB
+ if (mFileSize)
+ while(mFileSize < upTo)
+ mFileSize *= 2;
+ mFileSize = clamped(mFileSize, minPreallocate, maxPreallocate);
+ }
+ mFileSize = std::min(mFileSize, maxFileSize);
+#if !defined(XP_MACOSX)
+ mozilla::fallocate(mFD, mFileSize);
+#endif
+ }
+ if (PR_Seek(mFD, offset, PR_SEEK_SET) != offset)
+ return false;
+ return PR_Write(mFD, buf, amount) == amount;
+}
+
+size_t
+nsDiskCacheBlockFile::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
+{
+ return aMallocSizeOf(mBitMap) + aMallocSizeOf(mFD);
+}
diff --git a/netwerk/cache/nsDiskCacheBlockFile.h b/netwerk/cache/nsDiskCacheBlockFile.h
new file mode 100644
index 0000000000..058cc7fcca
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheBlockFile.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 _nsDiskCacheBlockFile_h_
+#define _nsDiskCacheBlockFile_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nsIFile.h"
+#include "nsDiskCache.h"
+
+/******************************************************************************
+ * nsDiskCacheBlockFile
+ *
+ * The structure of a cache block file is a 4096 bytes bit map, followed by
+ * some number of blocks of mBlockSize. The creator of a
+ * nsDiskCacheBlockFile object must provide the block size for a given file.
+ *
+ *****************************************************************************/
+class nsDiskCacheBlockFile {
+public:
+ nsDiskCacheBlockFile()
+ : mFD(nullptr)
+ , mBitMap(nullptr)
+ , mBlockSize(0)
+ , mBitMapWords(0)
+ , mFileSize(0)
+ , mBitMapDirty(false)
+ {}
+ ~nsDiskCacheBlockFile() { (void) Close(true); }
+
+ nsresult Open( nsIFile * blockFile, uint32_t blockSize,
+ uint32_t bitMapSize, nsDiskCache::CorruptCacheInfo * corruptInfo);
+ nsresult Close(bool flush);
+
+ /*
+ * Trim
+ * Truncates the block file to the end of the last allocated block.
+ */
+ nsresult Trim() { return nsDiskCache::Truncate(mFD, CalcBlockFileSize()); }
+ nsresult DeallocateBlocks( int32_t startBlock, int32_t numBlocks);
+ nsresult WriteBlocks( void * buffer, uint32_t size, int32_t numBlocks,
+ int32_t * startBlock);
+ nsresult ReadBlocks( void * buffer, int32_t startBlock, int32_t numBlocks,
+ int32_t * bytesRead);
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+private:
+ nsresult FlushBitMap();
+ int32_t AllocateBlocks( int32_t numBlocks);
+ nsresult VerifyAllocation( int32_t startBlock, int32_t numBLocks);
+ uint32_t CalcBlockFileSize();
+ bool Write(int32_t offset, const void *buf, int32_t amount);
+
+/**
+ * Data members
+ */
+ PRFileDesc * mFD;
+ uint32_t * mBitMap; // XXX future: array of bit map blocks
+ uint32_t mBlockSize;
+ uint32_t mBitMapWords;
+ int32_t mFileSize;
+ bool mBitMapDirty;
+};
+
+#endif // _nsDiskCacheBlockFile_h_
diff --git a/netwerk/cache/nsDiskCacheDevice.cpp b/netwerk/cache/nsDiskCacheDevice.cpp
new file mode 100644
index 0000000000..ac91534ff6
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDevice.cpp
@@ -0,0 +1,1149 @@
+/* -*- 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 <limits.h>
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsCache.h"
+#include "nsIMemoryReporter.h"
+
+// include files for ftruncate (or equivalent)
+#if defined(XP_UNIX)
+#include <unistd.h>
+#elif defined(XP_WIN)
+#include <windows.h>
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+#include "prthread.h"
+
+#include "private/pprio.h"
+
+#include "nsDiskCacheDevice.h"
+#include "nsDiskCacheEntry.h"
+#include "nsDiskCacheMap.h"
+#include "nsDiskCacheStreams.h"
+
+#include "nsDiskCache.h"
+
+#include "nsCacheService.h"
+
+#include "nsDeleteDir.h"
+
+#include "nsICacheVisitor.h"
+#include "nsReadableUtils.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCRT.h"
+#include "nsCOMArray.h"
+#include "nsISimpleEnumerator.h"
+
+#include "nsThreadUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Telemetry.h"
+
+static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
+using namespace mozilla;
+
+class nsDiskCacheDeviceDeactivateEntryEvent : public Runnable {
+public:
+ nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice *device,
+ nsCacheEntry * entry,
+ nsDiskCacheBinding * binding)
+ : mCanceled(false),
+ mEntry(entry),
+ mDevice(device),
+ mBinding(binding)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCacheServiceAutoLock lock;
+ CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
+ if (!mCanceled) {
+ (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
+ }
+ return NS_OK;
+ }
+
+ void CancelEvent() { mCanceled = true; }
+private:
+ bool mCanceled;
+ nsCacheEntry *mEntry;
+ nsDiskCacheDevice *mDevice;
+ nsDiskCacheBinding *mBinding;
+};
+
+class nsEvictDiskCacheEntriesEvent : public Runnable {
+public:
+ explicit nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice *device)
+ : mDevice(device) {}
+
+ NS_IMETHOD Run() override
+ {
+ nsCacheServiceAutoLock lock;
+ mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
+ return NS_OK;
+ }
+
+private:
+ nsDiskCacheDevice *mDevice;
+};
+
+/******************************************************************************
+ * nsDiskCacheEvictor
+ *
+ * Helper class for nsDiskCacheDevice.
+ *
+ *****************************************************************************/
+
+class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
+{
+public:
+ nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
+ nsDiskCacheBindery * cacheBindery,
+ uint32_t targetSize,
+ const char * clientID)
+ : mCacheMap(cacheMap)
+ , mBindery(cacheBindery)
+ , mTargetSize(targetSize)
+ , mClientID(clientID)
+ {
+ mClientIDSize = clientID ? strlen(clientID) : 0;
+ }
+
+ virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord);
+
+private:
+ nsDiskCacheMap * mCacheMap;
+ nsDiskCacheBindery * mBindery;
+ uint32_t mTargetSize;
+ const char * mClientID;
+ uint32_t mClientIDSize;
+};
+
+
+int32_t
+nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
+{
+ if (mCacheMap->TotalSize() < mTargetSize)
+ return kStopVisitingRecords;
+
+ if (mClientID) {
+ // we're just evicting records for a specific client
+ nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
+ if (!diskEntry)
+ return kVisitNextRecord; // XXX or delete record?
+
+ // Compare clientID's without malloc
+ if ((diskEntry->mKeySize <= mClientIDSize) ||
+ (diskEntry->Key()[mClientIDSize] != ':') ||
+ (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
+ return kVisitNextRecord; // clientID doesn't match, skip it
+ }
+ }
+
+ nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
+ if (binding) {
+ // If the entry is pending deactivation, cancel deactivation and doom
+ // the entry
+ if (binding->mDeactivateEvent) {
+ binding->mDeactivateEvent->CancelEvent();
+ binding->mDeactivateEvent = nullptr;
+ }
+ // We are currently using this entry, so all we can do is doom it.
+ // Since we're enumerating the records, we don't want to call
+ // DeleteRecord when nsCacheService::DoomEntry() calls us back.
+ binding->mDoomed = true; // mark binding record as 'deleted'
+ nsCacheService::DoomEntry(binding->mCacheEntry);
+ } else {
+ // entry not in use, just delete storage because we're enumerating the records
+ (void) mCacheMap->DeleteStorage(mapRecord);
+ }
+
+ return kDeleteRecordAndContinue; // this will REALLY delete the record
+}
+
+
+/******************************************************************************
+ * nsDiskCacheDeviceInfo
+ *****************************************************************************/
+
+class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEDEVICEINFO
+
+ explicit nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
+ : mDevice(device)
+ {
+ }
+
+private:
+ virtual ~nsDiskCacheDeviceInfo() {}
+
+ nsDiskCacheDevice* mDevice;
+};
+
+NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
+{
+ NS_ENSURE_ARG_POINTER(aDescription);
+ *aDescription = NS_strdup("Disk cache device");
+ return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
+{
+ NS_ENSURE_ARG_POINTER(usageReport);
+ nsCString buffer;
+
+ buffer.AssignLiteral(" <tr>\n"
+ " <th>Cache Directory:</th>\n"
+ " <td>");
+ nsCOMPtr<nsIFile> cacheDir;
+ nsAutoString path;
+ mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
+ nsresult rv = cacheDir->GetPath(path);
+ if (NS_SUCCEEDED(rv)) {
+ AppendUTF16toUTF8(path, buffer);
+ } else {
+ buffer.AppendLiteral("directory unavailable");
+ }
+ buffer.AppendLiteral("</td>\n"
+ " </tr>\n");
+
+ *usageReport = ToNewCString(buffer);
+ if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
+{
+ NS_ENSURE_ARG_POINTER(aEntryCount);
+ *aEntryCount = mDevice->getEntryCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
+{
+ NS_ENSURE_ARG_POINTER(aTotalSize);
+ // Returned unit's are in bytes
+ *aTotalSize = mDevice->getCacheSize() * 1024;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
+{
+ NS_ENSURE_ARG_POINTER(aMaximumSize);
+ // Returned unit's are in bytes
+ *aMaximumSize = mDevice->getCacheCapacity() * 1024;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsDiskCache
+ *****************************************************************************/
+
+/**
+ * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
+ *
+ * See http://burtleburtle.net/bob/hash/evahash.html for more information
+ * about this hash function.
+ *
+ * This algorithm of this method implies nsDiskCacheRecords 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 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);
+}
+
+PLDHashNumber
+nsDiskCache::Hash(const char * key, PLDHashNumber initval)
+{
+ const uint8_t *k = reinterpret_cast<const uint8_t*>(key);
+ uint32_t a, b, c, len, length;
+
+ length = strlen(key);
+ /* Set up the internal state */
+ len = length;
+ a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
+ c = initval; /* 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 += length;
+ switch(len) { /* all the case statements fall through */
+ case 11: c += (uint32_t(k[10])<<24); MOZ_FALLTHROUGH;
+ case 10: c += (uint32_t(k[9])<<16); MOZ_FALLTHROUGH;
+ case 9 : c += (uint32_t(k[8])<<8); MOZ_FALLTHROUGH;
+ /* the low-order byte of c is reserved for the length */
+ case 8 : b += (uint32_t(k[7])<<24); MOZ_FALLTHROUGH;
+ case 7 : b += (uint32_t(k[6])<<16); MOZ_FALLTHROUGH;
+ case 6 : b += (uint32_t(k[5])<<8); MOZ_FALLTHROUGH;
+ case 5 : b += k[4]; MOZ_FALLTHROUGH;
+ case 4 : a += (uint32_t(k[3])<<24); MOZ_FALLTHROUGH;
+ case 3 : a += (uint32_t(k[2])<<16); MOZ_FALLTHROUGH;
+ case 2 : a += (uint32_t(k[1])<<8); MOZ_FALLTHROUGH;
+ case 1 : a += k[0];
+ /* case 0: nothing left to add */
+ }
+ hashmix(a, b, c);
+
+ return c;
+}
+
+nsresult
+nsDiskCache::Truncate(PRFileDesc * fd, uint32_t newEOF)
+{
+ // use modified SetEOF from nsFileStreams::SetEOF()
+
+#if defined(XP_UNIX)
+ if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+
+#elif defined(XP_WIN)
+ int32_t cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
+ if (cnt == -1) return NS_ERROR_FAILURE;
+ if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+
+#else
+ // add implementations for other platforms here
+#endif
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsDiskCacheDevice
+ *****************************************************************************/
+
+nsDiskCacheDevice::nsDiskCacheDevice()
+ : mCacheCapacity(0)
+ , mMaxEntrySize(-1) // -1 means "no limit"
+ , mInitialized(false)
+ , mClearingDiskCache(false)
+{
+}
+
+nsDiskCacheDevice::~nsDiskCacheDevice()
+{
+ Shutdown();
+}
+
+
+/**
+ * methods of nsCacheDevice
+ */
+nsresult
+nsDiskCacheDevice::Init()
+{
+ nsresult rv;
+
+ if (Initialized()) {
+ NS_ERROR("Disk cache already initialized!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mCacheDirectory)
+ return NS_ERROR_FAILURE;
+
+ mBindery.Init();
+
+ // Open Disk Cache
+ rv = OpenDiskCache();
+ if (NS_FAILED(rv)) {
+ (void) mCacheMap.Close(false);
+ return rv;
+ }
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::Shutdown()
+{
+ nsCacheService::AssertOwnsLock();
+
+ nsresult rv = Shutdown_Private(true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_OK;
+}
+
+
+nsresult
+nsDiskCacheDevice::Shutdown_Private(bool flush)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
+
+ if (Initialized()) {
+ // check cache limits in case we need to evict.
+ EvictDiskCacheEntries(mCacheCapacity);
+
+ // At this point there may be a number of pending cache-requests on the
+ // cache-io thread. Wait for all these to run before we wipe out our
+ // datastructures (see bug #620660)
+ (void) nsCacheService::SyncWithCacheIOThread();
+
+ // write out persistent information about the cache.
+ (void) mCacheMap.Close(flush);
+
+ mBindery.Reset();
+
+ mInitialized = false;
+ }
+
+ return NS_OK;
+}
+
+
+const char *
+nsDiskCacheDevice::GetDeviceID()
+{
+ return DISK_CACHE_DEVICE_ID;
+}
+
+/**
+ * FindEntry -
+ *
+ * cases: key not in disk cache, hash number free
+ * key not in disk cache, hash number used
+ * key in disk cache
+ *
+ * NOTE: called while holding the cache service lock
+ */
+nsCacheEntry *
+nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
+{
+ Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH_2> timer;
+ if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
+ if (mClearingDiskCache) return nullptr;
+ nsDiskCacheRecord record;
+ nsDiskCacheBinding * binding = nullptr;
+ PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
+
+ *collision = false;
+
+ binding = mBindery.FindActiveBinding(hashNumber);
+ if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
+ *collision = true;
+ return nullptr;
+ } else if (binding && binding->mDeactivateEvent) {
+ binding->mDeactivateEvent->CancelEvent();
+ binding->mDeactivateEvent = nullptr;
+ CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
+ "req-key=%s entry-key=%s\n",
+ binding->mCacheEntry, key, binding->mCacheEntry->Key()));
+
+ return binding->mCacheEntry; // just return this one, observing that
+ // FindActiveBinding() does not return
+ // bindings to doomed entries
+ }
+ binding = nullptr;
+
+ // lookup hash number in cache map
+ nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
+ if (NS_FAILED(rv)) return nullptr; // XXX log error?
+
+ nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
+ if (!diskEntry) return nullptr;
+
+ // compare key to be sure
+ if (!key->Equals(diskEntry->Key())) {
+ *collision = true;
+ return nullptr;
+ }
+
+ nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
+ if (entry) {
+ binding = mBindery.CreateBinding(entry, &record);
+ if (!binding) {
+ delete entry;
+ entry = nullptr;
+ }
+ }
+
+ if (!entry) {
+ (void) mCacheMap.DeleteStorage(&record);
+ (void) mCacheMap.DeleteRecord(&record);
+ }
+
+ return entry;
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
+{
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
+ entry, binding->mRecord.HashNumber()));
+
+ nsDiskCacheDeviceDeactivateEntryEvent *event =
+ new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
+
+ // ensure we can cancel the event via the binding later if necessary
+ binding->mDeactivateEvent = event;
+
+ DebugOnly<nsresult> rv = nsCacheService::DispatchToCacheIOThread(event);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
+ "deactivation event");
+ return NS_OK;
+}
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
+ nsDiskCacheBinding * binding)
+{
+ nsresult rv = NS_OK;
+ if (entry->IsDoomed()) {
+ // delete data, entry, record from disk for entry
+ rv = mCacheMap.DeleteStorage(&binding->mRecord);
+
+ } else {
+ // save stuff to disk for entry
+ rv = mCacheMap.WriteDiskCacheEntry(binding);
+ if (NS_FAILED(rv)) {
+ // clean up as best we can
+ (void) mCacheMap.DeleteStorage(&binding->mRecord);
+ (void) mCacheMap.DeleteRecord(&binding->mRecord);
+ binding->mDoomed = true; // record is no longer in cache map
+ }
+ }
+
+ mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
+ delete entry; // which will release binding
+ return rv;
+}
+
+
+/**
+ * BindEntry()
+ * no hash number collision -> no problem
+ * collision
+ * record not active -> evict, no problem
+ * record is active
+ * record is already doomed -> record shouldn't have been in map, no problem
+ * record is not doomed -> doom, and replace record in map
+ *
+ * walk matching hashnumber list to find lowest generation number
+ * take generation number from other (data/meta) location,
+ * or walk active list
+ *
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
+{
+ if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
+ if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
+ nsresult rv = NS_OK;
+ nsDiskCacheRecord record, oldRecord;
+ nsDiskCacheBinding *binding;
+ PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
+
+ // Find out if there is already an active binding for this hash. If yes it
+ // should have another key since BindEntry() shouldn't be called twice for
+ // the same entry. Doom the old entry, the new one will get another
+ // generation number so files won't collide.
+ binding = mBindery.FindActiveBinding(hashNumber);
+ if (binding) {
+ NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
+ "BindEntry called for already bound entry!");
+ // If the entry is pending deactivation, cancel deactivation
+ if (binding->mDeactivateEvent) {
+ binding->mDeactivateEvent->CancelEvent();
+ binding->mDeactivateEvent = nullptr;
+ }
+ nsCacheService::DoomEntry(binding->mCacheEntry);
+ binding = nullptr;
+ }
+
+ // Lookup hash number in cache map. There can be a colliding inactive entry.
+ // See bug #321361 comment 21 for the scenario. If there is such entry,
+ // delete it.
+ rv = mCacheMap.FindRecord(hashNumber, &record);
+ if (NS_SUCCEEDED(rv)) {
+ nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
+ if (diskEntry) {
+ // compare key to be sure
+ if (!entry->Key()->Equals(diskEntry->Key())) {
+ mCacheMap.DeleteStorage(&record);
+ rv = mCacheMap.DeleteRecord(&record);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ record = nsDiskCacheRecord();
+ }
+
+ // create a new record for this entry
+ record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
+ record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
+
+ CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
+ entry, record.HashNumber()));
+
+ if (!entry->IsDoomed()) {
+ // if entry isn't doomed, add it to the cache map
+ rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t oldHashNumber = oldRecord.HashNumber();
+ if (oldHashNumber) {
+ // gotta evict this one first
+ nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
+ if (oldBinding) {
+ // XXX if debug : compare keys for hashNumber collision
+
+ if (!oldBinding->mCacheEntry->IsDoomed()) {
+ // If the old entry is pending deactivation, cancel deactivation
+ if (oldBinding->mDeactivateEvent) {
+ oldBinding->mDeactivateEvent->CancelEvent();
+ oldBinding->mDeactivateEvent = nullptr;
+ }
+ // we've got a live one!
+ nsCacheService::DoomEntry(oldBinding->mCacheEntry);
+ // storage will be delete when oldBinding->mCacheEntry is Deactivated
+ }
+ } else {
+ // delete storage
+ // XXX if debug : compare keys for hashNumber collision
+ rv = mCacheMap.DeleteStorage(&oldRecord);
+ if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
+ }
+ }
+ }
+
+ // Make sure this entry has its associated nsDiskCacheBinding attached.
+ binding = mBindery.CreateBinding(entry, &record);
+ NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
+ if (!binding) return NS_ERROR_OUT_OF_MEMORY;
+ NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
+
+ return NS_OK;
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+void
+nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
+
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
+ if (!binding)
+ return;
+
+ if (!binding->mDoomed) {
+ // so it can't be seen by FindEntry() ever again.
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ mCacheMap.DeleteRecord(&binding->mRecord);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
+ binding->mDoomed = true; // record in no longer in cache map
+ }
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
+ entry, mode, offset));
+
+ NS_ENSURE_ARG_POINTER(entry);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv;
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
+
+ rv = binding->EnsureStreamIO();
+ if (NS_FAILED(rv)) return rv;
+
+ return binding->mStreamIO->GetInputStream(offset, result);
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
+ entry, mode, offset));
+
+ NS_ENSURE_ARG_POINTER(entry);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv;
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
+
+ rv = binding->EnsureStreamIO();
+ if (NS_FAILED(rv)) return rv;
+
+ return binding->mStreamIO->GetOutputStream(offset, result);
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
+ nsIFile ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = nullptr;
+
+ nsresult rv;
+
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ // check/set binding->mRecord for separate file, sync w/mCacheMap
+ if (binding->mRecord.DataLocationInitialized()) {
+ if (binding->mRecord.DataFile() != 0)
+ return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
+
+ NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
+ } else {
+ binding->mRecord.SetDataFileGeneration(binding->mGeneration);
+ binding->mRecord.SetDataFileSize(0); // 1k minimum
+ if (!binding->mDoomed) {
+ // record stored in cache map, so update it
+ rv = mCacheMap.UpdateRecord(&binding->mRecord);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
+ nsDiskCache::kData,
+ false,
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*result = file);
+ return NS_OK;
+}
+
+
+/**
+ * This routine will get called every time an open descriptor is written to.
+ *
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
+ entry, deltaSize));
+
+ // If passed a negative value, then there's nothing to do.
+ if (deltaSize < 0)
+ return NS_OK;
+
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
+
+ uint32_t newSize = entry->DataSize() + deltaSize;
+ uint32_t newSizeK = ((newSize + 0x3FF) >> 10);
+
+ // If the new size is larger than max. file size or larger than
+ // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
+ if (EntryIsTooBig(newSize)) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return NS_ERROR_ABORT;
+ }
+
+ uint32_t sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
+
+ // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
+ // the target capacity should be calculated the same way.
+ if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
+ if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
+
+ // pre-evict entries to make space for new data
+ uint32_t targetCapacity = mCacheCapacity > (newSizeK - sizeK)
+ ? mCacheCapacity - (newSizeK - sizeK)
+ : 0;
+ EvictDiskCacheEntries(targetCapacity);
+
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * EntryInfoVisitor
+ *****************************************************************************/
+class EntryInfoVisitor : public nsDiskCacheRecordVisitor
+{
+public:
+ EntryInfoVisitor(nsDiskCacheMap * cacheMap,
+ nsICacheVisitor * visitor)
+ : mCacheMap(cacheMap)
+ , mVisitor(visitor)
+ {}
+
+ virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord)
+ {
+ // XXX optimization: do we have this record in memory?
+
+ // read in the entry (metadata)
+ nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
+ if (!diskEntry) {
+ return kVisitNextRecord;
+ }
+
+ // create nsICacheEntryInfo
+ nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
+ if (!entryInfo) {
+ return kStopVisitingRecords;
+ }
+ nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
+
+ bool keepGoing;
+ (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
+ return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
+ }
+
+private:
+ nsDiskCacheMap * mCacheMap;
+ nsICacheVisitor * mVisitor;
+};
+
+
+nsresult
+nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
+{
+ if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
+ nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
+ nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
+
+ bool keepGoing;
+ nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
+ if (NS_FAILED(rv)) return rv;
+
+ if (keepGoing) {
+ EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
+ return mCacheMap.VisitRecords(&infoVisitor);
+ }
+
+ return NS_OK;
+}
+
+// Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
+bool
+nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize)
+{
+ if (mMaxEntrySize == -1) // no limit
+ return entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
+ else
+ return entrySize > mMaxEntrySize ||
+ entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
+}
+
+nsresult
+nsDiskCacheDevice::EvictEntries(const char * clientID)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
+
+ if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
+ nsresult rv;
+
+ if (clientID == nullptr) {
+ // we're clearing the entire disk cache
+ rv = ClearDiskCache();
+ if (rv != NS_ERROR_CACHE_IN_USE)
+ return rv;
+ }
+
+ nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
+ rv = mCacheMap.VisitRecords(&evictor);
+
+ if (clientID == nullptr) // we tried to clear the entire cache
+ rv = mCacheMap.Trim(); // so trim cache block files (if possible)
+ return rv;
+}
+
+
+/**
+ * private methods
+ */
+
+nsresult
+nsDiskCacheDevice::OpenDiskCache()
+{
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
+ // if we don't have a cache directory, create one and open it
+ bool exists;
+ nsresult rv = mCacheDirectory->Exists(&exists);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (exists) {
+ // Try opening cache map file.
+ nsDiskCache::CorruptCacheInfo corruptInfo;
+ rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
+
+ if (rv == NS_ERROR_ALREADY_INITIALIZED) {
+ NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
+ } else if (NS_FAILED(rv)) {
+ // Consider cache corrupt: delete it
+ // delay delete by 1 minute to avoid IO thrash at startup
+ rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
+ if (NS_FAILED(rv))
+ return rv;
+ exists = false;
+ }
+ }
+
+ // if we don't have a cache directory, create one and open it
+ if (!exists) {
+ nsCacheService::MarkStartingFresh();
+ rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ CACHE_LOG_PATH(LogLevel::Info, "\ncreate cache directory: %s\n", mCacheDirectory);
+ CACHE_LOG_INFO(("mCacheDirectory->Create() = %x\n", rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // reopen the cache map
+ nsDiskCache::CorruptCacheInfo corruptInfo;
+ rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsDiskCacheDevice::ClearDiskCache()
+{
+ if (mBindery.ActiveBindings())
+ return NS_ERROR_CACHE_IN_USE;
+
+ mClearingDiskCache = true;
+
+ nsresult rv = Shutdown_Private(false); // false: don't bother flushing
+ if (NS_FAILED(rv))
+ return rv;
+
+ mClearingDiskCache = false;
+
+ // If the disk cache directory is already gone, then it's not an error if
+ // we fail to delete it ;-)
+ rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ return rv;
+
+ return Init();
+}
+
+
+nsresult
+nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
+ targetCapacity));
+
+ NS_ASSERTION(targetCapacity > 0, "oops");
+
+ if (mCacheMap.TotalSize() < targetCapacity)
+ return NS_OK;
+
+ // targetCapacity is in KiB's
+ nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
+ return mCacheMap.EvictRecords(&evictor);
+}
+
+
+/**
+ * methods for prefs
+ */
+
+void
+nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
+{
+ nsresult rv;
+ bool exists;
+
+ if (Initialized()) {
+ NS_ASSERTION(false, "Cannot switch cache directory when initialized");
+ return;
+ }
+
+ if (!parentDir) {
+ mCacheDirectory = nullptr;
+ return;
+ }
+
+ // ensure parent directory exists
+ rv = parentDir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv)) return;
+
+ // ensure cache directory exists
+ nsCOMPtr<nsIFile> directory;
+
+ rv = parentDir->Clone(getter_AddRefs(directory));
+ if (NS_FAILED(rv)) return;
+ rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
+ if (NS_FAILED(rv)) return;
+
+ mCacheDirectory = do_QueryInterface(directory);
+}
+
+
+void
+nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
+{
+ *result = mCacheDirectory;
+ NS_IF_ADDREF(*result);
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+void
+nsDiskCacheDevice::SetCapacity(uint32_t capacity)
+{
+ // Units are KiB's
+ mCacheCapacity = capacity;
+ if (Initialized()) {
+ if (NS_IsMainThread()) {
+ // Do not evict entries on the main thread
+ nsCacheService::DispatchToCacheIOThread(
+ new nsEvictDiskCacheEntriesEvent(this));
+ } else {
+ // start evicting entries if the new size is smaller!
+ EvictDiskCacheEntries(mCacheCapacity);
+ }
+ }
+ // Let cache map know of the new capacity
+ mCacheMap.NotifyCapacityChange(capacity);
+}
+
+
+uint32_t nsDiskCacheDevice::getCacheCapacity()
+{
+ return mCacheCapacity;
+}
+
+
+uint32_t nsDiskCacheDevice::getCacheSize()
+{
+ return mCacheMap.TotalSize();
+}
+
+
+uint32_t nsDiskCacheDevice::getEntryCount()
+{
+ return mCacheMap.EntryCount();
+}
+
+void
+nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
+{
+ // Internal units are bytes. Changing this only takes effect *after* the
+ // change and has no consequences for existing cache-entries
+ if (maxSizeInKilobytes >= 0)
+ mMaxEntrySize = maxSizeInKilobytes * 1024;
+ else
+ mMaxEntrySize = -1;
+}
+
+size_t
+nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
+{
+ size_t usage = aMallocSizeOf(this);
+
+ usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
+ usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
+
+ return usage;
+}
diff --git a/netwerk/cache/nsDiskCacheDevice.h b/netwerk/cache/nsDiskCacheDevice.h
new file mode 100644
index 0000000000..f986154e68
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDevice.h
@@ -0,0 +1,116 @@
+/* -*- 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 _nsDiskCacheDevice_h_
+#define _nsDiskCacheDevice_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nsCacheDevice.h"
+#include "nsDiskCacheBinding.h"
+#include "nsDiskCacheBlockFile.h"
+#include "nsDiskCacheEntry.h"
+
+#include "nsIFile.h"
+#include "nsIObserver.h"
+#include "nsCOMArray.h"
+
+class nsDiskCacheMap;
+
+
+class nsDiskCacheDevice final : public nsCacheDevice {
+public:
+ nsDiskCacheDevice();
+ virtual ~nsDiskCacheDevice();
+
+ virtual nsresult Init();
+ virtual nsresult Shutdown();
+
+ virtual const char * GetDeviceID(void);
+ virtual nsCacheEntry * FindEntry(nsCString * key, bool *collision);
+ virtual nsresult DeactivateEntry(nsCacheEntry * entry);
+ virtual nsresult BindEntry(nsCacheEntry * entry);
+ virtual void DoomEntry( nsCacheEntry * entry );
+
+ virtual nsresult OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result);
+
+ virtual nsresult OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result);
+
+ virtual nsresult GetFileForEntry(nsCacheEntry * entry,
+ nsIFile ** result);
+
+ virtual nsresult OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize);
+
+ virtual nsresult Visit(nsICacheVisitor * visitor);
+
+ virtual nsresult EvictEntries(const char * clientID);
+
+ bool EntryIsTooBig(int64_t entrySize);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ /**
+ * Preference accessors
+ */
+ void SetCacheParentDirectory(nsIFile * parentDir);
+ void SetCapacity(uint32_t capacity);
+ void SetMaxEntrySize(int32_t maxSizeInKilobytes);
+
+/* private: */
+
+ void getCacheDirectory(nsIFile ** result);
+ uint32_t getCacheCapacity();
+ uint32_t getCacheSize();
+ uint32_t getEntryCount();
+
+ nsDiskCacheMap * CacheMap() { return &mCacheMap; }
+
+private:
+ friend class nsDiskCacheDeviceDeactivateEntryEvent;
+ friend class nsEvictDiskCacheEntriesEvent;
+ friend class nsDiskCacheMap;
+ /**
+ * Private methods
+ */
+
+ inline bool IsValidBinding(nsDiskCacheBinding *binding)
+ {
+ NS_ASSERTION(binding, " binding == nullptr");
+ NS_ASSERTION(binding->mDeactivateEvent == nullptr,
+ " entry in process of deactivation");
+ return (binding && !binding->mDeactivateEvent);
+ }
+
+ bool Initialized() { return mInitialized; }
+
+ nsresult Shutdown_Private(bool flush);
+ nsresult DeactivateEntry_Private(nsCacheEntry * entry,
+ nsDiskCacheBinding * binding);
+
+ nsresult OpenDiskCache();
+ nsresult ClearDiskCache();
+
+ nsresult EvictDiskCacheEntries(uint32_t targetCapacity);
+
+ /**
+ * Member variables
+ */
+ nsCOMPtr<nsIFile> mCacheDirectory;
+ nsDiskCacheBindery mBindery;
+ uint32_t mCacheCapacity; // Unit is KiB's
+ int32_t mMaxEntrySize; // Unit is bytes internally
+ // XXX need soft/hard limits, currentTotal
+ nsDiskCacheMap mCacheMap;
+ bool mInitialized;
+ bool mClearingDiskCache;
+};
+
+#endif // _nsDiskCacheDevice_h_
diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
new file mode 100644
index 0000000000..56ece58870
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -0,0 +1,2906 @@
+/* -*- 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 "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "nsAutoPtr.h"
+#include "nsEscape.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.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 "mozIStorageService.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageFunction.h"
+#include "mozStorageHelper.h"
+
+#include "nsICacheVisitor.h"
+#include "nsISeekableStream.h"
+
+#include "mozilla/Telemetry.h"
+
+#include "sqlite3.h"
+#include "mozilla/storage.h"
+#include "nsVariant.h"
+#include "mozilla/BasePrincipal.h"
+
+using namespace mozilla;
+using namespace mozilla::storage;
+using mozilla::NeckoOriginAttributes;
+
+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(
+ NS_LITERAL_CSTRING("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(
+ NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
+ 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(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(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;
+
+} // 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)) {
+ NS_ADDREF(*_retval = new IntegerVariant(SQLITE_IGNORE));
+ 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=%x]!\n",
+ key, generation, 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;
+ items.SwapElements(*pitems);
+
+ for (int32_t i = 0; i < items.Count(); i++) {
+ if (MOZ_LOG_TEST(gCacheLog, LogLevel::Debug)) {
+ nsAutoCString path;
+ items[i]->GetNativePath(path);
+ LOG((" removing %s\n", path.get()));
+ }
+
+ items[i]->Remove(false);
+ }
+}
+
+class nsOfflineCacheDiscardCache : public Runnable
+{
+public:
+ nsOfflineCacheDiscardCache(nsOfflineCacheDevice *device,
+ nsCString &group,
+ nsCString &clientID)
+ : 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() {}
+
+ nsOfflineCacheDevice* mDevice;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetDescription(char **aDescription)
+{
+ *aDescription = NS_strdup("Offline cache device");
+ return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport)
+{
+ 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");
+
+ *usageReport = ToNewCString(buffer);
+ if (!*usageReport)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ 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() {}
+
+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(NS_LITERAL_CSTRING("placeholder"));
+
+ 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() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYINFO
+
+ nsOfflineCacheRecord *mRec;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetClientID(char **result)
+{
+ *result = NS_strdup(mRec->clientID);
+ return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID)
+{
+ *deviceID = NS_strdup(OFFLINE_CACHE_DEVICE_ID);
+ return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+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 = uri->CloneIgnoringRef(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)
+{
+ if (mDevice->BaseDirectory())
+ NS_ADDREF(*out = mDevice->BaseDirectory());
+ else
+ *out = nullptr;
+
+ 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,
+ uint32_t * count,
+ char *** keys)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GatherEntries(mClientID, typeBits, count, 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)
+ {
+ mDB = aDB;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mDB->Close();
+ return NS_OK;
+ }
+
+protected:
+ virtual ~nsCloseDBEvent() {}
+
+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)
+{
+}
+
+nsOfflineCacheDevice::~nsOfflineCacheDevice()
+{}
+
+/* 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(NS_LITERAL_CSTRING("index.sqlite"));
+ 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);
+
+ mInitThread = do_GetCurrentThread();
+
+ mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
+
+ // 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(
+ NS_LITERAL_CSTRING("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(
+ NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
+
+ // 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(
+ NS_LITERAL_CSTRING("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(
+ NS_LITERAL_CSTRING("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(
+ NS_LITERAL_CSTRING("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(
+ NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
+ 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(
+ NS_LITERAL_CSTRING("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(
+ NS_LITERAL_CSTRING("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(
+ NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("cache_eviction_observer"), 3, mEvictionFunction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create all (most) of our statements up front
+ StatementSql prepared[] = {
+ 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, "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, "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;")
+ };
+ 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 nsCSubstring &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 = aManifestURL->CloneIgnoringRef(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(NS_LITERAL_CSTRING(
+ "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(NS_LITERAL_CSTRING(
+ "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 (mInitThread)
+ mInitThread->IsOnCurrentThread(&isOnCurrentThread);
+
+ if (!isOnCurrentThread) {
+ nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
+
+ if (ev) {
+ mInitThread->Dispatch(ev, NS_DISPATCH_NORMAL);
+ }
+ }
+ else {
+ mDB->Close();
+ }
+
+ mDB = nullptr;
+ mInitThread = 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 %lld %lld %lld]\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, 16 * 1024);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bufferedOut.swap(*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(
+ NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
+ 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(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=?;"),
+ 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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("DELETE FROM moz_cache;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
+ 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(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
+ 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,
+ uint32_t *count,
+ char ***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, count, 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(uint32_t *count,
+ char ***keys)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetGroups"));
+
+ return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
+}
+
+nsresult
+nsOfflineCacheDevice::GetGroupsTimeOrdered(uint32_t *count,
+ char ***keys)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
+
+ return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, 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,
+ uint32_t * count,
+ char *** 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);
+ }
+
+ *count = valArray.Length();
+ char **ret = static_cast<char **>(moz_xmalloc(*count * sizeof(char*)));
+ if (!ret) return NS_ERROR_OUT_OF_MEMORY;
+
+ for (uint32_t i = 0; i < *count; i++) {
+ ret[i] = NS_strdup(valArray[i].get());
+ if (!ret[i]) {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ *values = ret;
+
+ 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("|%016lld|%d",
+ now / PR_USEC_PER_SEC,
+ gNextTemporaryClientID++));
+
+ nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this,
+ group,
+ clientID);
+ if (!cache)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIWeakReference> 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->mAppId == NECKO_NO_APP_ID && 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() {}
+ 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_LITERAL_CSTRING("#"));
+ if (hash == kNotFound) {
+ // Just ignore...
+ return NS_OK;
+ }
+
+ ++hash;
+
+ nsDependentCSubstring suffix(groupId.BeginReading() + hash, groupId.Length() - hash);
+
+ mozilla::NeckoOriginAttributes 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;
+}
+
+} // anon
+
+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(NS_LITERAL_CSTRING("ORIGIN_MATCH"), 1, function1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ class AutoRemoveFunc {
+ public:
+ mozIStorageConnection* mDB;
+ explicit AutoRemoveFunc(mozIStorageConnection* aDB) : mDB(aDB) {}
+ ~AutoRemoveFunc() {
+ mDB->RemoveFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"));
+ }
+ };
+ AutoRemoveFunc autoRemove(mDB);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ rv = mDB->CreateStatement(
+ NS_LITERAL_CSTRING("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 nsCSubstring &group,
+ const nsCSubstring &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 nsCSubstring &group,
+ const nsCSubstring &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(NS_LITERAL_CSTRING("OfflineCache"));
+ if (NS_FAILED(rv))
+ return;
+
+ mCacheDirectory = do_QueryInterface(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..fcde58d3d7
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -0,0 +1,290 @@
+/* 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 "nsIApplicationCacheService.h"
+#include "nsIObserver.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageFunction.h"
+#include "nsIFile.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsWeakReference.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() {}
+
+ 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() {}
+
+ 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 nsCSubstring &group,
+ const nsCSubstring &clientID);
+ bool IsActiveCache(const nsCSubstring &group,
+ const nsCSubstring &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(uint32_t *count,char ***keys);
+
+ nsresult GetGroupsTimeOrdered(uint32_t *count,
+ char ***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();
+
+ 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,
+ uint32_t *count,
+ char *** values);
+ nsresult AddNamespace(const nsCString &clientID,
+ nsIApplicationCacheNamespace *ns);
+
+ nsresult GetUsage(const nsACString &clientID,
+ uint32_t *usage);
+
+ nsresult RunSimpleQuery(mozIStorageStatement *statment,
+ uint32_t resultIndex,
+ uint32_t * count,
+ char *** 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<nsIThread> mInitThread;
+};
+
+#endif // nsOfflineCacheDevice_h__
diff --git a/netwerk/cache/nsDiskCacheEntry.cpp b/netwerk/cache/nsDiskCacheEntry.cpp
new file mode 100644
index 0000000000..cd5a349777
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheEntry.cpp
@@ -0,0 +1,133 @@
+/* -*- 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 "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheEntry.h"
+#include "nsDiskCacheBinding.h"
+#include "nsCRT.h"
+
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+
+/******************************************************************************
+ * nsDiskCacheEntry
+ *****************************************************************************/
+
+/**
+ * CreateCacheEntry()
+ *
+ * Creates an nsCacheEntry and sets all fields except for the binding.
+ */
+nsCacheEntry *
+nsDiskCacheEntry::CreateCacheEntry(nsCacheDevice * device)
+{
+ nsCacheEntry * entry = nullptr;
+ nsresult rv = nsCacheEntry::Create(Key(),
+ nsICache::STREAM_BASED,
+ nsICache::STORE_ON_DISK,
+ device,
+ &entry);
+ if (NS_FAILED(rv) || !entry) return nullptr;
+
+ entry->SetFetchCount(mFetchCount);
+ entry->SetLastFetched(mLastFetched);
+ entry->SetLastModified(mLastModified);
+ entry->SetExpirationTime(mExpirationTime);
+ entry->SetCacheDevice(device);
+ // XXX why does nsCacheService have to fill out device in BindEntry()?
+ entry->SetDataSize(mDataSize);
+
+ rv = entry->UnflattenMetaData(MetaData(), mMetaDataSize);
+ if (NS_FAILED(rv)) {
+ delete entry;
+ return nullptr;
+ }
+
+ // 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);
+ }
+
+ return entry;
+}
+
+
+/******************************************************************************
+ * nsDiskCacheEntryInfo
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsDiskCacheEntryInfo, nsICacheEntryInfo)
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetClientID(char ** clientID)
+{
+ NS_ENSURE_ARG_POINTER(clientID);
+ return ClientIDFromCacheKey(nsDependentCString(mDiskEntry->Key()), clientID);
+}
+
+extern const char DISK_CACHE_DEVICE_ID[];
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetDeviceID(char ** deviceID)
+{
+ NS_ENSURE_ARG_POINTER(deviceID);
+ *deviceID = NS_strdup(mDeviceID);
+ return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetKey(nsACString &clientKey)
+{
+ return ClientKeyFromCacheKey(nsDependentCString(mDiskEntry->Key()), clientKey);
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetFetchCount(int32_t *aFetchCount)
+{
+ NS_ENSURE_ARG_POINTER(aFetchCount);
+ *aFetchCount = mDiskEntry->mFetchCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched)
+{
+ NS_ENSURE_ARG_POINTER(aLastFetched);
+ *aLastFetched = mDiskEntry->mLastFetched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetLastModified(uint32_t *aLastModified)
+{
+ NS_ENSURE_ARG_POINTER(aLastModified);
+ *aLastModified = mDiskEntry->mLastModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime)
+{
+ NS_ENSURE_ARG_POINTER(aExpirationTime);
+ *aExpirationTime = mDiskEntry->mExpirationTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::IsStreamBased(bool *aStreamBased)
+{
+ NS_ENSURE_ARG_POINTER(aStreamBased);
+ *aStreamBased = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetDataSize(uint32_t *aDataSize)
+{
+ NS_ENSURE_ARG_POINTER(aDataSize);
+ *aDataSize = mDiskEntry->mDataSize;
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsDiskCacheEntry.h b/netwerk/cache/nsDiskCacheEntry.h
new file mode 100644
index 0000000000..3bec330536
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheEntry.h
@@ -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/. */
+
+#ifndef _nsDiskCacheEntry_h_
+#define _nsDiskCacheEntry_h_
+
+#include "nsDiskCacheMap.h"
+
+#include "nsCacheEntry.h"
+
+
+/******************************************************************************
+ * nsDiskCacheEntry
+ *****************************************************************************/
+struct nsDiskCacheEntry {
+ uint32_t mHeaderVersion; // useful for stand-alone metadata files
+ uint32_t mMetaLocation; // for verification
+ int32_t mFetchCount;
+ uint32_t mLastFetched;
+ uint32_t mLastModified;
+ uint32_t mExpirationTime;
+ uint32_t mDataSize;
+ uint32_t mKeySize; // includes terminating null byte
+ uint32_t mMetaDataSize; // includes terminating null byte
+ // followed by key data (mKeySize bytes)
+ // followed by meta data (mMetaDataSize bytes)
+
+ uint32_t Size() { return sizeof(nsDiskCacheEntry) +
+ mKeySize + mMetaDataSize;
+ }
+
+ char* Key() { return reinterpret_cast<char*const>(this) +
+ sizeof(nsDiskCacheEntry);
+ }
+
+ char* MetaData()
+ { return Key() + mKeySize; }
+
+ nsCacheEntry * CreateCacheEntry(nsCacheDevice * device);
+
+ void Swap() // host to network (memory to disk)
+ {
+#if defined(IS_LITTLE_ENDIAN)
+ mHeaderVersion = htonl(mHeaderVersion);
+ mMetaLocation = htonl(mMetaLocation);
+ mFetchCount = htonl(mFetchCount);
+ mLastFetched = htonl(mLastFetched);
+ mLastModified = htonl(mLastModified);
+ mExpirationTime = htonl(mExpirationTime);
+ mDataSize = htonl(mDataSize);
+ mKeySize = htonl(mKeySize);
+ mMetaDataSize = htonl(mMetaDataSize);
+#endif
+ }
+
+ void Unswap() // network to host (disk to memory)
+ {
+#if defined(IS_LITTLE_ENDIAN)
+ mHeaderVersion = ntohl(mHeaderVersion);
+ mMetaLocation = ntohl(mMetaLocation);
+ mFetchCount = ntohl(mFetchCount);
+ mLastFetched = ntohl(mLastFetched);
+ mLastModified = ntohl(mLastModified);
+ mExpirationTime = ntohl(mExpirationTime);
+ mDataSize = ntohl(mDataSize);
+ mKeySize = ntohl(mKeySize);
+ mMetaDataSize = ntohl(mMetaDataSize);
+#endif
+ }
+};
+
+
+/******************************************************************************
+ * nsDiskCacheEntryInfo
+ *****************************************************************************/
+class nsDiskCacheEntryInfo : public nsICacheEntryInfo {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYINFO
+
+ nsDiskCacheEntryInfo(const char * deviceID, nsDiskCacheEntry * diskEntry)
+ : mDeviceID(deviceID)
+ , mDiskEntry(diskEntry)
+ {
+ }
+
+ const char* Key() { return mDiskEntry->Key(); }
+
+private:
+ virtual ~nsDiskCacheEntryInfo() {}
+
+ const char * mDeviceID;
+ nsDiskCacheEntry * mDiskEntry;
+};
+
+
+#endif /* _nsDiskCacheEntry_h_ */
diff --git a/netwerk/cache/nsDiskCacheMap.cpp b/netwerk/cache/nsDiskCacheMap.cpp
new file mode 100644
index 0000000000..d7ce13a35a
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheMap.cpp
@@ -0,0 +1,1430 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin 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 "nsCache.h"
+#include "nsDiskCacheMap.h"
+#include "nsDiskCacheBinding.h"
+#include "nsDiskCacheEntry.h"
+#include "nsDiskCacheDevice.h"
+#include "nsCacheService.h"
+
+#include <string.h>
+#include "nsPrintfCString.h"
+
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+/******************************************************************************
+ * nsDiskCacheMap
+ *****************************************************************************/
+
+/**
+ * File operations
+ */
+
+nsresult
+nsDiskCacheMap::Open(nsIFile * cacheDirectory,
+ nsDiskCache::CorruptCacheInfo * corruptInfo)
+{
+ NS_ENSURE_ARG_POINTER(corruptInfo);
+
+ // Assume we have an unexpected error until we find otherwise.
+ *corruptInfo = nsDiskCache::kUnexpectedError;
+ NS_ENSURE_ARG_POINTER(cacheDirectory);
+ if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
+
+ mCacheDirectory = cacheDirectory; // save a reference for ourselves
+
+ // create nsIFile for _CACHE_MAP_
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ rv = cacheDirectory->Clone(getter_AddRefs(file));
+ rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // open the file - restricted to user, the data could be confidential
+ rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
+ if (NS_FAILED(rv)) {
+ *corruptInfo = nsDiskCache::kOpenCacheMapError;
+ NS_WARNING("Could not open cache map file");
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ bool cacheFilesExist = CacheFilesExist();
+ rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
+ uint32_t mapSize = PR_Available(mMapFD);
+
+ if (NS_FAILED(InitCacheClean(cacheDirectory,
+ corruptInfo))) {
+ // corruptInfo is set in the call to InitCacheClean
+ goto error_exit;
+ }
+
+ // check size of map file
+ if (mapSize == 0) { // creating a new _CACHE_MAP_
+
+ // block files shouldn't exist if we're creating the _CACHE_MAP_
+ if (cacheFilesExist) {
+ *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
+ goto error_exit;
+ }
+
+ if (NS_FAILED(CreateCacheSubDirectories())) {
+ *corruptInfo = nsDiskCache::kCreateCacheSubdirectories;
+ goto error_exit;
+ }
+
+ // create the file - initialize in memory
+ memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
+ mHeader.mVersion = nsDiskCache::kCurrentVersion;
+ mHeader.mRecordCount = kMinRecordCount;
+ mRecordArray = (nsDiskCacheRecord *)
+ PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord));
+ if (!mRecordArray) {
+ *corruptInfo = nsDiskCache::kOutOfMemory;
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto error_exit;
+ }
+ } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
+
+ // if _CACHE_MAP_ exists, so should the block files
+ if (!cacheFilesExist) {
+ *corruptInfo = nsDiskCache::kBlockFilesShouldExist;
+ goto error_exit;
+ }
+
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
+
+ // read the header
+ uint32_t bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
+ if (sizeof(nsDiskCacheHeader) != bytesRead) {
+ *corruptInfo = nsDiskCache::kHeaderSizeNotRead;
+ goto error_exit;
+ }
+ mHeader.Unswap();
+
+ if (mHeader.mIsDirty) {
+ *corruptInfo = nsDiskCache::kHeaderIsDirty;
+ goto error_exit;
+ }
+
+ if (mHeader.mVersion != nsDiskCache::kCurrentVersion) {
+ *corruptInfo = nsDiskCache::kVersionMismatch;
+ goto error_exit;
+ }
+
+ uint32_t recordArraySize =
+ mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
+ if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) {
+ *corruptInfo = nsDiskCache::kRecordsIncomplete;
+ goto error_exit;
+ }
+
+ // Get the space for the records
+ mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize);
+ if (!mRecordArray) {
+ *corruptInfo = nsDiskCache::kOutOfMemory;
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto error_exit;
+ }
+
+ // Read the records
+ bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
+ if (bytesRead < recordArraySize) {
+ *corruptInfo = nsDiskCache::kNotEnoughToRead;
+ goto error_exit;
+ }
+
+ // Unswap each record
+ int32_t total = 0;
+ for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
+ if (mRecordArray[i].HashNumber()) {
+#if defined(IS_LITTLE_ENDIAN)
+ mRecordArray[i].Unswap();
+#endif
+ total ++;
+ }
+ }
+
+ // verify entry count
+ if (total != mHeader.mEntryCount) {
+ *corruptInfo = nsDiskCache::kEntryCountIncorrect;
+ goto error_exit;
+ }
+
+ } else {
+ *corruptInfo = nsDiskCache::kHeaderIncomplete;
+ goto error_exit;
+ }
+
+ rv = OpenBlockFiles(corruptInfo);
+ if (NS_FAILED(rv)) {
+ // corruptInfo is set in the call to OpenBlockFiles
+ goto error_exit;
+ }
+
+ // set dirty bit and flush header
+ mHeader.mIsDirty = true;
+ rv = FlushHeader();
+ if (NS_FAILED(rv)) {
+ *corruptInfo = nsDiskCache::kFlushHeaderError;
+ goto error_exit;
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP_DISK_CACHE_OVERHEAD,
+ (uint32_t)SizeOfExcludingThis(moz_malloc_size_of));
+
+ *corruptInfo = nsDiskCache::kNotCorrupt;
+ return NS_OK;
+
+error_exit:
+ (void) Close(false);
+
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::Close(bool flush)
+{
+ nsCacheService::AssertOwnsLock();
+ nsresult rv = NS_OK;
+
+ // Cancel any pending cache validation event, the FlushRecords call below
+ // will validate the cache.
+ if (mCleanCacheTimer) {
+ mCleanCacheTimer->Cancel();
+ }
+
+ // If cache map file and its block files are still open, close them
+ if (mMapFD) {
+ // close block files
+ rv = CloseBlockFiles(flush);
+ if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
+ // write the map records
+ rv = FlushRecords(false); // don't bother swapping buckets back
+ if (NS_SUCCEEDED(rv)) {
+ // clear dirty bit
+ mHeader.mIsDirty = false;
+ rv = FlushHeader();
+ }
+ }
+ if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
+ rv = NS_ERROR_UNEXPECTED;
+
+ mMapFD = nullptr;
+ }
+
+ if (mCleanFD) {
+ PR_Close(mCleanFD);
+ mCleanFD = nullptr;
+ }
+
+ PR_FREEIF(mRecordArray);
+ PR_FREEIF(mBuffer);
+ mBufferSize = 0;
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::Trim()
+{
+ nsresult rv, rv2 = NS_OK;
+ for (int i=0; i < kNumBlockFiles; ++i) {
+ rv = mBlockFile[i].Trim();
+ if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
+ }
+ // Try to shrink the records array
+ rv = ShrinkRecords();
+ if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
+ return rv2;
+}
+
+
+nsresult
+nsDiskCacheMap::FlushHeader()
+{
+ if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
+
+ // seek to beginning of cache map
+ int32_t filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
+ if (filePos != 0) return NS_ERROR_UNEXPECTED;
+
+ // write the header
+ mHeader.Swap();
+ int32_t bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
+ mHeader.Unswap();
+ if (sizeof(nsDiskCacheHeader) != bytesWritten) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ PRStatus err = PR_Sync(mMapFD);
+ if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
+
+ // If we have a clean header then revalidate the cache clean file
+ if (!mHeader.mIsDirty) {
+ RevalidateCache();
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsDiskCacheMap::FlushRecords(bool unswap)
+{
+ if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
+
+ // seek to beginning of buckets
+ int32_t filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
+ if (filePos != sizeof(nsDiskCacheHeader))
+ return NS_ERROR_UNEXPECTED;
+
+#if defined(IS_LITTLE_ENDIAN)
+ // Swap each record
+ for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
+ if (mRecordArray[i].HashNumber())
+ mRecordArray[i].Swap();
+ }
+#endif
+
+ int32_t recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
+
+ int32_t bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
+ if (bytesWritten != recordArraySize)
+ return NS_ERROR_UNEXPECTED;
+
+#if defined(IS_LITTLE_ENDIAN)
+ if (unswap) {
+ // Unswap each record
+ for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
+ if (mRecordArray[i].HashNumber())
+ mRecordArray[i].Unswap();
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
+
+/**
+ * Record operations
+ */
+
+uint32_t
+nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex, uint32_t targetRank)
+{
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+ uint32_t rank = 0;
+
+ for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
+ if ((rank < records[i].EvictionRank()) &&
+ ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
+ rank = records[i].EvictionRank();
+ }
+ return rank;
+}
+
+nsresult
+nsDiskCacheMap::GrowRecords()
+{
+ if (mHeader.mRecordCount >= mMaxRecordCount)
+ return NS_OK;
+ CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
+
+ // Resize the record array
+ int32_t newCount = mHeader.mRecordCount << 1;
+ if (newCount > mMaxRecordCount)
+ newCount = mMaxRecordCount;
+ nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)
+ PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
+ if (!newArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Space out the buckets
+ uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
+ uint32_t newRecordsPerBucket = newCount / kBuckets;
+ // Work from back to space out each bucket to the new array
+ for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
+ // Move bucket
+ nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
+ const uint32_t count = mHeader.mBucketUsage[bucketIndex];
+ memmove(newRecords,
+ newArray + bucketIndex * oldRecordsPerBucket,
+ count * sizeof(nsDiskCacheRecord));
+ // clear unused records
+ memset(newRecords + count, 0,
+ (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
+ }
+
+ // Set as the new record array
+ mRecordArray = newArray;
+ mHeader.mRecordCount = newCount;
+
+ InvalidateCache();
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::ShrinkRecords()
+{
+ if (mHeader.mRecordCount <= kMinRecordCount)
+ return NS_OK;
+ CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
+
+ // Verify if we can shrink the record array: all buckets must be less than
+ // 1/2 filled
+ uint32_t maxUsage = 0, bucketIndex;
+ for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
+ if (maxUsage < mHeader.mBucketUsage[bucketIndex])
+ maxUsage = mHeader.mBucketUsage[bucketIndex];
+ }
+ // Determine new bucket size, halve size until maxUsage
+ uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
+ uint32_t newRecordsPerBucket = oldRecordsPerBucket;
+ while (maxUsage < (newRecordsPerBucket >> 1))
+ newRecordsPerBucket >>= 1;
+ if (newRecordsPerBucket < (kMinRecordCount / kBuckets))
+ newRecordsPerBucket = (kMinRecordCount / kBuckets);
+ NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket,
+ "ShrinkRecords() can't grow records!");
+ if (newRecordsPerBucket == oldRecordsPerBucket)
+ return NS_OK;
+ // Move the buckets close to each other
+ for (bucketIndex = 1; bucketIndex < kBuckets; ++bucketIndex) {
+ // Move bucket
+ memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
+ mRecordArray + bucketIndex * oldRecordsPerBucket,
+ newRecordsPerBucket * sizeof(nsDiskCacheRecord));
+ }
+
+ // Shrink the record array memory block itself
+ uint32_t newCount = newRecordsPerBucket * kBuckets;
+ nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
+ PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
+ if (!newArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Set as the new record array
+ mRecordArray = newArray;
+ mHeader.mRecordCount = newCount;
+
+ InvalidateCache();
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
+ nsDiskCacheRecord * oldRecord)
+{
+ CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
+
+ const uint32_t hashNumber = mapRecord->HashNumber();
+ const uint32_t bucketIndex = GetBucketIndex(hashNumber);
+ const uint32_t count = mHeader.mBucketUsage[bucketIndex];
+
+ oldRecord->SetHashNumber(0); // signify no record
+
+ if (count == GetRecordsPerBucket()) {
+ // Ignore failure to grow the record space, we will then reuse old records
+ GrowRecords();
+ }
+
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+ if (count < GetRecordsPerBucket()) {
+ // stick the new record at the end
+ records[count] = *mapRecord;
+ mHeader.mEntryCount++;
+ mHeader.mBucketUsage[bucketIndex]++;
+ if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
+ mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
+ InvalidateCache();
+ } else {
+ // Find the record with the highest eviction rank
+ nsDiskCacheRecord * mostEvictable = &records[0];
+ for (int i = count-1; i > 0; i--) {
+ if (records[i].EvictionRank() > mostEvictable->EvictionRank())
+ mostEvictable = &records[i];
+ }
+ *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
+ // evict the mostEvictable
+ *mostEvictable = *mapRecord; // replace it with the new record
+ // check if we need to update mostEvictable entry in header
+ if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
+ mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
+ if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
+ mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+ InvalidateCache();
+ }
+
+ NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
+ "eviction rank out of sync");
+ return NS_OK;
+}
+
+
+nsresult
+nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
+{
+ CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
+
+ const uint32_t hashNumber = mapRecord->HashNumber();
+ const uint32_t bucketIndex = GetBucketIndex(hashNumber);
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+
+ for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
+ if (records[i].HashNumber() == hashNumber) {
+ const uint32_t oldRank = records[i].EvictionRank();
+
+ // stick the new record here
+ records[i] = *mapRecord;
+
+ // update eviction rank in header if necessary
+ if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
+ mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
+ else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
+ mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+
+ InvalidateCache();
+
+NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
+ "eviction rank out of sync");
+ return NS_OK;
+ }
+ }
+ NS_NOTREACHED("record not found");
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+nsresult
+nsDiskCacheMap::FindRecord( uint32_t hashNumber, nsDiskCacheRecord * result)
+{
+ const uint32_t bucketIndex = GetBucketIndex(hashNumber);
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+
+ for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
+ if (records[i].HashNumber() == hashNumber) {
+ *result = records[i]; // copy the record
+ NS_ASSERTION(result->ValidRecord(), "bad cache map record");
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+}
+
+
+nsresult
+nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
+{
+ CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
+
+ const uint32_t hashNumber = mapRecord->HashNumber();
+ const uint32_t bucketIndex = GetBucketIndex(hashNumber);
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+ uint32_t last = mHeader.mBucketUsage[bucketIndex]-1;
+
+ for (int i = last; i >= 0; i--) {
+ if (records[i].HashNumber() == hashNumber) {
+ // found it, now delete it.
+ uint32_t evictionRank = records[i].EvictionRank();
+ NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
+ "evictionRank out of sync");
+ // if not the last record, shift last record into opening
+ records[i] = records[last];
+ records[last].SetHashNumber(0); // clear last record
+ mHeader.mBucketUsage[bucketIndex] = last;
+ mHeader.mEntryCount--;
+
+ // update eviction rank
+ uint32_t bucketIndex = GetBucketIndex(mapRecord->HashNumber());
+ if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
+ mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+ }
+
+ InvalidateCache();
+
+ NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
+ GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+int32_t
+nsDiskCacheMap::VisitEachRecord(uint32_t bucketIndex,
+ nsDiskCacheRecordVisitor * visitor,
+ uint32_t evictionRank)
+{
+ int32_t rv = kVisitNextRecord;
+ uint32_t count = mHeader.mBucketUsage[bucketIndex];
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+
+ // call visitor for each entry (matching any eviction rank)
+ for (int i = count-1; i >= 0; i--) {
+ if (evictionRank > records[i].EvictionRank()) continue;
+
+ rv = visitor->VisitRecord(&records[i]);
+ if (rv == kStopVisitingRecords)
+ break; // Stop visiting records
+
+ if (rv == kDeleteRecordAndContinue) {
+ --count;
+ records[i] = records[count];
+ records[count].SetHashNumber(0);
+ InvalidateCache();
+ }
+ }
+
+ if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
+ mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
+ mHeader.mBucketUsage[bucketIndex] = count;
+ // recalc eviction rank
+ mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+ }
+ NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
+ GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
+
+ return rv;
+}
+
+
+/**
+ * VisitRecords
+ *
+ * Visit every record in cache map in the most convenient order
+ */
+nsresult
+nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
+{
+ for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
+ if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
+ break;
+ }
+ return NS_OK;
+}
+
+
+/**
+ * EvictRecords
+ *
+ * Just like VisitRecords, but visits the records in order of their eviction rank
+ */
+nsresult
+nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
+{
+ uint32_t tempRank[kBuckets];
+ int bucketIndex = 0;
+
+ // copy eviction rank array
+ for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
+ tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
+
+ // Maximum number of iterations determined by number of records
+ // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
+ // the value could decrease if some entry is evicted.
+ int32_t entryCount = mHeader.mEntryCount;
+ for (int n = 0; n < entryCount; ++n) {
+
+ // find bucket with highest eviction rank
+ uint32_t rank = 0;
+ for (int i = 0; i < kBuckets; ++i) {
+ if (rank < tempRank[i]) {
+ rank = tempRank[i];
+ bucketIndex = i;
+ }
+ }
+
+ if (rank == 0) break; // we've examined all the records
+
+ // visit records in bucket with eviction ranks >= target eviction rank
+ if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
+ break;
+
+ // find greatest rank less than 'rank'
+ tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
+ }
+ return NS_OK;
+}
+
+
+
+nsresult
+nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo)
+{
+ NS_ENSURE_ARG_POINTER(corruptInfo);
+
+ // create nsIFile for block file
+ nsCOMPtr<nsIFile> blockFile;
+ nsresult rv = NS_OK;
+ *corruptInfo = nsDiskCache::kUnexpectedError;
+
+ for (int i = 0; i < kNumBlockFiles; ++i) {
+ rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
+ if (NS_FAILED(rv)) {
+ *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex;
+ break;
+ }
+
+ uint32_t blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
+ uint32_t bitMapSize = GetBitMapSizeForIndex(i+1);
+ rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo);
+ if (NS_FAILED(rv)) {
+ // corruptInfo was set inside the call to mBlockFile[i].Open
+ break;
+ }
+ }
+ // close all files in case of any error
+ if (NS_FAILED(rv))
+ (void)CloseBlockFiles(false); // we already have an error to report
+
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::CloseBlockFiles(bool flush)
+{
+ nsresult rv, rv2 = NS_OK;
+ for (int i=0; i < kNumBlockFiles; ++i) {
+ rv = mBlockFile[i].Close(flush);
+ if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
+ }
+ return rv2;
+}
+
+
+bool
+nsDiskCacheMap::CacheFilesExist()
+{
+ nsCOMPtr<nsIFile> blockFile;
+ nsresult rv;
+
+ for (int i = 0; i < kNumBlockFiles; ++i) {
+ bool exists;
+ rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
+ if (NS_FAILED(rv)) return false;
+
+ rv = blockFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) return false;
+ }
+
+ return true;
+}
+
+
+nsresult
+nsDiskCacheMap::CreateCacheSubDirectories()
+{
+ if (!mCacheDirectory)
+ return NS_ERROR_UNEXPECTED;
+
+ for (int32_t index = 0 ; index < 16 ; index++) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = file->AppendNative(nsPrintfCString("%X", index));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+
+nsDiskCacheEntry *
+nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
+{
+ CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ nsDiskCacheEntry * diskEntry = nullptr;
+ uint32_t metaFile = record->MetaFile();
+ int32_t bytesRead = 0;
+
+ if (!record->MetaLocationInitialized()) return nullptr;
+
+ if (metaFile == 0) { // entry/metadata stored in separate file
+ // open and read the file
+ nsCOMPtr<nsIFile> file;
+ rv = GetLocalFileForDiskCacheRecord(record,
+ nsDiskCache::kMetaData,
+ false,
+ getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
+ "[this=%p] reading disk cache entry", this));
+
+ PRFileDesc * fd = nullptr;
+
+ // open the file - restricted to user, the data could be confidential
+ rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ int32_t fileSize = PR_Available(fd);
+ if (fileSize < 0) {
+ // an error occurred. We could call PR_GetError(), but how would that help?
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ rv = EnsureBuffer(fileSize);
+ if (NS_SUCCEEDED(rv)) {
+ bytesRead = PR_Read(fd, mBuffer, fileSize);
+ if (bytesRead < fileSize) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+ PR_Close(fd);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ } else if (metaFile < (kNumBlockFiles + 1)) {
+ // entry/metadata stored in cache block file
+
+ // allocate buffer
+ uint32_t blockCount = record->MetaBlockCount();
+ bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
+
+ rv = EnsureBuffer(bytesRead);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ // read diskEntry, note when the blocks are at the end of file,
+ // bytesRead may be less than blockSize*blockCount.
+ // But the bytesRead should at least agree with the real disk entry size.
+ rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
+ record->MetaStartBlock(),
+ blockCount,
+ &bytesRead);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+ diskEntry = (nsDiskCacheEntry *)mBuffer;
+ diskEntry->Unswap(); // disk to memory
+ // Check if calculated size agrees with bytesRead
+ if (bytesRead < 0 || (uint32_t)bytesRead < diskEntry->Size())
+ return nullptr;
+
+ // Return the buffer containing the diskEntry structure
+ return diskEntry;
+}
+
+
+/**
+ * CreateDiskCacheEntry(nsCacheEntry * entry)
+ *
+ * Prepare an nsCacheEntry for writing to disk
+ */
+nsDiskCacheEntry *
+nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding,
+ uint32_t * aSize)
+{
+ nsCacheEntry * entry = binding->mCacheEntry;
+ if (!entry) return nullptr;
+
+ // Store security info, if it is serializable
+ nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
+ if (infoObj && !serializable) return nullptr;
+ if (serializable) {
+ nsCString info;
+ nsresult rv = NS_SerializeToString(serializable, info);
+ if (NS_FAILED(rv)) return nullptr;
+ rv = entry->SetMetaDataElement("security-info", info.get());
+ if (NS_FAILED(rv)) return nullptr;
+ }
+
+ uint32_t keySize = entry->Key()->Length() + 1;
+ uint32_t metaSize = entry->MetaDataSize();
+ uint32_t size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
+
+ if (aSize) *aSize = size;
+
+ nsresult rv = EnsureBuffer(size);
+ if (NS_FAILED(rv)) return nullptr;
+
+ nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
+ diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
+ diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
+ diskEntry->mFetchCount = entry->FetchCount();
+ diskEntry->mLastFetched = entry->LastFetched();
+ diskEntry->mLastModified = entry->LastModified();
+ diskEntry->mExpirationTime = entry->ExpirationTime();
+ diskEntry->mDataSize = entry->DataSize();
+ diskEntry->mKeySize = keySize;
+ diskEntry->mMetaDataSize = metaSize;
+
+ memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
+
+ rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
+ if (NS_FAILED(rv)) return nullptr;
+
+ return diskEntry;
+}
+
+
+nsresult
+nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
+{
+ CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
+ binding->mRecord.HashNumber()));
+
+ nsresult rv = NS_OK;
+ uint32_t size;
+ nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size);
+ if (!diskEntry) return NS_ERROR_UNEXPECTED;
+
+ uint32_t fileIndex = CalculateFileIndex(size);
+
+ // Deallocate old storage if necessary
+ if (binding->mRecord.MetaLocationInitialized()) {
+ // we have existing storage
+
+ if ((binding->mRecord.MetaFile() == 0) &&
+ (fileIndex == 0)) { // keeping the separate file
+ // just decrement total
+ DecrementTotalSize(binding->mRecord.MetaFileSize());
+ NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
+ "generations out of sync");
+ } else {
+ rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
+ // write entry data to disk cache block file
+ diskEntry->Swap();
+
+ if (fileIndex != 0) {
+ while (1) {
+ uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
+ uint32_t blocks = ((size - 1) / blockSize) + 1;
+
+ int32_t startBlock;
+ rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
+ &startBlock);
+ if (NS_SUCCEEDED(rv)) {
+ // update binding and cache map record
+ binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
+
+ rv = UpdateRecord(&binding->mRecord);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX we should probably write out bucket ourselves
+
+ IncrementTotalSize(blocks, blockSize);
+ break;
+ }
+
+ if (fileIndex == kNumBlockFiles) {
+ fileIndex = 0; // write data to separate file
+ break;
+ }
+
+ // try next block file
+ fileIndex++;
+ }
+ }
+
+ if (fileIndex == 0) {
+ // Write entry data to separate file
+ uint32_t metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
+ if (metaFileSizeK > kMaxDataSizeK)
+ metaFileSizeK = kMaxDataSizeK;
+
+ binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
+ binding->mRecord.SetMetaFileSize(metaFileSizeK);
+ rv = UpdateRecord(&binding->mRecord);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
+ nsDiskCache::kMetaData,
+ true,
+ getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // open the file
+ PRFileDesc * fd;
+ // open the file - restricted to user, the data could be confidential
+ rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the file
+ int32_t bytesWritten = PR_Write(fd, diskEntry, size);
+
+ PRStatus err = PR_Close(fd);
+ if ((bytesWritten != (int32_t)size) || (err != PR_SUCCESS)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ IncrementTotalSize(metaFileSizeK);
+ }
+
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
+{
+ CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
+ binding->mRecord.HashNumber(), size));
+
+ uint32_t fileIndex = binding->mRecord.DataFile();
+ int32_t readSize = size;
+
+ nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
+ binding->mRecord.DataStartBlock(),
+ binding->mRecord.DataBlockCount(),
+ &readSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (readSize < (int32_t)size) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
+{
+ CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
+ binding->mRecord.HashNumber(), size));
+
+ nsresult rv = NS_OK;
+
+ // determine block file & number of blocks
+ uint32_t fileIndex = CalculateFileIndex(size);
+ uint32_t blockCount = 0;
+ int32_t startBlock = 0;
+
+ if (size > 0) {
+ // if fileIndex is 0, bad things happen below, which makes gcc 4.7
+ // complain, but it's not supposed to happen. See bug 854105.
+ MOZ_ASSERT(fileIndex);
+ while (fileIndex) {
+ uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
+ blockCount = ((size - 1) / blockSize) + 1;
+
+ rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
+ &startBlock);
+ if (NS_SUCCEEDED(rv)) {
+ IncrementTotalSize(blockCount, blockSize);
+ break;
+ }
+
+ if (fileIndex == kNumBlockFiles)
+ return rv;
+
+ fileIndex++;
+ }
+ }
+
+ // update binding and cache map record
+ binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
+ if (!binding->mDoomed) {
+ rv = UpdateRecord(&binding->mRecord);
+ }
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
+{
+ nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
+ nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
+ return NS_FAILED(rv1) ? rv1 : rv2;
+}
+
+
+nsresult
+nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData)
+{
+ CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
+ metaData));
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ uint32_t fileIndex = metaData ? record->MetaFile() : record->DataFile();
+ nsCOMPtr<nsIFile> file;
+
+ if (fileIndex == 0) {
+ // delete the file
+ uint32_t sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
+ // XXX if sizeK == USHRT_MAX, stat file for actual size
+
+ rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ rv = file->Remove(false); // false == non-recursive
+ }
+ DecrementTotalSize(sizeK);
+
+ } else if (fileIndex < (kNumBlockFiles + 1)) {
+ // deallocate blocks
+ uint32_t startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
+ uint32_t blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
+
+ rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
+ DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
+ }
+ if (metaData) record->ClearMetaLocation();
+ else record->ClearDataLocation();
+
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
+ bool meta,
+ bool createPath,
+ nsIFile ** result)
+{
+ if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t hash = record->HashNumber();
+
+ // The file is stored under subdirectories according to the hash number:
+ // 0x01234567 -> 0/12/
+ rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
+ if (NS_FAILED(rv)) return rv;
+ rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ int16_t generation = record->Generation();
+ char name[32];
+ // Cut the beginning of the hash that was used in the path
+ ::SprintfLiteral(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
+ generation);
+ rv = file->AppendNative(nsDependentCString(name));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*result = file);
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
+ bool meta,
+ bool createPath,
+ nsIFile ** result)
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFileForDiskCacheRecord(record,
+ meta,
+ createPath,
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*result = file);
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::GetBlockFileForIndex(uint32_t index, nsIFile ** result)
+{
+ if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ char name[32];
+ ::SprintfLiteral(name, "_CACHE_%03d_", index + 1);
+ rv = file->AppendNative(nsDependentCString(name));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*result = file);
+
+ return rv;
+}
+
+
+uint32_t
+nsDiskCacheMap::CalculateFileIndex(uint32_t size)
+{
+ // We prefer to use block file with larger block if the wasted space would
+ // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
+ // instead of in 4 1K-blocks.
+
+ if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
+ if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
+ if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
+ return 0;
+}
+
+nsresult
+nsDiskCacheMap::EnsureBuffer(uint32_t bufSize)
+{
+ if (mBufferSize < bufSize) {
+ char * buf = (char *)PR_REALLOC(mBuffer, bufSize);
+ if (!buf) {
+ mBufferSize = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mBuffer = buf;
+ mBufferSize = bufSize;
+ }
+ return NS_OK;
+}
+
+void
+nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity)
+{
+ // Heuristic 1. average cache entry size is probably around 1KB
+ // Heuristic 2. we don't want more than 32MB reserved to store the record
+ // map in memory.
+ const int32_t RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
+ int32_t maxRecordCount = std::min(int32_t(capacity), RECORD_COUNT_LIMIT);
+ if (mMaxRecordCount < maxRecordCount) {
+ // We can only grow
+ mMaxRecordCount = maxRecordCount;
+ }
+}
+
+size_t
+nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
+{
+ size_t usage = aMallocSizeOf(mRecordArray);
+
+ usage += aMallocSizeOf(mBuffer);
+ usage += aMallocSizeOf(mMapFD);
+ usage += aMallocSizeOf(mCleanFD);
+ usage += aMallocSizeOf(mCacheDirectory);
+ usage += aMallocSizeOf(mCleanCacheTimer);
+
+ for (int i = 0; i < kNumBlockFiles; i++) {
+ usage += mBlockFile[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ return usage;
+}
+
+nsresult
+nsDiskCacheMap::InitCacheClean(nsIFile * cacheDirectory,
+ nsDiskCache::CorruptCacheInfo * corruptInfo)
+{
+ // The _CACHE_CLEAN_ file will be used in the future to determine
+ // if the cache is clean or not.
+ bool cacheCleanFileExists = false;
+ nsCOMPtr<nsIFile> cacheCleanFile;
+ nsresult rv = cacheDirectory->GetParent(getter_AddRefs(cacheCleanFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheCleanFile->AppendNative(
+ NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
+ if (NS_SUCCEEDED(rv)) {
+ // Check if the file already exists, if it does, we will later read the
+ // value and report it to telemetry.
+ cacheCleanFile->Exists(&cacheCleanFileExists);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not build cache clean file path");
+ *corruptInfo = nsDiskCache::kCacheCleanFilePathError;
+ return rv;
+ }
+
+ // Make sure the _CACHE_CLEAN_ file exists
+ rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
+ 00600, &mCleanFD);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not open cache clean file");
+ *corruptInfo = nsDiskCache::kCacheCleanOpenFileError;
+ return rv;
+ }
+
+ if (cacheCleanFileExists) {
+ char clean = '0';
+ int32_t bytesRead = PR_Read(mCleanFD, &clean, 1);
+ if (bytesRead != 1) {
+ NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
+ }
+ }
+
+ // Create a timer that will be used to validate the cache
+ // as long as an activity threshold was met
+ mCleanCacheTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mCleanCacheTimer->SetTarget(nsCacheService::GlobalInstance()->mCacheIOThread);
+ rv = ResetCacheTimer();
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not create cache clean timer");
+ mCleanCacheTimer = nullptr;
+ *corruptInfo = nsDiskCache::kCacheCleanTimerError;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::WriteCacheClean(bool clean)
+{
+ nsCacheService::AssertOwnsLock();
+ if (!mCleanFD) {
+ NS_WARNING("Cache clean file is not open!");
+ return NS_ERROR_FAILURE;
+ }
+
+ CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean? 1 : 0));
+ // I'm using a simple '1' or '0' to denote cache clean
+ // since it can be edited easily by any text editor for testing.
+ char data = clean? '1' : '0';
+ int32_t filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET);
+ if (filePos != 0) {
+ NS_WARNING("Could not seek in cache clean file!");
+ return NS_ERROR_FAILURE;
+ }
+ int32_t bytesWritten = PR_Write(mCleanFD, &data, 1);
+ if (bytesWritten != 1) {
+ NS_WARNING("Could not write cache clean file!");
+ return NS_ERROR_FAILURE;
+ }
+ PRStatus err = PR_Sync(mCleanFD);
+ if (err != PR_SUCCESS) {
+ NS_WARNING("Could not flush cache clean file!");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::InvalidateCache()
+{
+ nsCacheService::AssertOwnsLock();
+ CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
+ nsresult rv;
+
+ if (!mIsDirtyCacheFlushed) {
+ rv = WriteCacheClean(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mIsDirtyCacheFlushed = true;
+ }
+
+ rv = ResetCacheTimer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::ResetCacheTimer(int32_t timeout)
+{
+ mCleanCacheTimer->Cancel();
+ nsresult rv =
+ mCleanCacheTimer->InitWithFuncCallback(RevalidateTimerCallback,
+ nullptr, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mLastInvalidateTime = PR_IntervalNow();
+
+ return rv;
+}
+
+void
+nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg)
+{
+ nsCacheServiceAutoLock lock;
+ if (!nsCacheService::gService->mDiskDevice ||
+ !nsCacheService::gService->mDiskDevice->Initialized()) {
+ return;
+ }
+
+ nsDiskCacheMap *diskCacheMap =
+ &nsCacheService::gService->mDiskDevice->mCacheMap;
+
+ // If we have less than kRevalidateCacheTimeout since the last timer was
+ // issued then another thread called InvalidateCache. This won't catch
+ // all cases where we wanted to cancel the timer, but under the lock it
+ // is always OK to revalidate as long as IsCacheInSafeState() returns
+ // true. We just want to avoid revalidating when we can to reduce IO
+ // and this check will do that.
+ uint32_t delta =
+ PR_IntervalToMilliseconds(PR_IntervalNow() -
+ diskCacheMap->mLastInvalidateTime) +
+ kRevalidateCacheTimeoutTolerance;
+ if (delta < kRevalidateCacheTimeout) {
+ diskCacheMap->ResetCacheTimer();
+ return;
+ }
+
+ nsresult rv = diskCacheMap->RevalidateCache();
+ if (NS_FAILED(rv)) {
+ diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout);
+ }
+}
+
+bool
+nsDiskCacheMap::IsCacheInSafeState()
+{
+ return nsCacheService::GlobalInstance()->IsDoomListEmpty();
+}
+
+nsresult
+nsDiskCacheMap::RevalidateCache()
+{
+ CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
+ nsresult rv;
+
+ if (!IsCacheInSafeState()) {
+ CACHE_LOG_DEBUG(("CACHE: Revalidation should not performed because "
+ "cache not in a safe state\n"));
+ // Normally we would return an error here, but there is a bug where
+ // the doom list sometimes gets an entry 'stuck' and doens't clear it
+ // until browser shutdown. So we allow revalidation for the time being
+ // to get proper telemetry data of how much the cache corruption plan
+ // would help.
+ }
+
+ // If telemetry data shows it is worth it, we'll be flushing headers and
+ // records before flushing the clean cache file.
+
+ // Write out the _CACHE_CLEAN_ file with '1'
+ rv = WriteCacheClean(true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mIsDirtyCacheFlushed = false;
+
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsDiskCacheMap.h b/netwerk/cache/nsDiskCacheMap.h
new file mode 100644
index 0000000000..77af2586fa
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheMap.h
@@ -0,0 +1,577 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin 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 _nsDiskCacheMap_h_
+#define _nsDiskCacheMap_h_
+
+#include "mozilla/MemoryReporting.h"
+#include <limits.h>
+
+#include "prnetdb.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIFile.h"
+#include "nsITimer.h"
+
+#include "nsDiskCache.h"
+#include "nsDiskCacheBlockFile.h"
+
+
+class nsDiskCacheBinding;
+struct nsDiskCacheEntry;
+
+/******************************************************************************
+ * nsDiskCacheRecord
+ *
+ * Cache Location Format
+ *
+ * 1000 0000 0000 0000 0000 0000 0000 0000 : initialized bit
+ *
+ * 0011 0000 0000 0000 0000 0000 0000 0000 : File Selector (0 = separate file)
+ * 0000 0011 0000 0000 0000 0000 0000 0000 : number of extra contiguous blocks 1-4
+ * 0100 1100 0000 0000 0000 0000 0000 0000 : reserved bits
+ * 0000 0000 1111 1111 1111 1111 1111 1111 : block# 0-16777216 (2^24)
+ *
+ * 0000 0000 1111 1111 1111 1111 0000 0000 : eFileSizeMask (size of file in k: see note)
+ * 0000 0000 0000 0000 0000 0000 1111 1111 : eFileGenerationMask
+ *
+ * File Selector:
+ * 0 = separate file on disk
+ * 1 = 256 byte block file
+ * 2 = 1k block file
+ * 3 = 4k block file
+ *
+ * eFileSizeMask note: Files larger than 65535 KiB have this limit stored in
+ * the location. The file itself must be examined to
+ * determine its actual size if necessary.
+ *
+ *****************************************************************************/
+
+/*
+ We have 3 block files with roughly the same max size (32MB)
+ 1 - block size 256B, number of blocks 131072
+ 2 - block size 1kB, number of blocks 32768
+ 3 - block size 4kB, number of blocks 8192
+*/
+#define kNumBlockFiles 3
+#define SIZE_SHIFT(idx) (2 * ((idx) - 1))
+#define BLOCK_SIZE_FOR_INDEX(idx) ((idx) ? (256 << SIZE_SHIFT(idx)) : 0)
+#define BITMAP_SIZE_FOR_INDEX(idx) ((idx) ? (131072 >> SIZE_SHIFT(idx)) : 0)
+
+// Min and max values for the number of records in the DiskCachemap
+#define kMinRecordCount 512
+
+#define kSeparateFile 0
+#define kBuckets (1 << 5) // must be a power of 2!
+
+// Maximum size in K which can be stored in the location (see eFileSizeMask).
+// Both data and metadata can be larger, but only up to kMaxDataSizeK can be
+// counted into total cache size. I.e. if there are entries where either data or
+// metadata is larger than kMaxDataSizeK, the total cache size will be
+// inaccurate (smaller) than the actual cache size. The alternative is to stat
+// the files to find the real size, which was decided against for performance
+// reasons. See bug #651100 comment #21.
+#define kMaxDataSizeK 0xFFFF
+
+// preallocate up to 1MB of separate cache file
+#define kPreallocateLimit 1 * 1024 * 1024
+
+// The minimum amount of milliseconds to wait before re-attempting to
+// revalidate the cache.
+#define kRevalidateCacheTimeout 3000
+#define kRevalidateCacheTimeoutTolerance 10
+#define kRevalidateCacheErrorTimeout 1000
+
+class nsDiskCacheRecord {
+
+private:
+ uint32_t mHashNumber;
+ uint32_t mEvictionRank;
+ uint32_t mDataLocation;
+ uint32_t mMetaLocation;
+
+ enum {
+ eLocationInitializedMask = 0x80000000,
+
+ eLocationSelectorMask = 0x30000000,
+ eLocationSelectorOffset = 28,
+
+ eExtraBlocksMask = 0x03000000,
+ eExtraBlocksOffset = 24,
+
+ eReservedMask = 0x4C000000,
+
+ eBlockNumberMask = 0x00FFFFFF,
+
+ eFileSizeMask = 0x00FFFF00,
+ eFileSizeOffset = 8,
+ eFileGenerationMask = 0x000000FF,
+ eFileReservedMask = 0x4F000000
+
+ };
+
+public:
+ nsDiskCacheRecord()
+ : mHashNumber(0), mEvictionRank(0), mDataLocation(0), mMetaLocation(0)
+ {
+ }
+
+ bool ValidRecord()
+ {
+ if ((mDataLocation & eReservedMask) || (mMetaLocation & eReservedMask))
+ return false;
+ return true;
+ }
+
+ // HashNumber accessors
+ uint32_t HashNumber() const { return mHashNumber; }
+ void SetHashNumber( uint32_t hashNumber) { mHashNumber = hashNumber; }
+
+ // EvictionRank accessors
+ uint32_t EvictionRank() const { return mEvictionRank; }
+ void SetEvictionRank( uint32_t rank) { mEvictionRank = rank ? rank : 1; }
+
+ // DataLocation accessors
+ bool DataLocationInitialized() const { return 0 != (mDataLocation & eLocationInitializedMask); }
+ void ClearDataLocation() { mDataLocation = 0; }
+
+ uint32_t DataFile() const
+ {
+ return (uint32_t)(mDataLocation & eLocationSelectorMask) >> eLocationSelectorOffset;
+ }
+
+ void SetDataBlocks( uint32_t index, uint32_t startBlock, uint32_t blockCount)
+ {
+ // clear everything
+ mDataLocation = 0;
+
+ // set file index
+ NS_ASSERTION( index < (kNumBlockFiles + 1), "invalid location index");
+ NS_ASSERTION( index > 0,"invalid location index");
+ mDataLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask;
+
+ // set startBlock
+ NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number");
+ mDataLocation |= startBlock & eBlockNumberMask;
+
+ // set blockCount
+ NS_ASSERTION( (blockCount>=1) && (blockCount<=4),"invalid block count");
+ --blockCount;
+ mDataLocation |= (blockCount << eExtraBlocksOffset) & eExtraBlocksMask;
+
+ mDataLocation |= eLocationInitializedMask;
+ }
+
+ uint32_t DataBlockCount() const
+ {
+ return (uint32_t)((mDataLocation & eExtraBlocksMask) >> eExtraBlocksOffset) + 1;
+ }
+
+ uint32_t DataStartBlock() const
+ {
+ return (mDataLocation & eBlockNumberMask);
+ }
+
+ uint32_t DataBlockSize() const
+ {
+ return BLOCK_SIZE_FOR_INDEX(DataFile());
+ }
+
+ uint32_t DataFileSize() const { return (mDataLocation & eFileSizeMask) >> eFileSizeOffset; }
+ void SetDataFileSize(uint32_t size)
+ {
+ NS_ASSERTION((mDataLocation & eFileReservedMask) == 0, "bad location");
+ mDataLocation &= ~eFileSizeMask; // clear eFileSizeMask
+ mDataLocation |= (size << eFileSizeOffset) & eFileSizeMask;
+ }
+
+ uint8_t DataFileGeneration() const
+ {
+ return (mDataLocation & eFileGenerationMask);
+ }
+
+ void SetDataFileGeneration( uint8_t generation)
+ {
+ // clear everything, (separate file index = 0)
+ mDataLocation = 0;
+ mDataLocation |= generation & eFileGenerationMask;
+ mDataLocation |= eLocationInitializedMask;
+ }
+
+ // MetaLocation accessors
+ bool MetaLocationInitialized() const { return 0 != (mMetaLocation & eLocationInitializedMask); }
+ void ClearMetaLocation() { mMetaLocation = 0; }
+ uint32_t MetaLocation() const { return mMetaLocation; }
+
+ uint32_t MetaFile() const
+ {
+ return (uint32_t)(mMetaLocation & eLocationSelectorMask) >> eLocationSelectorOffset;
+ }
+
+ void SetMetaBlocks( uint32_t index, uint32_t startBlock, uint32_t blockCount)
+ {
+ // clear everything
+ mMetaLocation = 0;
+
+ // set file index
+ NS_ASSERTION( index < (kNumBlockFiles + 1), "invalid location index");
+ NS_ASSERTION( index > 0, "invalid location index");
+ mMetaLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask;
+
+ // set startBlock
+ NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number");
+ mMetaLocation |= startBlock & eBlockNumberMask;
+
+ // set blockCount
+ NS_ASSERTION( (blockCount>=1) && (blockCount<=4),"invalid block count");
+ --blockCount;
+ mMetaLocation |= (blockCount << eExtraBlocksOffset) & eExtraBlocksMask;
+
+ mMetaLocation |= eLocationInitializedMask;
+ }
+
+ uint32_t MetaBlockCount() const
+ {
+ return (uint32_t)((mMetaLocation & eExtraBlocksMask) >> eExtraBlocksOffset) + 1;
+ }
+
+ uint32_t MetaStartBlock() const
+ {
+ return (mMetaLocation & eBlockNumberMask);
+ }
+
+ uint32_t MetaBlockSize() const
+ {
+ return BLOCK_SIZE_FOR_INDEX(MetaFile());
+ }
+
+ uint32_t MetaFileSize() const { return (mMetaLocation & eFileSizeMask) >> eFileSizeOffset; }
+ void SetMetaFileSize(uint32_t size)
+ {
+ mMetaLocation &= ~eFileSizeMask; // clear eFileSizeMask
+ mMetaLocation |= (size << eFileSizeOffset) & eFileSizeMask;
+ }
+
+ uint8_t MetaFileGeneration() const
+ {
+ return (mMetaLocation & eFileGenerationMask);
+ }
+
+ void SetMetaFileGeneration( uint8_t generation)
+ {
+ // clear everything, (separate file index = 0)
+ mMetaLocation = 0;
+ mMetaLocation |= generation & eFileGenerationMask;
+ mMetaLocation |= eLocationInitializedMask;
+ }
+
+ uint8_t Generation() const
+ {
+ if ((mDataLocation & eLocationInitializedMask) &&
+ (DataFile() == 0))
+ return DataFileGeneration();
+
+ if ((mMetaLocation & eLocationInitializedMask) &&
+ (MetaFile() == 0))
+ return MetaFileGeneration();
+
+ return 0; // no generation
+ }
+
+#if defined(IS_LITTLE_ENDIAN)
+ void Swap()
+ {
+ mHashNumber = htonl(mHashNumber);
+ mEvictionRank = htonl(mEvictionRank);
+ mDataLocation = htonl(mDataLocation);
+ mMetaLocation = htonl(mMetaLocation);
+ }
+#endif
+
+#if defined(IS_LITTLE_ENDIAN)
+ void Unswap()
+ {
+ mHashNumber = ntohl(mHashNumber);
+ mEvictionRank = ntohl(mEvictionRank);
+ mDataLocation = ntohl(mDataLocation);
+ mMetaLocation = ntohl(mMetaLocation);
+ }
+#endif
+
+};
+
+
+/******************************************************************************
+ * nsDiskCacheRecordVisitor
+ *****************************************************************************/
+
+enum { kDeleteRecordAndContinue = -1,
+ kStopVisitingRecords = 0,
+ kVisitNextRecord = 1
+};
+
+class nsDiskCacheRecordVisitor {
+ public:
+
+ virtual int32_t VisitRecord( nsDiskCacheRecord * mapRecord) = 0;
+};
+
+
+/******************************************************************************
+ * nsDiskCacheHeader
+ *****************************************************************************/
+
+struct nsDiskCacheHeader {
+ uint32_t mVersion; // cache version.
+ uint32_t mDataSize; // size of cache in units of 1024bytes.
+ int32_t mEntryCount; // number of entries stored in cache.
+ uint32_t mIsDirty; // dirty flag.
+ int32_t mRecordCount; // Number of records
+ uint32_t mEvictionRank[kBuckets]; // Highest EvictionRank of the bucket
+ uint32_t mBucketUsage[kBuckets]; // Number of used entries in the bucket
+
+ nsDiskCacheHeader()
+ : mVersion(nsDiskCache::kCurrentVersion)
+ , mDataSize(0)
+ , mEntryCount(0)
+ , mIsDirty(true)
+ , mRecordCount(0)
+ {}
+
+ void Swap()
+ {
+#if defined(IS_LITTLE_ENDIAN)
+ mVersion = htonl(mVersion);
+ mDataSize = htonl(mDataSize);
+ mEntryCount = htonl(mEntryCount);
+ mIsDirty = htonl(mIsDirty);
+ mRecordCount = htonl(mRecordCount);
+
+ for (uint32_t i = 0; i < kBuckets ; i++) {
+ mEvictionRank[i] = htonl(mEvictionRank[i]);
+ mBucketUsage[i] = htonl(mBucketUsage[i]);
+ }
+#endif
+ }
+
+ void Unswap()
+ {
+#if defined(IS_LITTLE_ENDIAN)
+ mVersion = ntohl(mVersion);
+ mDataSize = ntohl(mDataSize);
+ mEntryCount = ntohl(mEntryCount);
+ mIsDirty = ntohl(mIsDirty);
+ mRecordCount = ntohl(mRecordCount);
+
+ for (uint32_t i = 0; i < kBuckets ; i++) {
+ mEvictionRank[i] = ntohl(mEvictionRank[i]);
+ mBucketUsage[i] = ntohl(mBucketUsage[i]);
+ }
+#endif
+ }
+};
+
+
+/******************************************************************************
+ * nsDiskCacheMap
+ *****************************************************************************/
+
+class nsDiskCacheMap {
+public:
+
+ nsDiskCacheMap() :
+ mCacheDirectory(nullptr),
+ mMapFD(nullptr),
+ mCleanFD(nullptr),
+ mRecordArray(nullptr),
+ mBufferSize(0),
+ mBuffer(nullptr),
+ mMaxRecordCount(16384), // this default value won't matter
+ mIsDirtyCacheFlushed(false),
+ mLastInvalidateTime(0)
+ { }
+
+ ~nsDiskCacheMap()
+ {
+ (void) Close(true);
+ }
+
+/**
+ * File Operations
+ *
+ * Open
+ *
+ * Creates a new cache map file if one doesn't exist.
+ * Returns error if it detects change in format or cache wasn't closed.
+ */
+ nsresult Open( nsIFile * cacheDirectory,
+ nsDiskCache::CorruptCacheInfo * corruptInfo);
+ nsresult Close(bool flush);
+ nsresult Trim();
+
+ nsresult FlushHeader();
+ nsresult FlushRecords( bool unswap);
+
+ void NotifyCapacityChange(uint32_t capacity);
+
+/**
+ * Record operations
+ */
+ nsresult AddRecord( nsDiskCacheRecord * mapRecord, nsDiskCacheRecord * oldRecord);
+ nsresult UpdateRecord( nsDiskCacheRecord * mapRecord);
+ nsresult FindRecord( uint32_t hashNumber, nsDiskCacheRecord * mapRecord);
+ nsresult DeleteRecord( nsDiskCacheRecord * mapRecord);
+ nsresult VisitRecords( nsDiskCacheRecordVisitor * visitor);
+ nsresult EvictRecords( nsDiskCacheRecordVisitor * visitor);
+
+/**
+ * Disk Entry operations
+ */
+ nsresult DeleteStorage( nsDiskCacheRecord * record);
+
+ nsresult GetFileForDiskCacheRecord( nsDiskCacheRecord * record,
+ bool meta,
+ bool createPath,
+ nsIFile ** result);
+
+ nsresult GetLocalFileForDiskCacheRecord( nsDiskCacheRecord * record,
+ bool meta,
+ bool createPath,
+ nsIFile ** result);
+
+ // On success, this returns the buffer owned by nsDiskCacheMap,
+ // so it must not be deleted by the caller.
+ nsDiskCacheEntry * ReadDiskCacheEntry( nsDiskCacheRecord * record);
+
+ nsresult WriteDiskCacheEntry( nsDiskCacheBinding * binding);
+
+ nsresult ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size);
+ nsresult WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size);
+ nsresult DeleteStorage( nsDiskCacheRecord * record, bool metaData);
+
+ /**
+ * Statistical Operations
+ */
+ void IncrementTotalSize( uint32_t delta)
+ {
+ mHeader.mDataSize += delta;
+ mHeader.mIsDirty = true;
+ }
+
+ void DecrementTotalSize( uint32_t delta)
+ {
+ NS_ASSERTION(mHeader.mDataSize >= delta, "disk cache size negative?");
+ mHeader.mDataSize = mHeader.mDataSize > delta ? mHeader.mDataSize - delta : 0;
+ mHeader.mIsDirty = true;
+ }
+
+ inline void IncrementTotalSize( uint32_t blocks, uint32_t blockSize)
+ {
+ // Round up to nearest K
+ IncrementTotalSize(((blocks*blockSize) + 0x03FF) >> 10);
+ }
+
+ inline void DecrementTotalSize( uint32_t blocks, uint32_t blockSize)
+ {
+ // Round up to nearest K
+ DecrementTotalSize(((blocks*blockSize) + 0x03FF) >> 10);
+ }
+
+ uint32_t TotalSize() { return mHeader.mDataSize; }
+
+ int32_t EntryCount() { return mHeader.mEntryCount; }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+
+private:
+
+ /**
+ * Private methods
+ */
+ nsresult OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo);
+ nsresult CloseBlockFiles(bool flush);
+ bool CacheFilesExist();
+
+ nsresult CreateCacheSubDirectories();
+
+ uint32_t CalculateFileIndex(uint32_t size);
+
+ nsresult GetBlockFileForIndex( uint32_t index, nsIFile ** result);
+ uint32_t GetBlockSizeForIndex( uint32_t index) const {
+ return BLOCK_SIZE_FOR_INDEX(index);
+ }
+ uint32_t GetBitMapSizeForIndex( uint32_t index) const {
+ return BITMAP_SIZE_FOR_INDEX(index);
+ }
+
+ // returns the bucket number
+ uint32_t GetBucketIndex( uint32_t hashNumber) const {
+ return (hashNumber & (kBuckets - 1));
+ }
+
+ // Gets the size of the bucket (in number of records)
+ uint32_t GetRecordsPerBucket() const {
+ return mHeader.mRecordCount / kBuckets;
+ }
+
+ // Gets the first record in the bucket
+ nsDiskCacheRecord *GetFirstRecordInBucket(uint32_t bucket) const {
+ return mRecordArray + bucket * GetRecordsPerBucket();
+ }
+
+ uint32_t GetBucketRank(uint32_t bucketIndex, uint32_t targetRank);
+
+ int32_t VisitEachRecord(uint32_t bucketIndex,
+ nsDiskCacheRecordVisitor * visitor,
+ uint32_t evictionRank);
+
+ nsresult GrowRecords();
+ nsresult ShrinkRecords();
+
+ nsresult EnsureBuffer(uint32_t bufSize);
+
+ // The returned structure will point to the buffer owned by nsDiskCacheMap,
+ // so it must not be deleted by the caller.
+ nsDiskCacheEntry * CreateDiskCacheEntry(nsDiskCacheBinding * binding,
+ uint32_t * size);
+
+ // Initializes the _CACHE_CLEAN_ related functionality
+ nsresult InitCacheClean(nsIFile * cacheDirectory,
+ nsDiskCache::CorruptCacheInfo * corruptInfo);
+ // Writes out a value of '0' or '1' in the _CACHE_CLEAN_ file
+ nsresult WriteCacheClean(bool clean);
+ // Resets the timout for revalidating the cache
+ nsresult ResetCacheTimer(int32_t timeout = kRevalidateCacheTimeout);
+ // Invalidates the cache, calls WriteCacheClean and ResetCacheTimer
+ nsresult InvalidateCache();
+ // Determines if the cache is in a safe state
+ bool IsCacheInSafeState();
+ // Revalidates the cache by writting out the header, records, and finally
+ // by calling WriteCacheClean(true).
+ nsresult RevalidateCache();
+ // Timer which revalidates the cache
+ static void RevalidateTimerCallback(nsITimer *aTimer, void *arg);
+
+/**
+ * data members
+ */
+private:
+ nsCOMPtr<nsITimer> mCleanCacheTimer;
+ nsCOMPtr<nsIFile> mCacheDirectory;
+ PRFileDesc * mMapFD;
+ PRFileDesc * mCleanFD;
+ nsDiskCacheRecord * mRecordArray;
+ nsDiskCacheBlockFile mBlockFile[kNumBlockFiles];
+ uint32_t mBufferSize;
+ char * mBuffer;
+ nsDiskCacheHeader mHeader;
+ int32_t mMaxRecordCount;
+ bool mIsDirtyCacheFlushed;
+ PRIntervalTime mLastInvalidateTime;
+};
+
+#endif // _nsDiskCacheMap_h_
diff --git a/netwerk/cache/nsDiskCacheStreams.cpp b/netwerk/cache/nsDiskCacheStreams.cpp
new file mode 100644
index 0000000000..d58cca7fd8
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheStreams.cpp
@@ -0,0 +1,693 @@
+/* -*- 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 "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheDevice.h"
+#include "nsDiskCacheStreams.h"
+#include "nsCacheService.h"
+#include "mozilla/FileUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include <algorithm>
+
+// we pick 16k as the max buffer size because that is the threshold above which
+// we are unable to store the data in the cache block files
+// see nsDiskCacheMap.[cpp,h]
+#define kMaxBufferSize (16 * 1024)
+
+// Assumptions:
+// - cache descriptors live for life of streams
+// - streams will only be used by FileTransport,
+// they will not be directly accessible to clients
+// - overlapped I/O is NOT supported
+
+
+/******************************************************************************
+ * nsDiskCacheInputStream
+ *****************************************************************************/
+class nsDiskCacheInputStream : public nsIInputStream {
+
+public:
+
+ nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
+ PRFileDesc * fileDesc,
+ const char * buffer,
+ uint32_t endOfStream);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+private:
+ virtual ~nsDiskCacheInputStream();
+
+ nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
+ PRFileDesc * mFD;
+ const char * mBuffer;
+ uint32_t mStreamEnd;
+ uint32_t mPos; // stream position
+ bool mClosed;
+};
+
+
+NS_IMPL_ISUPPORTS(nsDiskCacheInputStream, nsIInputStream)
+
+
+nsDiskCacheInputStream::nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
+ PRFileDesc * fileDesc,
+ const char * buffer,
+ uint32_t endOfStream)
+ : mStreamIO(parent)
+ , mFD(fileDesc)
+ , mBuffer(buffer)
+ , mStreamEnd(endOfStream)
+ , mPos(0)
+ , mClosed(false)
+{
+ NS_ADDREF(mStreamIO);
+ mStreamIO->IncrementInputStreamCount();
+}
+
+
+nsDiskCacheInputStream::~nsDiskCacheInputStream()
+{
+ Close();
+ mStreamIO->DecrementInputStreamCount();
+ NS_RELEASE(mStreamIO);
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::Close()
+{
+ if (!mClosed) {
+ if (mFD) {
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ }
+ mClosed = true;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::Available(uint64_t * bytesAvailable)
+{
+ if (mClosed) return NS_BASE_STREAM_CLOSED;
+ if (mStreamEnd < mPos) return NS_ERROR_UNEXPECTED;
+
+ *bytesAvailable = mStreamEnd - mPos;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::Read(char * buffer, uint32_t count, uint32_t * bytesRead)
+{
+ *bytesRead = 0;
+
+ if (mClosed) {
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+ "[stream=%p] stream was closed",
+ this, buffer, count));
+ return NS_OK;
+ }
+
+ if (mPos == mStreamEnd) {
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+ "[stream=%p] stream at end of file",
+ this, buffer, count));
+ return NS_OK;
+ }
+ if (mPos > mStreamEnd) {
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+ "[stream=%p] stream past end of file (!)",
+ this, buffer, count));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (count > mStreamEnd - mPos)
+ count = mStreamEnd - mPos;
+
+ if (mFD) {
+ // just read from file
+ int32_t result = PR_Read(mFD, buffer, count);
+ if (result < 0) {
+ nsresult rv = NS_ErrorAccordingToNSPR();
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read PR_Read failed"
+ "[stream=%p, rv=%d, NSPR error %s",
+ this, int(rv), PR_ErrorToName(PR_GetError())));
+ return rv;
+ }
+
+ mPos += (uint32_t)result;
+ *bytesRead = (uint32_t)result;
+
+ } else if (mBuffer) {
+ // read data from mBuffer
+ memcpy(buffer, mBuffer + mPos, count);
+ mPos += count;
+ *bytesRead = count;
+ } else {
+ // no data source for input stream
+ }
+
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+ "[stream=%p, count=%ud, byteRead=%ud] ",
+ this, unsigned(count), unsigned(*bytesRead)));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer,
+ void * closure,
+ uint32_t count,
+ uint32_t * bytesRead)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
+
+
+
+
+/******************************************************************************
+ * nsDiskCacheStreamIO
+ *****************************************************************************/
+NS_IMPL_ISUPPORTS(nsDiskCacheStreamIO, nsIOutputStream)
+
+nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
+ : mBinding(binding)
+ , mInStreamCount(0)
+ , mFD(nullptr)
+ , mStreamEnd(0)
+ , mBufSize(0)
+ , mBuffer(nullptr)
+ , mOutputStreamIsOpen(false)
+{
+ mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
+
+ // acquire "death grip" on cache service
+ nsCacheService *service = nsCacheService::GlobalInstance();
+ NS_ADDREF(service);
+}
+
+
+nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
+{
+ nsCacheService::AssertOwnsLock();
+
+ // Close the outputstream
+ if (mBinding && mOutputStreamIsOpen) {
+ (void)CloseOutputStream();
+ }
+
+ // release "death grip" on cache service
+ nsCacheService *service = nsCacheService::GlobalInstance();
+ NS_RELEASE(service);
+
+ // assert streams closed
+ NS_ASSERTION(!mOutputStreamIsOpen, "output stream still open");
+ NS_ASSERTION(mInStreamCount == 0, "input stream still open");
+ NS_ASSERTION(!mFD, "file descriptor not closed");
+
+ DeleteBuffer();
+}
+
+
+// NOTE: called with service lock held
+nsresult
+nsDiskCacheStreamIO::GetInputStream(uint32_t offset, nsIInputStream ** inputStream)
+{
+ NS_ENSURE_ARG_POINTER(inputStream);
+ NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
+
+ *inputStream = nullptr;
+
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ if (mOutputStreamIsOpen) {
+ NS_WARNING("already have an output stream open");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ PRFileDesc * fd = nullptr;
+
+ mStreamEnd = mBinding->mCacheEntry->DataSize();
+ if (mStreamEnd == 0) {
+ // there's no data to read
+ NS_ASSERTION(!mBinding->mRecord.DataLocationInitialized(), "storage allocated for zero data size");
+ } else if (mBinding->mRecord.DataFile() == 0) {
+ // open file desc for data
+ rv = OpenCacheFile(PR_RDONLY, &fd);
+ if (NS_FAILED(rv)) return rv; // unable to open file
+ NS_ASSERTION(fd, "cache stream lacking open file.");
+
+ } else if (!mBuffer) {
+ // read block file for data
+ rv = ReadCacheBlocks(mStreamEnd);
+ if (NS_FAILED(rv)) return rv;
+ }
+ // else, mBuffer already contains all of the data (left over from a
+ // previous block-file read or write).
+
+ NS_ASSERTION(!(fd && mBuffer), "ambiguous data sources for input stream");
+
+ // create a new input stream
+ nsDiskCacheInputStream * inStream = new nsDiskCacheInputStream(this, fd, mBuffer, mStreamEnd);
+ if (!inStream) return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*inputStream = inStream);
+ return NS_OK;
+}
+
+
+// NOTE: called with service lock held
+nsresult
+nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream)
+{
+ NS_ENSURE_ARG_POINTER(outputStream);
+ *outputStream = nullptr;
+
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ASSERTION(!mOutputStreamIsOpen, "already have an output stream open");
+ NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
+ if (mOutputStreamIsOpen || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
+
+ mStreamEnd = mBinding->mCacheEntry->DataSize();
+
+ // Inits file or buffer and truncate at the desired offset
+ nsresult rv = SeekAndTruncate(offset);
+ if (NS_FAILED(rv)) return rv;
+
+ mOutputStreamIsOpen = true;
+ NS_ADDREF(*outputStream = this);
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheStreamIO::ClearBinding()
+{
+ nsresult rv = NS_OK;
+ if (mBinding && mOutputStreamIsOpen)
+ rv = CloseOutputStream();
+ mBinding = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::Close()
+{
+ if (!mOutputStreamIsOpen) return NS_OK;
+
+ // grab service lock
+ nsCacheServiceAutoLock lock;
+
+ if (!mBinding) { // if we're severed, just clear member variables
+ mOutputStreamIsOpen = false;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = CloseOutputStream();
+ if (NS_FAILED(rv))
+ NS_WARNING("CloseOutputStream() failed");
+
+ return rv;
+}
+
+nsresult
+nsDiskCacheStreamIO::CloseOutputStream()
+{
+ NS_ASSERTION(mBinding, "oops");
+
+ CACHE_LOG_DEBUG(("CACHE: CloseOutputStream [%x doomed=%u]\n",
+ mBinding->mRecord.HashNumber(), mBinding->mDoomed));
+
+ // Mark outputstream as closed, even if saving the stream fails
+ mOutputStreamIsOpen = false;
+
+ // When writing to a file, just close the file
+ if (mFD) {
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ return NS_OK;
+ }
+
+ // write data to cache blocks, or flush mBuffer to file
+ NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "stream is bigger than buffer");
+
+ nsDiskCacheMap *cacheMap = mDevice->CacheMap(); // get map reference
+ nsDiskCacheRecord * record = &mBinding->mRecord;
+ nsresult rv = NS_OK;
+
+ // delete existing storage
+ if (record->DataLocationInitialized()) {
+ rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only call UpdateRecord when there is no data to write,
+ // because WriteDataCacheBlocks / FlushBufferToFile calls it.
+ if ((mStreamEnd == 0) && (!mBinding->mDoomed)) {
+ rv = cacheMap->UpdateRecord(record);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("cacheMap->UpdateRecord() failed.");
+ return rv; // XXX doom cache entry
+ }
+ }
+ }
+
+ if (mStreamEnd == 0) return NS_OK; // nothing to write
+
+ // try to write to the cache blocks
+ rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("WriteDataCacheBlocks() failed.");
+
+ // failed to store in cacheblocks, save as separate file
+ rv = FlushBufferToFile(); // initializes DataFileLocation() if necessary
+ if (mFD) {
+ UpdateFileSize();
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ }
+ else
+ NS_WARNING("no file descriptor");
+ }
+
+ return rv;
+}
+
+
+// assumptions:
+// only one thread writing at a time
+// never have both output and input streams open
+// OnDataSizeChanged() will have already been called to update entry->DataSize()
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::Write( const char * buffer,
+ uint32_t count,
+ uint32_t * bytesWritten)
+{
+ NS_ENSURE_ARG_POINTER(buffer);
+ NS_ENSURE_ARG_POINTER(bytesWritten);
+ if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
+
+ *bytesWritten = 0; // always initialize to zero in case of errors
+
+ NS_ASSERTION(count, "Write called with count of zero");
+ if (count == 0) {
+ return NS_OK; // nothing to write
+ }
+
+ // grab service lock
+ nsCacheServiceAutoLock lock;
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ if (mInStreamCount) {
+ // we have open input streams already
+ // this is an error until we support overlapped I/O
+ NS_WARNING("Attempting to write to cache entry with open input streams.\n");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Not writing to file, and it will fit in the cachedatablocks?
+ if (!mFD && (mStreamEnd + count <= kMaxBufferSize)) {
+
+ // We have more data than the current buffer size?
+ if ((mStreamEnd + count > mBufSize) && (mBufSize < kMaxBufferSize)) {
+ // Increase buffer to the maximum size.
+ mBuffer = (char *) moz_xrealloc(mBuffer, kMaxBufferSize);
+ mBufSize = kMaxBufferSize;
+ }
+
+ // Store in the buffer but only if it fits
+ if (mStreamEnd + count <= mBufSize) {
+ memcpy(mBuffer + mStreamEnd, buffer, count);
+ mStreamEnd += count;
+ *bytesWritten = count;
+ return NS_OK;
+ }
+ }
+
+ // There are more bytes than fit in the buffer/cacheblocks, switch to file
+ if (!mFD) {
+ // Opens a cache file and write the buffer to it
+ nsresult rv = FlushBufferToFile();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ // Write directly to the file
+ if (PR_Write(mFD, buffer, count) != (int32_t)count) {
+ NS_WARNING("failed to write all data");
+ return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
+ }
+ mStreamEnd += count;
+ *bytesWritten = count;
+
+ UpdateFileSize();
+ NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream");
+
+ return NS_OK;
+}
+
+
+void
+nsDiskCacheStreamIO::UpdateFileSize()
+{
+ NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called");
+
+ nsDiskCacheRecord * record = &mBinding->mRecord;
+ const uint32_t oldSizeK = record->DataFileSize();
+ uint32_t newSizeK = (mStreamEnd + 0x03FF) >> 10;
+
+ // make sure the size won't overflow (bug #651100)
+ if (newSizeK > kMaxDataSizeK)
+ newSizeK = kMaxDataSizeK;
+
+ if (newSizeK == oldSizeK) return;
+
+ record->SetDataFileSize(newSizeK);
+
+ // update cache size totals
+ nsDiskCacheMap * cacheMap = mDevice->CacheMap();
+ cacheMap->DecrementTotalSize(oldSizeK); // decrement old size
+ cacheMap->IncrementTotalSize(newSizeK); // increment new size
+
+ if (!mBinding->mDoomed) {
+ nsresult rv = cacheMap->UpdateRecord(record);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("cacheMap->UpdateRecord() failed.");
+ // XXX doom cache entry?
+ }
+ }
+}
+
+
+nsresult
+nsDiskCacheStreamIO::OpenCacheFile(int flags, PRFileDesc ** fd)
+{
+ NS_ENSURE_ARG_POINTER(fd);
+
+ CACHE_LOG_DEBUG(("nsDiskCacheStreamIO::OpenCacheFile"));
+
+ nsresult rv;
+ nsDiskCacheMap * cacheMap = mDevice->CacheMap();
+ nsCOMPtr<nsIFile> localFile;
+
+ rv = cacheMap->GetLocalFileForDiskCacheRecord(&mBinding->mRecord,
+ nsDiskCache::kData,
+ !!(flags & PR_CREATE_FILE),
+ getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ // create PRFileDesc for input stream - the 00600 is just for consistency
+ return localFile->OpenNSPRFileDesc(flags, 00600, fd);
+}
+
+
+nsresult
+nsDiskCacheStreamIO::ReadCacheBlocks(uint32_t bufferSize)
+{
+ NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "bad stream");
+ NS_ASSERTION(bufferSize <= kMaxBufferSize, "bufferSize too large for buffer");
+ NS_ASSERTION(mStreamEnd <= bufferSize, "data too large for buffer");
+
+ nsDiskCacheRecord * record = &mBinding->mRecord;
+ if (!record->DataLocationInitialized()) return NS_OK;
+
+ NS_ASSERTION(record->DataFile() != kSeparateFile, "attempt to read cache blocks on separate file");
+
+ if (!mBuffer) {
+ mBuffer = (char *) moz_xmalloc(bufferSize);
+ mBufSize = bufferSize;
+ }
+
+ // read data stored in cache block files
+ nsDiskCacheMap *map = mDevice->CacheMap(); // get map reference
+ return map->ReadDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
+}
+
+
+nsresult
+nsDiskCacheStreamIO::FlushBufferToFile()
+{
+ nsresult rv;
+ nsDiskCacheRecord * record = &mBinding->mRecord;
+
+ if (!mFD) {
+ if (record->DataLocationInitialized() && (record->DataFile() > 0)) {
+ // remove cache block storage
+ nsDiskCacheMap * cacheMap = mDevice->CacheMap();
+ rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
+ if (NS_FAILED(rv)) return rv;
+ }
+ record->SetDataFileGeneration(mBinding->mGeneration);
+
+ // allocate file
+ rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
+ if (NS_FAILED(rv)) return rv;
+
+ int64_t dataSize = mBinding->mCacheEntry->PredictedDataSize();
+ if (dataSize != -1)
+ mozilla::fallocate(mFD, std::min<int64_t>(dataSize, kPreallocateLimit));
+ }
+
+ // write buffer to the file when there is data in it
+ if (mStreamEnd > 0) {
+ if (!mBuffer) {
+ NS_RUNTIMEABORT("Fix me!");
+ }
+ if (PR_Write(mFD, mBuffer, mStreamEnd) != (int32_t)mStreamEnd) {
+ NS_WARNING("failed to flush all data");
+ return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
+ }
+ }
+
+ // buffer is no longer valid
+ DeleteBuffer();
+
+ return NS_OK;
+}
+
+
+void
+nsDiskCacheStreamIO::DeleteBuffer()
+{
+ if (mBuffer) {
+ free(mBuffer);
+ mBuffer = nullptr;
+ mBufSize = 0;
+ }
+}
+
+size_t
+nsDiskCacheStreamIO::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+ size_t usage = aMallocSizeOf(this);
+
+ usage += aMallocSizeOf(mFD);
+ usage += aMallocSizeOf(mBuffer);
+
+ return usage;
+}
+
+nsresult
+nsDiskCacheStreamIO::SeekAndTruncate(uint32_t offset)
+{
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ if (uint32_t(offset) > mStreamEnd) return NS_ERROR_FAILURE;
+
+ // Set the current end to the desired offset
+ mStreamEnd = offset;
+
+ // Currently stored in file?
+ if (mBinding->mRecord.DataLocationInitialized() &&
+ (mBinding->mRecord.DataFile() == 0)) {
+ if (!mFD) {
+ // we need an mFD, we better open it now
+ nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (offset) {
+ if (PR_Seek(mFD, offset, PR_SEEK_SET) == -1)
+ return NS_ErrorAccordingToNSPR();
+ }
+ nsDiskCache::Truncate(mFD, offset);
+ UpdateFileSize();
+
+ // When we starting at zero again, close file and start with buffer.
+ // If offset is non-zero (and within buffer) an option would be
+ // to read the file into the buffer, but chance is high that it is
+ // rewritten to the file anyway.
+ if (offset == 0) {
+ // close file descriptor
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ }
+ return NS_OK;
+ }
+
+ // read data into mBuffer if not read yet.
+ if (offset && !mBuffer) {
+ nsresult rv = ReadCacheBlocks(kMaxBufferSize);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // stream buffer sanity check
+ NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "bad stream");
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::Flush()
+{
+ if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
+{
+ NS_NOTREACHED("WriteFrom");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::WriteSegments( nsReadSegmentFun reader,
+ void * closure,
+ uint32_t count,
+ uint32_t * bytesWritten)
+{
+ NS_NOTREACHED("WriteSegments");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsDiskCacheStreams.h b/netwerk/cache/nsDiskCacheStreams.h
new file mode 100644
index 0000000000..247c16a6ef
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheStreams.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef _nsDiskCacheStreams_h_
+#define _nsDiskCacheStreams_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nsDiskCacheBinding.h"
+
+#include "nsCache.h"
+
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+
+#include "mozilla/Atomics.h"
+
+class nsDiskCacheDevice;
+
+class nsDiskCacheStreamIO : public nsIOutputStream {
+public:
+ explicit nsDiskCacheStreamIO(nsDiskCacheBinding * binding);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsresult GetInputStream(uint32_t offset, nsIInputStream ** inputStream);
+ nsresult GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream);
+
+ nsresult ClearBinding();
+
+ void IncrementInputStreamCount() { mInStreamCount++; }
+ void DecrementInputStreamCount()
+ {
+ mInStreamCount--;
+ NS_ASSERTION(mInStreamCount >= 0, "mInStreamCount has gone negative");
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ // GCC 2.95.2 requires this to be defined, although we never call it.
+ // and OS/2 requires that it not be private
+ nsDiskCacheStreamIO() { NS_NOTREACHED("oops"); }
+
+private:
+ virtual ~nsDiskCacheStreamIO();
+
+ nsresult OpenCacheFile(int flags, PRFileDesc ** fd);
+ nsresult ReadCacheBlocks(uint32_t bufferSize);
+ nsresult FlushBufferToFile();
+ void UpdateFileSize();
+ void DeleteBuffer();
+ nsresult CloseOutputStream();
+ nsresult SeekAndTruncate(uint32_t offset);
+
+ nsDiskCacheBinding * mBinding; // not an owning reference
+ nsDiskCacheDevice * mDevice;
+ mozilla::Atomic<int32_t> mInStreamCount;
+ PRFileDesc * mFD;
+
+ uint32_t mStreamEnd; // current size of data
+ uint32_t mBufSize; // current end of buffer
+ char * mBuffer;
+ bool mOutputStreamIsOpen;
+};
+
+#endif // _nsDiskCacheStreams_h_
diff --git a/netwerk/cache/nsICache.idl b/netwerk/cache/nsICache.idl
new file mode 100644
index 0000000000..95bab1515e
--- /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.
+ */
+[scriptable, uuid(d6c67f38-b39a-4582-8a48-4c4f8a56dfd0)]
+interface nsICache
+{
+ /**
+ * 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..20fac747da
--- /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..b015a7244a
--- /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..e2a4dec5bd
--- /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..ddc9936357
--- /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 string description;
+
+ /**
+ * Get a usage report, statistics, miscellaneous data about
+ * the cache device.
+ */
+ readonly attribute string 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 string clientID;
+
+ /**
+ * Get the id for the device that stores this cache entry.
+ */
+ readonly attribute string 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/cache/nsMemoryCacheDevice.cpp b/netwerk/cache/nsMemoryCacheDevice.cpp
new file mode 100644
index 0000000000..042e860224
--- /dev/null
+++ b/netwerk/cache/nsMemoryCacheDevice.cpp
@@ -0,0 +1,617 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 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/. */
+
+#include "nsCache.h"
+#include "nsMemoryCacheDevice.h"
+#include "nsCacheService.h"
+#include "nsICacheService.h"
+#include "nsICacheVisitor.h"
+#include "nsIStorageStream.h"
+#include "nsCRT.h"
+#include "nsReadableUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Telemetry.h"
+#include <algorithm>
+
+// The memory cache implements the "LRU-SP" caching algorithm
+// described in "LRU-SP: A Size-Adjusted and Popularity-Aware LRU Replacement
+// Algorithm for Web Caching" by Kai Cheng and Yahiko Kambayashi.
+
+// We keep kQueueCount LRU queues, which should be about ceil(log2(mHardLimit))
+// The queues hold exponentially increasing ranges of floor(log2((size/nref)))
+// values for entries.
+// Entries larger than 2^(kQueueCount-1) go in the last queue.
+// Entries with no expiration go in the first queue.
+
+const char *gMemoryDeviceID = "memory";
+using namespace mozilla;
+
+nsMemoryCacheDevice::nsMemoryCacheDevice()
+ : mInitialized(false),
+ mHardLimit(4 * 1024 * 1024), // default, if no pref
+ mSoftLimit((mHardLimit * 9) / 10), // default, if no pref
+ mTotalSize(0),
+ mInactiveSize(0),
+ mEntryCount(0),
+ mMaxEntryCount(0),
+ mMaxEntrySize(-1) // -1 means "no limit"
+{
+ for (int i=0; i<kQueueCount; ++i)
+ PR_INIT_CLIST(&mEvictionList[i]);
+}
+
+
+nsMemoryCacheDevice::~nsMemoryCacheDevice()
+{
+ Shutdown();
+}
+
+
+nsresult
+nsMemoryCacheDevice::Init()
+{
+ if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
+
+ mMemCacheEntries.Init();
+ mInitialized = true;
+ return NS_OK;
+}
+
+
+nsresult
+nsMemoryCacheDevice::Shutdown()
+{
+ NS_ASSERTION(mInitialized, "### attempting shutdown while not initialized");
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ mMemCacheEntries.Shutdown();
+
+ // evict all entries
+ nsCacheEntry * entry, * next;
+
+ for (int i = kQueueCount - 1; i >= 0; --i) {
+ entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
+ while (entry != &mEvictionList[i]) {
+ NS_ASSERTION(!entry->IsInUse(), "### shutting down with active entries");
+ next = (nsCacheEntry *)PR_NEXT_LINK(entry);
+ PR_REMOVE_AND_INIT_LINK(entry);
+
+ // update statistics
+ int32_t memoryRecovered = (int32_t)entry->DataSize();
+ mTotalSize -= memoryRecovered;
+ mInactiveSize -= memoryRecovered;
+ --mEntryCount;
+
+ delete entry;
+ entry = next;
+ }
+ }
+
+/*
+ * we're not factoring in changes to meta data yet...
+ * NS_ASSERTION(mTotalSize == 0, "### mem cache leaking entries?");
+ */
+ NS_ASSERTION(mInactiveSize == 0, "### mem cache leaking entries?");
+ NS_ASSERTION(mEntryCount == 0, "### mem cache leaking entries?");
+
+ mInitialized = false;
+
+ return NS_OK;
+}
+
+
+const char *
+nsMemoryCacheDevice::GetDeviceID()
+{
+ return gMemoryDeviceID;
+}
+
+
+nsCacheEntry *
+nsMemoryCacheDevice::FindEntry(nsCString * key, bool *collision)
+{
+ mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_MEMORY_SEARCH_2> timer;
+ nsCacheEntry * entry = mMemCacheEntries.GetEntry(key);
+ if (!entry) return nullptr;
+
+ // move entry to the tail of an eviction list
+ PR_REMOVE_AND_INIT_LINK(entry);
+ PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
+
+ mInactiveSize -= entry->DataSize();
+
+ return entry;
+}
+
+
+nsresult
+nsMemoryCacheDevice::DeactivateEntry(nsCacheEntry * entry)
+{
+ CACHE_LOG_DEBUG(("nsMemoryCacheDevice::DeactivateEntry for entry 0x%p\n",
+ entry));
+ if (entry->IsDoomed()) {
+#ifdef DEBUG
+ // XXX verify we've removed it from mMemCacheEntries & eviction list
+#endif
+ delete entry;
+ CACHE_LOG_DEBUG(("deleted doomed entry 0x%p\n", entry));
+ return NS_OK;
+ }
+
+#ifdef DEBUG
+ nsCacheEntry * ourEntry = mMemCacheEntries.GetEntry(entry->Key());
+ NS_ASSERTION(ourEntry, "DeactivateEntry called for an entry we don't have!");
+ NS_ASSERTION(entry == ourEntry, "entry doesn't match ourEntry");
+ if (ourEntry != entry)
+ return NS_ERROR_INVALID_POINTER;
+#endif
+
+ mInactiveSize += entry->DataSize();
+ EvictEntriesIfNecessary();
+
+ return NS_OK;
+}
+
+
+nsresult
+nsMemoryCacheDevice::BindEntry(nsCacheEntry * entry)
+{
+ if (!entry->IsDoomed()) {
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(entry),"entry is already on a list!");
+
+ // append entry to the eviction list
+ PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
+
+ // add entry to hashtable of mem cache entries
+ nsresult rv = mMemCacheEntries.AddEntry(entry);
+ if (NS_FAILED(rv)) {
+ PR_REMOVE_AND_INIT_LINK(entry);
+ return rv;
+ }
+
+ // add size of entry to memory totals
+ ++mEntryCount;
+ if (mMaxEntryCount < mEntryCount) mMaxEntryCount = mEntryCount;
+
+ mTotalSize += entry->DataSize();
+ EvictEntriesIfNecessary();
+ }
+
+ return NS_OK;
+}
+
+
+void
+nsMemoryCacheDevice::DoomEntry(nsCacheEntry * entry)
+{
+#ifdef DEBUG
+ // debug code to verify we have entry
+ nsCacheEntry * hashEntry = mMemCacheEntries.GetEntry(entry->Key());
+ if (!hashEntry) NS_WARNING("no entry for key");
+ else if (entry != hashEntry) NS_WARNING("entry != hashEntry");
+#endif
+ CACHE_LOG_DEBUG(("Dooming entry 0x%p in memory cache\n", entry));
+ EvictEntry(entry, DO_NOT_DELETE_ENTRY);
+}
+
+
+nsresult
+nsMemoryCacheDevice::OpenInputStreamForEntry( nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(entry);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCOMPtr<nsIStorageStream> storage;
+ nsresult rv;
+
+ nsISupports *data = entry->Data();
+ if (data) {
+ storage = do_QueryInterface(data, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else {
+ rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage));
+ if (NS_FAILED(rv))
+ return rv;
+ entry->SetData(storage);
+ }
+
+ return storage->NewInputStream(offset, result);
+}
+
+
+nsresult
+nsMemoryCacheDevice::OpenOutputStreamForEntry( nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(entry);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCOMPtr<nsIStorageStream> storage;
+ nsresult rv;
+
+ nsISupports *data = entry->Data();
+ if (data) {
+ storage = do_QueryInterface(data, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else {
+ rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage));
+ if (NS_FAILED(rv))
+ return rv;
+ entry->SetData(storage);
+ }
+
+ return storage->GetOutputStream(offset, result);
+}
+
+
+nsresult
+nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry * entry,
+ nsIFile ** result )
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+bool
+nsMemoryCacheDevice::EntryIsTooBig(int64_t entrySize)
+{
+ CACHE_LOG_DEBUG(("nsMemoryCacheDevice::EntryIsTooBig "
+ "[size=%d max=%d soft=%d]\n",
+ entrySize, mMaxEntrySize, mSoftLimit));
+ if (mMaxEntrySize == -1)
+ return entrySize > mSoftLimit;
+ else
+ return (entrySize > mSoftLimit || entrySize > mMaxEntrySize);
+}
+
+size_t
+nsMemoryCacheDevice::TotalSize()
+{
+ return mTotalSize;
+}
+
+nsresult
+nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, int32_t deltaSize)
+{
+ if (entry->IsStreamData()) {
+ // we have the right to refuse or pre-evict
+ uint32_t newSize = entry->DataSize() + deltaSize;
+ if (EntryIsTooBig(newSize)) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ // adjust our totals
+ mTotalSize += deltaSize;
+
+ if (!entry->IsDoomed()) {
+ // move entry to the tail of the appropriate eviction list
+ PR_REMOVE_AND_INIT_LINK(entry);
+ PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, deltaSize)]);
+ }
+
+ EvictEntriesIfNecessary();
+ return NS_OK;
+}
+
+
+void
+nsMemoryCacheDevice::AdjustMemoryLimits(int32_t softLimit, int32_t hardLimit)
+{
+ mSoftLimit = softLimit;
+ mHardLimit = hardLimit;
+
+ // First, evict entries that won't fit into the new cache size.
+ EvictEntriesIfNecessary();
+}
+
+
+void
+nsMemoryCacheDevice::EvictEntry(nsCacheEntry * entry, bool deleteEntry)
+{
+ CACHE_LOG_DEBUG(("Evicting entry 0x%p from memory cache, deleting: %d\n",
+ entry, deleteEntry));
+ // remove entry from our hashtable
+ mMemCacheEntries.RemoveEntry(entry);
+
+ // remove entry from the eviction list
+ PR_REMOVE_AND_INIT_LINK(entry);
+
+ // update statistics
+ int32_t memoryRecovered = (int32_t)entry->DataSize();
+ mTotalSize -= memoryRecovered;
+ if (!entry->IsDoomed())
+ mInactiveSize -= memoryRecovered;
+ --mEntryCount;
+
+ if (deleteEntry) delete entry;
+}
+
+
+void
+nsMemoryCacheDevice::EvictEntriesIfNecessary(void)
+{
+ nsCacheEntry * entry;
+ nsCacheEntry * maxEntry;
+ CACHE_LOG_DEBUG(("EvictEntriesIfNecessary. mTotalSize: %d, mHardLimit: %d,"
+ "mInactiveSize: %d, mSoftLimit: %d\n",
+ mTotalSize, mHardLimit, mInactiveSize, mSoftLimit));
+
+ if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit))
+ return;
+
+ uint32_t now = SecondsFromPRTime(PR_Now());
+ uint64_t entryCost = 0;
+ uint64_t maxCost = 0;
+ do {
+ // LRU-SP eviction selection: Check the head of each segment (each
+ // eviction list, kept in LRU order) and select the maximal-cost
+ // entry for eviction. Cost is time-since-accessed * size / nref.
+ maxEntry = 0;
+ for (int i = kQueueCount - 1; i >= 0; --i) {
+ entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
+
+ // If the head of a list is in use, check the next available entry
+ while ((entry != &mEvictionList[i]) &&
+ (entry->IsInUse())) {
+ entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
+ }
+
+ if (entry != &mEvictionList[i]) {
+ entryCost = (uint64_t)
+ (now - entry->LastFetched()) * entry->DataSize() /
+ std::max(1, entry->FetchCount());
+ if (!maxEntry || (entryCost > maxCost)) {
+ maxEntry = entry;
+ maxCost = entryCost;
+ }
+ }
+ }
+ if (maxEntry) {
+ EvictEntry(maxEntry, DELETE_ENTRY);
+ } else {
+ break;
+ }
+ }
+ while ((mTotalSize >= mHardLimit) || (mInactiveSize >= mSoftLimit));
+}
+
+
+int
+nsMemoryCacheDevice::EvictionList(nsCacheEntry * entry, int32_t deltaSize)
+{
+ // favor items which never expire by putting them in the lowest-index queue
+ if (entry->ExpirationTime() == nsICache::NO_EXPIRATION_TIME)
+ return 0;
+
+ // compute which eviction queue this entry should go into,
+ // based on floor(log2(size/nref))
+ int32_t size = deltaSize + (int32_t)entry->DataSize();
+ int32_t fetchCount = std::max(1, entry->FetchCount());
+
+ return std::min((int)mozilla::FloorLog2(size / fetchCount), kQueueCount - 1);
+}
+
+
+nsresult
+nsMemoryCacheDevice::Visit(nsICacheVisitor * visitor)
+{
+ nsMemoryCacheDeviceInfo * deviceInfo = new nsMemoryCacheDeviceInfo(this);
+ nsCOMPtr<nsICacheDeviceInfo> deviceRef(deviceInfo);
+ if (!deviceInfo) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool keepGoing;
+ nsresult rv = visitor->VisitDevice(gMemoryDeviceID, deviceInfo, &keepGoing);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!keepGoing)
+ return NS_OK;
+
+ nsCacheEntry * entry;
+ nsCOMPtr<nsICacheEntryInfo> entryRef;
+
+ for (int i = kQueueCount - 1; i >= 0; --i) {
+ entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
+ while (entry != &mEvictionList[i]) {
+ nsCacheEntryInfo * entryInfo = new nsCacheEntryInfo(entry);
+ if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY;
+ entryRef = entryInfo;
+
+ rv = visitor->VisitEntry(gMemoryDeviceID, entryInfo, &keepGoing);
+ entryInfo->DetachEntry();
+ if (NS_FAILED(rv)) return rv;
+ if (!keepGoing) break;
+
+ entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
+ }
+ }
+ return NS_OK;
+}
+
+
+static bool
+IsEntryPrivate(nsCacheEntry* entry, void* args)
+{
+ return entry->IsPrivate();
+}
+
+struct ClientIDArgs {
+ const char* clientID;
+ uint32_t prefixLength;
+};
+
+static bool
+EntryMatchesClientID(nsCacheEntry* entry, void* args)
+{
+ const char * clientID = static_cast<ClientIDArgs*>(args)->clientID;
+ uint32_t prefixLength = static_cast<ClientIDArgs*>(args)->prefixLength;
+ const char * key = entry->Key()->get();
+ return !clientID || nsCRT::strncmp(clientID, key, prefixLength) == 0;
+}
+
+nsresult
+nsMemoryCacheDevice::DoEvictEntries(bool (*matchFn)(nsCacheEntry* entry, void* args), void* args)
+{
+ nsCacheEntry * entry;
+
+ for (int i = kQueueCount - 1; i >= 0; --i) {
+ PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
+ while (elem != &mEvictionList[i]) {
+ entry = (nsCacheEntry *)elem;
+ elem = PR_NEXT_LINK(elem);
+
+ if (!matchFn(entry, args))
+ continue;
+
+ if (entry->IsInUse()) {
+ nsresult rv = nsCacheService::DoomEntry(entry);
+ if (NS_FAILED(rv)) {
+ CACHE_LOG_WARNING(("memCache->DoEvictEntries() aborted: rv =%x", rv));
+ return rv;
+ }
+ } else {
+ EvictEntry(entry, DELETE_ENTRY);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMemoryCacheDevice::EvictEntries(const char * clientID)
+{
+ ClientIDArgs args = {clientID, clientID ? uint32_t(strlen(clientID)) : 0};
+ return DoEvictEntries(&EntryMatchesClientID, &args);
+}
+
+nsresult
+nsMemoryCacheDevice::EvictPrivateEntries()
+{
+ return DoEvictEntries(&IsEntryPrivate, nullptr);
+}
+
+
+// WARNING: SetCapacity can get called before Init()
+void
+nsMemoryCacheDevice::SetCapacity(int32_t capacity)
+{
+ int32_t hardLimit = capacity * 1024; // convert k into bytes
+ int32_t softLimit = (hardLimit * 9) / 10;
+ AdjustMemoryLimits(softLimit, hardLimit);
+}
+
+void
+nsMemoryCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
+{
+ // Internal unit is bytes. Changing this only takes effect *after* the
+ // change and has no consequences for existing cache-entries
+ if (maxSizeInKilobytes >= 0)
+ mMaxEntrySize = maxSizeInKilobytes * 1024;
+ else
+ mMaxEntrySize = -1;
+}
+
+#ifdef DEBUG
+void
+nsMemoryCacheDevice::CheckEntryCount()
+{
+ if (!mInitialized) return;
+
+ int32_t evictionListCount = 0;
+ for (int i=0; i<kQueueCount; ++i) {
+ PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
+ while (elem != &mEvictionList[i]) {
+ elem = PR_NEXT_LINK(elem);
+ ++evictionListCount;
+ }
+ }
+ NS_ASSERTION(mEntryCount == evictionListCount, "### mem cache badness");
+
+ int32_t entryCount = 0;
+ for (auto iter = mMemCacheEntries.Iter(); !iter.Done(); iter.Next()) {
+ ++entryCount;
+ }
+ NS_ASSERTION(mEntryCount == entryCount, "### mem cache badness");
+}
+#endif
+
+/******************************************************************************
+ * nsMemoryCacheDeviceInfo - for implementing about:cache
+ *****************************************************************************/
+
+
+NS_IMPL_ISUPPORTS(nsMemoryCacheDeviceInfo, nsICacheDeviceInfo)
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetDescription(char ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = NS_strdup("Memory cache device");
+ if (!*result) return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetUsageReport(char ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCString buffer;
+
+ buffer.AssignLiteral(" <tr>\n"
+ " <th>Inactive storage:</th>\n"
+ " <td>");
+ buffer.AppendInt(mDevice->mInactiveSize / 1024);
+ buffer.AppendLiteral(" KiB</td>\n"
+ " </tr>\n");
+
+ *result = ToNewCString(buffer);
+ if (!*result) return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetEntryCount(uint32_t * result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ // XXX compare calculated count vs. mEntryCount
+ *result = (uint32_t)mDevice->mEntryCount;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetTotalSize(uint32_t * result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = (uint32_t)mDevice->mTotalSize;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetMaximumSize(uint32_t * result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = (uint32_t)mDevice->mHardLimit;
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsMemoryCacheDevice.h b/netwerk/cache/nsMemoryCacheDevice.h
new file mode 100644
index 0000000000..331b04ba6a
--- /dev/null
+++ b/netwerk/cache/nsMemoryCacheDevice.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 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/. */
+
+#ifndef _nsMemoryCacheDevice_h_
+#define _nsMemoryCacheDevice_h_
+
+#include "nsCacheDevice.h"
+#include "PLDHashTable.h"
+#include "nsCacheEntry.h"
+
+
+class nsMemoryCacheDeviceInfo;
+
+/******************************************************************************
+ * nsMemoryCacheDevice
+ ******************************************************************************/
+class nsMemoryCacheDevice : public nsCacheDevice
+{
+public:
+ nsMemoryCacheDevice();
+ virtual ~nsMemoryCacheDevice();
+
+ virtual nsresult Init();
+ virtual nsresult Shutdown();
+
+ virtual const char * GetDeviceID(void);
+
+ virtual nsresult BindEntry( nsCacheEntry * entry );
+ virtual nsCacheEntry * FindEntry( nsCString * key, bool *collision );
+ virtual void DoomEntry( nsCacheEntry * entry );
+ virtual nsresult DeactivateEntry( nsCacheEntry * entry );
+
+ virtual nsresult OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result);
+
+ virtual nsresult OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result);
+
+ virtual nsresult GetFileForEntry( nsCacheEntry * entry,
+ nsIFile ** result );
+
+ virtual nsresult OnDataSizeChange( nsCacheEntry * entry, int32_t deltaSize );
+
+ virtual nsresult Visit( nsICacheVisitor * visitor );
+
+ virtual nsresult EvictEntries(const char * clientID);
+ nsresult EvictPrivateEntries();
+
+ void SetCapacity(int32_t capacity);
+ void SetMaxEntrySize(int32_t maxSizeInKilobytes);
+
+ bool EntryIsTooBig(int64_t entrySize);
+
+ size_t TotalSize();
+
+private:
+ friend class nsMemoryCacheDeviceInfo;
+ enum { DELETE_ENTRY = true,
+ DO_NOT_DELETE_ENTRY = false };
+
+ void AdjustMemoryLimits( int32_t softLimit, int32_t hardLimit);
+ void EvictEntry( nsCacheEntry * entry , bool deleteEntry);
+ void EvictEntriesIfNecessary();
+ int EvictionList(nsCacheEntry * entry, int32_t deltaSize);
+
+ typedef bool (*EvictionMatcherFn)(nsCacheEntry* entry, void* args);
+ nsresult DoEvictEntries(EvictionMatcherFn matchFn, void* args);
+
+#ifdef DEBUG
+ void CheckEntryCount();
+#endif
+ /*
+ * Data members
+ */
+ enum {
+ kQueueCount = 24 // entries > 2^23 (8Mb) start in last queue
+ };
+
+ nsCacheEntryHashTable mMemCacheEntries;
+ bool mInitialized;
+
+ PRCList mEvictionList[kQueueCount];
+
+ int32_t mHardLimit;
+ int32_t mSoftLimit;
+
+ int32_t mTotalSize;
+ int32_t mInactiveSize;
+
+ int32_t mEntryCount;
+ int32_t mMaxEntryCount;
+ int32_t mMaxEntrySize; // internal unit is bytes
+
+ // XXX what other stats do we want to keep?
+};
+
+
+/******************************************************************************
+ * nsMemoryCacheDeviceInfo - used to call nsIVisitor for about:cache
+ ******************************************************************************/
+class nsMemoryCacheDeviceInfo : public nsICacheDeviceInfo {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEDEVICEINFO
+
+ explicit nsMemoryCacheDeviceInfo(nsMemoryCacheDevice* device)
+ : mDevice(device)
+ {
+ }
+
+private:
+ virtual ~nsMemoryCacheDeviceInfo() {}
+ nsMemoryCacheDevice* mDevice;
+};
+
+
+#endif // _nsMemoryCacheDevice_h_
diff --git a/netwerk/cache2/AppCacheStorage.cpp b/netwerk/cache2/AppCacheStorage.cpp
new file mode 100644
index 0000000000..c550b272d4
--- /dev/null
+++ b/netwerk/cache2/AppCacheStorage.cpp
@@ -0,0 +1,177 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED0(AppCacheStorage, CacheStorage)
+
+AppCacheStorage::AppCacheStorage(nsILoadContextInfo* aInfo,
+ nsIApplicationCache* aAppCache)
+: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */, false /* skip size check */, false /* pin */)
+, mAppCache(aAppCache)
+{
+ MOZ_COUNT_CTOR(AppCacheStorage);
+}
+
+AppCacheStorage::~AppCacheStorage()
+{
+ ProxyReleaseMainThread(mAppCache);
+ MOZ_COUNT_DTOR(AppCacheStorage);
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncOpenURI(nsIURI *aURI,
+ const nsACString & aIdExtension,
+ uint32_t aFlags,
+ nsICacheEntryOpenCallback *aCallback)
+{
+ if (!CacheStorageService::Self())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ 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 = aURI->CloneIgnoringRef(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 = NS_LITERAL_CSTRING("anon&") + 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;
+ }
+
+ 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;
+
+ 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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cache2/AppCacheStorage.h b/netwerk/cache2/AppCacheStorage.h
new file mode 100644
index 0000000000..eef70dea08
--- /dev/null
+++ b/netwerk/cache2/AppCacheStorage.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 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_DECL_ISUPPORTS_INHERITED
+ 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..b79bf73734
--- /dev/null
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -0,0 +1,1920 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIStreamTransportService.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 <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)
+{
+ MOZ_COUNT_CTOR(CacheEntryHandle);
+
+#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));
+}
+
+CacheEntryHandle::~CacheEntryHandle()
+{
+ mEntry->ReleaseHandleRef();
+ mEntry->OnHandleClosed(this);
+
+ MOZ_COUNT_DTOR(CacheEntryHandle);
+}
+
+// CacheEntry::Callback
+
+CacheEntry::Callback::Callback(CacheEntry* aEntry,
+ nsICacheEntryOpenCallback *aCallback,
+ bool aReadOnly, bool aCheckOnAnyThread,
+ bool aSecret)
+: mEntry(aEntry)
+, mCallback(aCallback)
+, mTargetThread(do_GetCurrentThread())
+, 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)
+, mTargetThread(aThat.mTargetThread)
+, 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(mCallback, mTargetThread);
+
+ 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 mTargetThread->IsOnCurrentThread(aOnCheckThread);
+ }
+
+ // We can invoke check anywhere
+ *aOnCheckThread = true;
+ return NS_OK;
+}
+
+nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
+{
+ return mTargetThread->IsOnCurrentThread(aOnAvailThread);
+}
+
+// CacheEntry
+
+NS_IMPL_ISUPPORTS(CacheEntry,
+ nsICacheEntry,
+ nsIRunnable,
+ CacheFileListener)
+
+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)
+, mPredictedDataSize(0)
+, mUseCount(0)
+{
+ 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(EmptyCString(), mEnhanceID, mURI, aResult);
+}
+
+// static
+nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
+ nsCSubstring const& 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(nsCSubstring const& aStorageID,
+ nsCSubstring const& aEnhanceID,
+ nsCSubstring const& 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%08x, new=%d]",
+ this, 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%08x", this, newEntry.get(), rv));
+ newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
+ } else {
+ LOG((" exchanged of entry %p failed, rv=0x%08x", this, 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 (unfortunatelly, 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].mTargetThread->Dispatch(NewRunnableMethod(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);
+
+ nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
+ this, nullptr, &checkResult);
+ LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, 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 const 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.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+ LOG((" redispatched, (rv = 0x%08x)", 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%08x)", 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
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey)
+{
+ aKey.Assign(mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount)
+{
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
+}
+
+NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched)
+{
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetLastFetched(aLastFetched);
+}
+
+NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified)
+{
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetLastModified(aLastModified);
+}
+
+NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
+{
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetExpirationTime(aExpirationTime);
+}
+
+NS_IMETHODIMP CacheEntry::GetIsForcedValid(bool *aIsForcedValid)
+{
+ NS_ENSURE_ARG(aIsForcedValid);
+
+ MOZ_ASSERT(mState > LOADING);
+
+ 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;
+}
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
+{
+ LOG(("CacheEntry::OpenInputStream [this=%p]", this));
+ return OpenInputStreamInternal(offset, nullptr, _retval);
+}
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval)
+{
+ LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
+
+ nsresult rv;
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mState > EMPTY);
+
+ 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;
+}
+
+NS_IMETHODIMP CacheEntry::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+{
+ LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
+ PromiseFlatCString(type).get()));
+
+ nsresult rv;
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
+ LOG((" entry not in state to write alt-data"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIOutputStream> 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;
+}
+
+NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
+{
+ *aPredictedDataSize = mPredictedDataSize;
+ return NS_OK;
+}
+NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
+{
+ mPredictedDataSize = aPredictedDataSize;
+
+ if (!mSkipSizeCheck && CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
+ LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
+ AsyncDoom(nullptr);
+
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP 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);
+
+ nsXPIDLCString info;
+ nsCOMPtr<nsISupports> secInfo;
+ nsresult rv;
+
+ rv = mFile->GetElement("security-info", getter_Copies(info));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (info) {
+ 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;
+}
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
+{
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetElement(aKey, aRetval);
+}
+
+NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
+{
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->SetElement(aKey, aValue);
+}
+
+NS_IMETHODIMP CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
+{
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->VisitMetaData(aVisitor);
+}
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP 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;
+}
+
+NS_IMETHODIMP 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=%lld", *aDataSize));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP CacheEntry::GetAltDataSize(int64_t *aDataSize)
+{
+ LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
+ if (NS_FAILED(mFileStatus)) {
+ return mFileStatus;
+ }
+ return mFile->GetAltDataSize(aDataSize);
+}
+
+
+NS_IMETHODIMP CacheEntry::MarkValid()
+{
+ // NOT IMPLEMENTED ACTUALLY
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::MaybeMarkValid()
+{
+ // NOT IMPLEMENTED ACTUALLY
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
+{
+ *aWriteAccess = aWriteAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::Close()
+{
+ // NOT IMPLEMENTED ACTUALLY
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheEntry::GetDiskStorageSizeInKB(uint32_t *aDiskStorageSize)
+{
+ if (NS_FAILED(mFileStatus)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mFile->GetDiskStorageSizeInKB(aDiskStorageSize);
+}
+
+// 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;
+ }
+ }
+
+ 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>(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)
+: mEntry(aEntry)
+{
+ MOZ_COUNT_CTOR(CacheOutputCloseListener);
+}
+
+CacheOutputCloseListener::~CacheOutputCloseListener()
+{
+ MOZ_COUNT_DTOR(CacheOutputCloseListener);
+}
+
+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 unfortunatelly 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..7331be2a4f
--- /dev/null
+++ b/netwerk/cache2/CacheEntry.h
@@ -0,0 +1,418 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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)
+{
+ PRTime usec_per_sec = PR_USEC_PER_SEC;
+ return uint32_t(t_usec /= 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 nsICacheEntry
+ , public nsIRunnable
+ , public CacheFileListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRY
+ NS_DECL_NSIRUNNABLE
+
+ 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();
+
+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(nsCSubstring const& aStorageID,
+ nsCSubstring const& aEnhanceID,
+ nsIURI* aURI,
+ nsACString &aResult);
+
+ static nsresult HashingKey(nsCSubstring const& aStorageID,
+ nsCSubstring const& aEnhanceID,
+ nsCSubstring const& 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<nsIThread> mTargetThread;
+ 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)
+ : 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)
+ : 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;
+ int64_t mPredictedDataSize;
+ mozilla::TimeStamp mLoadStart;
+ uint32_t mUseCount;
+};
+
+
+class CacheEntryHandle : public nsICacheEntry
+{
+public:
+ explicit CacheEntryHandle(CacheEntry* aEntry);
+ CacheEntry* Entry() const { return mEntry; }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_NSICACHEENTRY(mEntry->)
+private:
+ virtual ~CacheEntryHandle();
+ RefPtr<CacheEntry> mEntry;
+};
+
+
+class CacheOutputCloseListener final : public Runnable
+{
+public:
+ void OnOutputClosed();
+
+private:
+ friend class CacheEntry;
+
+ virtual ~CacheOutputCloseListener();
+
+ 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..fa0a893823
--- /dev/null
+++ b/netwerk/cache2/CacheFile.cpp
@@ -0,0 +1,2377 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "CacheFile.h"
+
+#include "CacheFileChunk.h"
+#include "CacheFileInputStream.h"
+#include "CacheFileOutputStream.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Move.h"
+#include <algorithm>
+#include "nsComponentManagerUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Telemetry.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)
+ : mCallback(aCallback)
+ , mRV(aResult)
+ , mIsNew(aIsNew)
+ {
+ LOG(("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() "
+ "[this=%p]", this));
+ MOZ_COUNT_CTOR(NotifyCacheFileListenerEvent);
+ }
+
+protected:
+ ~NotifyCacheFileListenerEvent()
+ {
+ LOG(("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() "
+ "[this=%p]", this));
+ MOZ_COUNT_DTOR(NotifyCacheFileListenerEvent);
+ }
+
+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)
+ : mCallback(aCallback)
+ , mRV(aResult)
+ , mChunkIdx(aChunkIdx)
+ , mChunk(aChunk)
+ {
+ LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]",
+ this));
+ MOZ_COUNT_CTOR(NotifyChunkListenerEvent);
+ }
+
+protected:
+ ~NotifyChunkListenerEvent()
+ {
+ LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]",
+ this));
+ MOZ_COUNT_DTOR(NotifyChunkListenerEvent);
+ }
+
+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)
+ {
+ MOZ_COUNT_CTOR(DoomFileHelper);
+ }
+
+
+ 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()
+ {
+ MOZ_COUNT_DTOR(DoomFileHelper);
+ }
+
+ 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_THREADSAFE
+
+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;
+ }
+ else {
+ 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%08x, chunk=%p, idx=%u]",
+ this, aResult, aChunk, index));
+
+ 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%08x, chunk=%p, idx=%u]",
+ this, aResult, aChunk, aChunk->Index()));
+
+ MOZ_ASSERT(!mMemoryOnly);
+ MOZ_ASSERT(!mOpeningFile);
+ MOZ_ASSERT(mHandle);
+
+ 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=%d]", 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)
+{
+ nsresult rv;
+
+ // 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%08x, handle=%p]",
+ this, 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();
+ const RefPtr<CacheFileChunk>& chunk = iter.Data();
+
+ LOG(("CacheFile::OnFileOpened() - write [this=%p, idx=%u, chunk=%p]",
+ this, idx, chunk.get()));
+
+ mChunks.Put(idx, 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(RefPtr<nsISupports>(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);
+
+ rv = mMetadata->ReadMetadata(this);
+ if (NS_FAILED(rv)) {
+ mListener.swap(listener);
+ listener->OnFileReady(rv, false);
+ }
+
+ 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%08x]", this, 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, nullptr)) ||
+ (mAltDataOffset > mDataSize))) {
+ // alt-metadata cannot be parsed or alt-data offset is invalid
+ mMetadata->InitEmptyMetadata();
+ isNew = true;
+ mAltDataOffset = -1;
+ 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%08x]", this, 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%08x, handle=%p]",
+ this, 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%08x]", this, 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;
+ NS_ADDREF(*_retval = input);
+ return NS_OK;
+}
+
+nsresult
+CacheFile::OpenAlternativeInputStream(nsICacheEntry *aEntryHandle,
+ const char *aAltDataType,
+ nsIInputStream **_retval)
+{
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ nsresult rv;
+
+ 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%08x]", this, 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;
+ }
+
+ const char *altData = mMetadata->GetElement(CacheFileUtils::kAltDataKey);
+ MOZ_ASSERT(altData, "alt-metadata should exist but was not found!");
+ if (NS_WARN_IF(!altData)) {
+ LOG(("CacheFile::OpenAlternativeInputStream() - alt-metadata not found but "
+ "alt-data exists according to mAltDataOffset! [this=%p, ]", this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ int64_t offset;
+ nsCString availableAltData;
+ rv = CacheFileUtils::ParseAlternativeDataInfo(altData, &offset,
+ &availableAltData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_ASSERT(false, "alt-metadata unexpectedly failed to parse");
+ LOG(("CacheFile::OpenAlternativeInputStream() - Cannot parse alternative "
+ "metadata! [this=%p]", this));
+ return rv;
+ }
+
+ if (availableAltData != aAltDataType) {
+ LOG(("CacheFile::OpenAlternativeInputStream() - Alternative data is of a "
+ "different type than requested [this=%p, availableType=%s, "
+ "requestedType=%s]", this, availableAltData.get(), aAltDataType));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // mAltDataOffset must be in sync with what is stored in metadata
+ MOZ_ASSERT(mAltDataOffset == offset);
+
+ // 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;
+ NS_ADDREF(*_retval = input);
+ 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;
+ }
+
+ // 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)) {
+ return rv;
+ }
+ mMetadata->SetElement(CacheFileUtils::kAltDataKey, nullptr);
+ mAltDataOffset = -1;
+ }
+
+ // 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;
+ NS_ADDREF(*_retval = mOutput);
+ return NS_OK;
+}
+
+nsresult
+CacheFile::OpenAlternativeOutputStream(CacheOutputCloseListener *aCloseListener,
+ const char *aAltDataType,
+ nsIOutputStream **_retval)
+{
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ nsresult rv;
+
+ 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;
+ }
+
+ // 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) {
+ // Truncate old alt-data
+ rv = Truncate(mAltDataOffset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ mAltDataOffset = mDataSize;
+ }
+
+ nsAutoCString altMetadata;
+ CacheFileUtils::BuildAlternativeDataInfo(aAltDataType, mAltDataOffset,
+ altMetadata);
+ rv = mMetadata->SetElement(CacheFileUtils::kAltDataKey, altMetadata.get());
+ if (NS_FAILED(rv)) {
+ // Removing element shouldn't fail because it doesn't allocate memory.
+ mMetadata->SetElement(CacheFileUtils::kAltDataKey, nullptr);
+
+ mAltDataOffset = -1;
+ 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;
+ NS_ADDREF(*_retval = mOutput);
+ return NS_OK;
+}
+
+nsresult
+CacheFile::SetMemoryOnly()
+{
+ 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_strdup(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);
+
+ return mMetadata->Visit(aVisitor);
+}
+
+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();
+
+ if (mHandle && !mHandle->IsDoomed())
+ CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, &aExpirationTime);
+
+ return mMetadata->SetExpirationTime(aExpirationTime);
+}
+
+nsresult
+CacheFile::GetExpirationTime(uint32_t *_retval)
+{
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ return mMetadata->GetExpirationTime(_retval);
+}
+
+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);
+
+ return mMetadata->SetFrecency(aFrecency);
+}
+
+nsresult
+CacheFile::GetFrecency(uint32_t *_retval)
+{
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ return mMetadata->GetFrecency(_retval);
+}
+
+nsresult
+CacheFile::GetLastModified(uint32_t *_retval)
+{
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ return mMetadata->GetLastModified(_retval);
+}
+
+nsresult
+CacheFile::GetLastFetched(uint32_t *_retval)
+{
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ return mMetadata->GetLastFetched(_retval);
+}
+
+nsresult
+CacheFile::GetFetchCount(uint32_t *_retval)
+{
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ return mMetadata->GetFetchCount(_retval);
+}
+
+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();
+
+ return mMetadata->OnFetched();
+}
+
+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;
+ objs.SwapElements(mObjsToRelease);
+
+ mLock.Unlock();
+
+}
+
+void
+CacheFile::AssertOwnsLock() const
+{
+ mLock.AssertCurrentThreadOwns();
+}
+
+void
+CacheFile::ReleaseOutsideLock(RefPtr<nsISupports> aObject)
+{
+ AssertOwnsLock();
+
+ mObjsToRelease.AppendElement(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 {
+ rv = QueueChunkListener(aIndex, aCallback);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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, 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=%lld, mDataSize=%lld]",
+ this, off, mDataSize));
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ chunk = new CacheFileChunk(this, aIndex, aCaller == WRITER);
+ mChunks.Put(aIndex, 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) {
+ rv = QueueChunkListener(aIndex, aCallback);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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, 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
+ rv = QueueChunkListener(aIndex, aCallback);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } 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=%d]", 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>(aChunk->mFile.forget()).forget());
+
+ 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%08x]", this, chunk.get(), 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%08x]",
+ this, chunk.get(), 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>(aChunk->mFile.forget()).forget());
+
+ if (aCacheChunk) {
+ mCachedChunks.Put(aChunk->Index(), 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();
+
+ nsresult rv;
+
+ MOZ_ASSERT(aOffset <= mDataSize);
+ MOZ_ASSERT(mReady);
+
+ uint32_t lastChunk = 0;
+
+ if (aOffset > 0) {
+ lastChunk = (mDataSize - 1) / kChunkSize;
+ }
+
+ uint32_t bytesInLastChunk = aOffset - lastChunk * kChunkSize;
+
+ for (auto iter = mCachedChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+
+ if (idx > lastChunk) {
+ // This is unused chunk, simply remove it.
+ iter.Remove();
+ continue;
+ }
+
+ if (idx == lastChunk) {
+ RefPtr<CacheFileChunk>& chunk = iter.Data();
+
+ rv = chunk->Truncate(bytesInLastChunk);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ for (auto iter = mChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+ RefPtr<CacheFileChunk>& chunk = iter.Data();
+
+ if (idx > lastChunk) {
+ chunk->mDiscardedChunk = true;
+ mDiscardedChunks.AppendElement(chunk);
+ iter.Remove();
+ continue;
+ }
+
+ if (idx == lastChunk) {
+ RefPtr<CacheFileChunk>& chunk = iter.Data();
+
+ rv = chunk->Truncate(bytesInLastChunk);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ 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
+ }
+
+ NS_NOTREACHED("We should never get here");
+}
+
+nsresult
+CacheFile::RemoveInput(CacheFileInputStream *aInput, nsresult aStatus)
+{
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::RemoveInput() [this=%p, input=%p, status=0x%08x]", this,
+ aInput, 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));
+
+ return NS_OK;
+}
+
+nsresult
+CacheFile::RemoveOutput(CacheFileOutputStream *aOutput, nsresult aStatus)
+{
+ AssertOwnsLock();
+
+ LOG(("CacheFile::RemoveOutput() [this=%p, output=%p, status=0x%08x]", this,
+ aOutput, aStatus));
+
+ if (mOutput != aOutput) {
+ LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring"
+ " call [this=%p]", this));
+ return NS_OK;
+ }
+
+ 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) {
+ mStatus = aStatus;
+ }
+
+ // Notify close listener as the last action
+ aOutput->NotifyCloseListener();
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_OUTPUT_STREAM_STATUS,
+ StatusToTelemetryEnum(aStatus));
+
+ return NS_OK;
+}
+
+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%08x, idx=%u, chunk=%p]", this, aCallback, aTarget, aResult,
+ aChunkIdx, aChunk));
+
+ nsresult rv;
+ RefPtr<NotifyChunkListenerEvent> ev;
+ ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk);
+ if (aTarget)
+ rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+ else
+ rv = NS_DispatchToCurrentThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+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 = do_GetMainThread();
+ }
+ item->mCallback = aCallback;
+
+ ChunkListeners *listeners;
+ if (!mChunkListeners.Get(aIndex, &listeners)) {
+ listeners = new ChunkListeners();
+ mChunkListeners.Put(aIndex, listeners);
+ }
+
+ listeners->mItems.AppendElement(item);
+ return NS_OK;
+}
+
+nsresult
+CacheFile::NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
+ CacheFileChunk *aChunk)
+{
+ LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%u, rv=0x%08x, "
+ "chunk=%p]", this, aIndex, 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();
+ nsAutoPtr<ChunkListeners>& listeners = iter.Data();
+
+ LOG(("CacheFile::NotifyListenersAboutOutputRemoval() - fail "
+ "[this=%p, idx=%u]", this, idx));
+
+ RefPtr<CacheFileChunk> chunk;
+ mChunks.Get(idx, getter_AddRefs(chunk));
+ if (chunk) {
+ MOZ_ASSERT(!chunk->IsReady());
+ 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;
+}
+
+bool
+CacheFile::IsDoomed()
+{
+ CacheFileAutoLock lock(this);
+
+ if (!mHandle)
+ return false;
+
+ return mHandle->IsDoomed();
+}
+
+bool
+CacheFile::IsWriteInProgress()
+{
+ // Returns true when there is a potentially unfinished write operation.
+ // Not using lock for performance reasons. mMetadata is never released
+ // during life time of CacheFile.
+
+ bool result = false;
+
+ if (!mMemoryOnly) {
+ result = mDataIsDirty ||
+ (mMetadata && mMetadata->IsDirty()) ||
+ mWritingMetadata;
+ }
+
+ result = result ||
+ mOpeningFile ||
+ mOutput ||
+ mChunks.Count();
+
+ return result;
+}
+
+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(chunk.forget());
+ 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(chunk.forget());
+
+ 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 expTime;
+ mMetadata->GetExpirationTime(&expTime);
+
+ uint32_t frecency;
+ mMetadata->GetFrecency(&frecency);
+
+ rv = CacheFileIOManager::UpdateIndexEntry(mHandle, &frecency, &expTime);
+ 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);
+ }
+ if (mMetadata) {
+ 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..3c8f7e97d6
--- /dev/null
+++ b/netwerk/cache2/CacheFile.h
@@ -0,0 +1,272 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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, nsIOutputStream **_retval);
+ NS_IMETHOD SetMemoryOnly();
+ NS_IMETHOD Doom(CacheFileListener *aCallback);
+
+ void Kill() { mKill = true; }
+ nsresult ThrowMemoryCachedData();
+
+ nsresult GetAltDataSize(int64_t *aSize);
+
+ // 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 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; }
+ bool IsWriteInProgress();
+
+ // 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);
+
+ nsresult RemoveInput(CacheFileInputStream *aInput, nsresult aStatus);
+ nsresult RemoveOutput(CacheFileOutputStream *aOutput, nsresult aStatus);
+ nsresult NotifyChunkListener(CacheFileChunkListener *aCallback,
+ nsIEventTarget *aTarget,
+ nsresult aResult,
+ uint32_t aChunkIdx,
+ CacheFileChunk *aChunk);
+ nsresult 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 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;
+
+ 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..83d79f79fc
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -0,0 +1,932 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+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->mBufSize);
+ memcpy(mBuf + invalidOffset, aOther->mBuf + invalidOffset, invalidLength);
+ }
+ invalidOffset = validOffset + validLength;
+ }
+
+ if (invalidOffset < aOther->mBufSize) {
+ invalidLength = aOther->mBufSize - invalidOffset;
+ memcpy(mBuf + invalidOffset, aOther->mBuf + invalidOffset, invalidLength);
+ }
+
+ return NS_OK;
+}
+
+MOZ_MUST_USE 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.
+ (mDataSize == 0 && 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)
+ : mCallback(aCallback)
+ , mChunk(aChunk)
+ {
+ LOG(("NotifyUpdateListenerEvent::NotifyUpdateListenerEvent() [this=%p]",
+ this));
+ MOZ_COUNT_CTOR(NotifyUpdateListenerEvent);
+ }
+
+protected:
+ ~NotifyUpdateListenerEvent()
+ {
+ LOG(("NotifyUpdateListenerEvent::~NotifyUpdateListenerEvent() [this=%p]",
+ this));
+ MOZ_COUNT_DTOR(NotifyUpdateListenerEvent);
+ }
+
+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(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;
+ }
+
+ NS_PRECONDITION(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_THREADSAFE
+
+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));
+ MOZ_COUNT_CTOR(CacheFileChunk);
+
+ mBuf = new CacheFileChunkBuffer(this);
+}
+
+CacheFileChunk::~CacheFileChunk()
+{
+ LOG(("CacheFileChunk::~CacheFileChunk() [this=%p]", this));
+ MOZ_COUNT_DTOR(CacheFileChunk);
+}
+
+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 = new 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 = do_GetMainThread();
+ }
+ item->mCallback = aCallback;
+ MOZ_ASSERT(item->mTarget);
+ item->mCallback = aCallback;
+
+ mUpdateListeners.AppendElement(item);
+}
+
+nsresult
+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
+
+ return NS_OK;
+}
+
+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
+{
+ AssertOwnsLock();
+
+ MOZ_ASSERT(!mListener);
+ 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();
+}
+
+nsresult
+CacheFileChunk::Truncate(uint32_t aOffset)
+{
+ mBuf->SetDataSize(aOffset);
+ return NS_OK;
+}
+
+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%08x]",
+ this, aHandle, 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%08x]",
+ this, aHandle, 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->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
+{
+ AssertOwnsLock();
+
+ return (NS_SUCCEEDED(mStatus) && (mState == READY || mState == WRITING));
+}
+
+bool
+CacheFileChunk::IsDirty() const
+{
+ AssertOwnsLock();
+
+ return mIsDirty;
+}
+
+nsresult
+CacheFileChunk::GetStatus()
+{
+ AssertOwnsLock();
+
+ return mStatus;
+}
+
+void
+CacheFileChunk::SetError(nsresult aStatus)
+{
+ LOG(("CacheFileChunk::SetError() [this=%p, status=0x%08x]", this, 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));
+
+ uint32_t limit = CacheObserver::MaxDiskChunksMemoryUsage(mIsPriority);
+ if (limit == 0) {
+ return true;
+ }
+
+ uint32_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..09acb62c5a
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.h
@@ -0,0 +1,252 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsAutoPtr.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+#define kChunkSize (256 * 1024)
+#define 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:
+ ChunkListenerItem() { MOZ_COUNT_CTOR(ChunkListenerItem); }
+ ~ChunkListenerItem() { MOZ_COUNT_DTOR(ChunkListenerItem); }
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+ nsCOMPtr<CacheFileChunkListener> mCallback;
+};
+
+class ChunkListeners {
+public:
+ ChunkListeners() { MOZ_COUNT_CTOR(ChunkListeners); }
+ ~ChunkListeners() { MOZ_COUNT_DTOR(ChunkListeners); }
+
+ nsTArray<ChunkListenerItem *> mItems;
+};
+
+class CacheFileChunk : 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);
+ nsresult 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);
+ nsresult 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.
+ nsAutoPtr<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..acf089b797
--- /dev/null
+++ b/netwerk/cache2/CacheFileContextEvictor.cpp
@@ -0,0 +1,663 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsISimpleEnumerator.h"
+#include "nsIDirectoryEnumerator.h"
+#include "mozilla/Base64.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(NS_LITERAL_CSTRING(ENTRIES_DIR));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!sDiskAlreadySearched) {
+ LoadEvictInfoFromDisk();
+ if ((mEntries.Length() != 0) && mIndexIsUpToDate) {
+ CreateIterators();
+ StartEvicting();
+ }
+ }
+
+ return NS_OK;
+}
+
+uint32_t
+CacheFileContextEvictor::ContextsCount()
+{
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ return mEntries.Length();
+}
+
+nsresult
+CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo,
+ bool aPinned)
+{
+ 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) {
+ entry = mEntries[i];
+ 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.RemoveElementAt(i);
+ }
+ }
+ }
+
+ if (!entry) {
+ entry = new CacheFileContextEvictorEntry();
+ entry->mInfo = aLoadContextInfo;
+ entry->mPinned = aPinned;
+ mEntries.AppendElement(entry);
+ }
+
+ entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
+
+ PersistEvictionInfoToDisk(aLoadContextInfo, aPinned);
+
+ 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%08x]", rv));
+ mEntries.RemoveElement(entry);
+ return rv;
+ }
+
+ StartEvicting();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+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 NS_OK;
+ }
+
+ if (!isUpToDate && !mIndexIsUpToDate) {
+ // Index is outdated and status has not changed, nothing to do.
+ return NS_OK;
+ }
+
+ if (isUpToDate && mIndexIsUpToDate) {
+ // Status has not changed, but make sure the eviction is running.
+ if (mEvicting) {
+ return NS_OK;
+ }
+
+ // 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();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::WasEvicted(const nsACString &aKey, nsIFile *aFile,
+ bool *aEvictedAsPinned, bool *aEvictedAsNonPinned)
+{
+ LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
+ PromiseFlatCString(aKey).get()));
+
+ nsresult rv;
+
+ *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 NS_OK;
+ }
+
+ for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+ CacheFileContextEvictorEntry *entry = mEntries[i];
+
+ if (entry->mInfo && !info->Equals(entry->mInfo)) {
+ continue;
+ }
+
+ PRTime lastModifiedTime;
+ rv = aFile->GetLastModifiedTime(&lastModifiedTime);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheFileContextEvictor::WasEvicted() - Cannot get last modified time"
+ ", returning false."));
+ return NS_OK;
+ }
+
+ if (lastModifiedTime > entry->mTimeStamp) {
+ // File has been modified since context eviction.
+ continue;
+ }
+
+ LOG(("CacheFileContextEvictor::WasEvicted() - evicted [pinning=%d, "
+ "mTimeStamp=%lld, lastModifiedTime=%lld]",
+ entry->mPinned, entry->mTimeStamp, lastModifiedTime));
+
+ if (entry->mPinned) {
+ *aEvictedAsPinned = true;
+ } else {
+ *aEvictedAsNonPinned = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::PersistEvictionInfoToDisk(
+ nsILoadContextInfo *aLoadContextInfo, bool aPinned)
+{
+ LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
+ "loadContextInfo=%p]", this, aLoadContextInfo));
+
+ nsresult rv;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString path;
+ file->GetNativePath(path);
+
+ 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%08x]", path.get(), 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)
+{
+ LOG(("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
+ "loadContextInfo=%p]", this, aLoadContextInfo));
+
+ nsresult rv;
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString path;
+ file->GetNativePath(path);
+
+ rv = file->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Removing file"
+ " failed! [path=%s, rv=0x%08x]", path.get(), 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<nsISimpleEnumerator> enumerator;
+ rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(enumerator));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnum = do_QueryInterface(enumerator, &rv);
+ 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, NS_LITERAL_CSTRING(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);
+ }
+
+ nsCOMPtr<nsILoadContextInfo> info;
+ if (!NS_LITERAL_CSTRING("*").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;
+ entry->mTimeStamp = lastModifiedTime;
+ mEntries.AppendElement(entry);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
+ bool aPinned,
+ nsIFile **_retval)
+{
+ nsresult rv;
+
+ nsAutoCString leafName;
+ leafName.AssignLiteral(CONTEXT_EVICTION_PREFIX);
+
+ 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('*');
+ }
+
+ nsAutoCString data64;
+ rv = Base64Encode(keyPrefix, data64);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Replace '/' with '-' since '/' cannot be part of the filename.
+ data64.ReplaceChar('/', '-');
+
+ leafName.Append(data64);
+
+ 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%08x]", 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 evicintg."));
+ return;
+ }
+
+ if (mEntries.Length() == 0) {
+ LOG(("CacheFileContextEvictor::StartEvicting() - no context to evict."));
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod(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%08x]", rv));
+ }
+
+ mEvicting = true;
+}
+
+nsresult
+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 NS_OK;
+ }
+
+ 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 NS_OK;
+ }
+
+ if (CacheIOThread::YieldAndRerun()) {
+ LOG(("CacheFileContextEvictor::EvictEntries() - Breaking loop for higher "
+ "level events."));
+ mEvicting = true;
+ return NS_OK;
+ }
+
+ 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 NS_OK;
+ }
+
+ 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.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;
+ rv = CacheIndex::HasEntry(hash, &status, &pinned);
+ // 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;
+ }
+
+ 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=%lld, lastModifiedTime=%lld]", mEntries[0]->mTimeStamp,
+ lastModifiedTime));
+ continue;
+ }
+
+ LOG(("CacheFileContextEvictor::EvictEntries - Removing entry."));
+ file->Remove(false);
+ CacheIndex::RemoveEntry(&hash);
+ }
+
+ NS_NOTREACHED("We should never get here");
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheFileContextEvictor.h b/netwerk/cache2/CacheFileContextEvictor.h
new file mode 100644
index 0000000000..f5fd49ecae
--- /dev/null
+++ b/netwerk/cache2/CacheFileContextEvictor.h
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileContextEvictor__h__
+#define CacheFileContextEvictor__h__
+
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+
+class nsIFile;
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheIndexIterator;
+
+struct CacheFileContextEvictorEntry
+{
+ nsCOMPtr<nsILoadContextInfo> mInfo;
+ bool mPinned;
+ 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);
+
+ // Returns number of contexts that are being evicted.
+ uint32_t ContextsCount();
+ // Start evicting given context.
+ nsresult AddContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
+ // 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.
+ nsresult 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.
+ nsresult 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);
+ // Once we are done with eviction for the given context, the eviction info is
+ // removed from the disk.
+ nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
+ // Tries to load all contexts from the disk. This method is called just once
+ // after startup.
+ nsresult LoadEvictInfoFromDisk();
+ nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo, bool aPinned,
+ nsIFile **_retval);
+
+ void CreateIterators();
+ void CloseIterators();
+ void StartEvicting();
+ nsresult 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<nsAutoPtr<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..1d0d576355
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -0,0 +1,4274 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsISimpleEnumerator.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsIObserverService.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsISizeOf.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/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 = 200*1024; // 200 MB
+#else
+const uint32_t kMaxCacheSizeKB = 350*1024; // 350 MB
+#endif
+
+bool
+CacheFileHandle::DispatchRelease()
+{
+ if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+ if (!ioTarget) {
+ return false;
+ }
+
+ nsresult rv =
+ ioTarget->Dispatch(NewNonOwningRunnableMethod(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=%d]", this, mRefCnt.get()));
+ NS_PRECONDITION(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_THREADSAFE
+
+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=%d, fileExists=%d, fileSize=%lld, leafName=%s, key=%s]",
+ this,
+ bool(mIsDoomed), bool(mPriority), bool(mClosed), bool(mInvalid),
+ mPinning, bool(mFileExists), 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=%d, fileExists=%d, fileSize=%lld, leafName=%s, key=%s]",
+ this, LOGSHA1(mHash),
+ bool(mIsDoomed), bool(mPriority), bool(mClosed), bool(mInvalid),
+ mPinning, bool(mFileExists), 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;
+}
+
+
+nsresult
+CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
+ bool aPriority, CacheFileHandle::PinningStatus aPinning,
+ CacheFileHandle **_retval)
+{
+ 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));
+
+ handle.forget(_retval);
+ return NS_OK;
+}
+
+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->Hash());
+ }
+}
+
+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()
+ : mMonitor("ShutdownEvent.mMonitor")
+ , mNotified(false)
+ {
+ MOZ_COUNT_CTOR(ShutdownEvent);
+ }
+
+protected:
+ ~ShutdownEvent()
+ {
+ MOZ_COUNT_DTOR(ShutdownEvent);
+ }
+
+public:
+ NS_IMETHOD Run() override
+ {
+ MonitorAutoLock mon(mMonitor);
+
+ CacheFileIOManager::gInstance->ShutdownInternal();
+
+ mNotified = true;
+ mon.Notify();
+
+ return NS_OK;
+ }
+
+ void PostAndWait()
+ {
+ MonitorAutoLock mon(mMonitor);
+
+ DebugOnly<nsresult> rv;
+ rv = CacheFileIOManager::gInstance->mIOThread->Dispatch(
+ this, CacheIOThread::WRITE); // When writes and closing of handles is done
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ PRIntervalTime const waitTime = PR_MillisecondsToInterval(1000);
+ 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 OpenFileEvent : public Runnable {
+public:
+ OpenFileEvent(const nsACString &aKey, uint32_t aFlags,
+ CacheFileIOListener *aCallback)
+ : mFlags(aFlags)
+ , mCallback(aCallback)
+ , mKey(aKey)
+ {
+ MOZ_COUNT_CTOR(OpenFileEvent);
+ mIOMan = CacheFileIOManager::gInstance;
+ }
+
+protected:
+ ~OpenFileEvent()
+ {
+ MOZ_COUNT_DTOR(OpenFileEvent);
+ }
+
+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));
+ }
+ 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:
+ ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf,
+ int32_t aCount, CacheFileIOListener *aCallback)
+ : mHandle(aHandle)
+ , mOffset(aOffset)
+ , mBuf(aBuf)
+ , mCount(aCount)
+ , mCallback(aCallback)
+ {
+ MOZ_COUNT_CTOR(ReadEvent);
+ }
+
+protected:
+ ~ReadEvent()
+ {
+ MOZ_COUNT_DTOR(ReadEvent);
+ }
+
+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);
+ }
+
+ 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:
+ WriteEvent(CacheFileHandle *aHandle, int64_t aOffset, const char *aBuf,
+ int32_t aCount, bool aValidate, bool aTruncate,
+ CacheFileIOListener *aCallback)
+ : mHandle(aHandle)
+ , mOffset(aOffset)
+ , mBuf(aBuf)
+ , mCount(aCount)
+ , mValidate(aValidate)
+ , mTruncate(aTruncate)
+ , mCallback(aCallback)
+ {
+ MOZ_COUNT_CTOR(WriteEvent);
+ }
+
+protected:
+ ~WriteEvent()
+ {
+ MOZ_COUNT_DTOR(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_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)
+ : mCallback(aCallback)
+ , mHandle(aHandle)
+ {
+ MOZ_COUNT_CTOR(DoomFileEvent);
+ }
+
+protected:
+ ~DoomFileEvent()
+ {
+ MOZ_COUNT_DTOR(DoomFileEvent);
+ }
+
+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)
+ : mCallback(aCallback)
+ {
+ MOZ_COUNT_CTOR(DoomFileByKeyEvent);
+
+ SHA1Sum sum;
+ sum.update(aKey.BeginReading(), aKey.Length());
+ sum.finish(mHash);
+
+ mIOMan = CacheFileIOManager::gInstance;
+ }
+
+protected:
+ ~DoomFileByKeyEvent()
+ {
+ MOZ_COUNT_DTOR(DoomFileByKeyEvent);
+ }
+
+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)
+ : mHandle(aHandle)
+ {
+ MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent);
+ }
+
+protected:
+ ~ReleaseNSPRHandleEvent()
+ {
+ MOZ_COUNT_DTOR(ReleaseNSPRHandleEvent);
+ }
+
+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)
+ : mHandle(aHandle)
+ , mTruncatePos(aTruncatePos)
+ , mEOFPos(aEOFPos)
+ , mCallback(aCallback)
+ {
+ MOZ_COUNT_CTOR(TruncateSeekSetEOFEvent);
+ }
+
+protected:
+ ~TruncateSeekSetEOFEvent()
+ {
+ MOZ_COUNT_DTOR(TruncateSeekSetEOFEvent);
+ }
+
+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)
+ : mHandle(aHandle)
+ , mNewName(aNewName)
+ , mCallback(aCallback)
+ {
+ MOZ_COUNT_CTOR(RenameFileEvent);
+ }
+
+protected:
+ ~RenameFileEvent()
+ {
+ MOZ_COUNT_DTOR(RenameFileEvent);
+ }
+
+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)
+ : mHandle(aHandle)
+ , mOriginAttrsHash(aOriginAttrsHash)
+ , mAnonymous(aAnonymous)
+ , mPinning(aPinning)
+ {
+ MOZ_COUNT_CTOR(InitIndexEntryEvent);
+ }
+
+protected:
+ ~InitIndexEntryEvent()
+ {
+ MOZ_COUNT_DTOR(InitIndexEntryEvent);
+ }
+
+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 and expiration time 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, &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 uint32_t *aExpirationTime)
+ : mHandle(aHandle)
+ , mHasFrecency(false)
+ , mHasExpirationTime(false)
+ {
+ MOZ_COUNT_CTOR(UpdateIndexEntryEvent);
+ if (aFrecency) {
+ mHasFrecency = true;
+ mFrecency = *aFrecency;
+ }
+ if (aExpirationTime) {
+ mHasExpirationTime = true;
+ mExpirationTime = *aExpirationTime;
+ }
+ }
+
+protected:
+ ~UpdateIndexEntryEvent()
+ {
+ MOZ_COUNT_DTOR(UpdateIndexEntryEvent);
+ }
+
+public:
+ NS_IMETHOD Run() override
+ {
+ if (mHandle->IsClosed() || mHandle->IsDoomed()) {
+ return NS_OK;
+ }
+
+ CacheIndex::UpdateEntry(mHandle->Hash(),
+ mHasFrecency ? &mFrecency : nullptr,
+ mHasExpirationTime ? &mExpirationTime : nullptr,
+ nullptr);
+ return NS_OK;
+ }
+
+protected:
+ RefPtr<CacheFileHandle> mHandle;
+ bool mHasFrecency;
+ bool mHasExpirationTime;
+ uint32_t mFrecency;
+ uint32_t mExpirationTime;
+};
+
+class MetadataWriteScheduleEvent : public Runnable
+{
+public:
+ enum EMode {
+ SCHEDULE,
+ UNSCHEDULE,
+ SHUTDOWN
+ } mMode;
+
+ RefPtr<CacheFile> mFile;
+ RefPtr<CacheFileIOManager> mIOMan;
+
+ MetadataWriteScheduleEvent(CacheFileIOManager * aManager,
+ CacheFile * aFile,
+ EMode aMode)
+ : mMode(aMode)
+ , mFile(aFile)
+ , mIOMan(aManager)
+ { }
+
+ virtual ~MetadataWriteScheduleEvent() { }
+
+ 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)
+
+CacheFileIOManager::CacheFileIOManager()
+ : mShuttingDown(false)
+ , mTreeCreated(false)
+ , mTreeCreationFailed(false)
+ , mOverLimitEvicting(false)
+ , mRemovingTrashDirs(false)
+{
+ LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
+ MOZ_COUNT_CTOR(CacheFileIOManager);
+ MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
+}
+
+CacheFileIOManager::~CacheFileIOManager()
+{
+ LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
+ MOZ_COUNT_DTOR(CacheFileIOManager);
+}
+
+// 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 = ioMan.forget();
+ 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;
+}
+
+nsresult
+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;
+
+ // 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;
+ }
+
+ return NS_OK;
+}
+
+// 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(NS_LITERAL_STRING("cache2"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // All functions return a clone.
+ ioMan->mCacheDirectory.swap(directory);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ if (profilelessDirectory) {
+ rv = profilelessDirectory->Append(NS_LITERAL_STRING("cache2"));
+ 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) {
+ mMetadataWritesTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mMetadataWritesTimer->InitWithCallback(
+ 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);
+}
+
+nsresult
+CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile * aFile)
+{
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+
+ mScheduledMetadataWrites.RemoveElement(aFile);
+
+ if (mScheduledMetadataWrites.Length() == 0 &&
+ mMetadataWritesTimer) {
+ mMetadataWritesTimer->Cancel();
+ mMetadataWritesTimer = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// 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);
+}
+
+nsresult
+CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal()
+{
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+
+ nsTArray<RefPtr<CacheFile> > files;
+ files.SwapElements(mScheduledMetadataWrites);
+ for (uint32_t i = 0; i < files.Length(); ++i) {
+ CacheFile * file = files[i];
+ file->WriteMetadataIfNeeded();
+ }
+
+ if (mMetadataWritesTimer) {
+ mMetadataWritesTimer->Cancel();
+ mMetadataWritesTimer = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileIOManager::Notify(nsITimer * aTimer)
+{
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+ MOZ_ASSERT(mMetadataWritesTimer == aTimer);
+
+ mMetadataWritesTimer = nullptr;
+
+ nsTArray<RefPtr<CacheFile> > files;
+ files.SwapElements(mScheduledMetadataWrites);
+ for (uint32_t i = 0; i < files.Length(); ++i) {
+ CacheFile * file = files[i];
+ file->WriteMetadataIfNeeded();
+ }
+
+ 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;
+ }
+
+ rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning, getter_AddRefs(handle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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%08x]", 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;
+ }
+
+ rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning, getter_AddRefs(handle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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));
+ }
+
+ rv = file->GetFileSize(&handle->mFileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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%08x]", 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) {
+ rv = file->GetFileSize(&handle->mFileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ handle->mFileExists = true;
+ } else {
+ handle->mFileSize = 0;
+ }
+
+ handle->mFile.swap(file);
+ handle.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult
+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) && NS_SUCCEEDED(rv)) {
+ LOG(("CacheFileIOManager::CloseHandleInternal() - Removing file from "
+ "disk"));
+
+ aHandle->mFile->Remove(false);
+ }
+
+ 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);
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+CacheFileIOManager::Read(CacheFileHandle *aHandle, int64_t aOffset,
+ char *aBuf, int32_t aCount,
+ CacheFileIOListener *aCallback)
+{
+ LOG(("CacheFileIOManager::Read() [handle=%p, offset=%lld, 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=%lld, 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=%lld, 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=%lld, 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;
+ }
+
+ // Check whether this write would cause critical low disk space.
+ if (aHandle->mFileSize < aOffset + aCount) {
+ int64_t freeSpace = -1;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() "
+ "failed! [rv=0x%08x]", rv));
+ } else {
+ uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
+ if (freeSpace - aOffset - aCount + aHandle->mFileSize < limit) {
+ LOG(("CacheFileIOManager::WriteInternal() - Low free space, refusing "
+ "to write! [freeSpace=%lld, limit=%u]", 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, &newSizeInK);
+
+ if (oldSizeInK < newSizeInK) {
+ EvictIfOverLimitInternal();
+ }
+ }
+ }
+
+ 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%08x]", 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=%lld, "
+ "EOFPos=%lld, 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.
+ nsAutoCString key;
+ metadata->GetKey(key);
+
+ RefPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(key, &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;
+ if (NS_FAILED(metadata->GetFetchCount(&fetchCount))) {
+ fetchCount = 0;
+ }
+ uint32_t expirationTime;
+ if (NS_FAILED(metadata->GetExpirationTime(&expirationTime))) {
+ expirationTime = 0;
+ }
+ uint32_t lastModified;
+ if (NS_FAILED(metadata->GetLastModified(&lastModified))) {
+ lastModified = 0;
+ }
+
+ // Call directly on the callback.
+ aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, fetchCount,
+ lastModified, expirationTime, metadata->Pinned());
+
+ return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
+ int64_t aTruncatePos,
+ int64_t aEOFPos)
+{
+ LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, "
+ "truncatePos=%lld, EOFPos=%lld]", 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;
+ }
+
+ // Check whether this operation would cause critical low disk space.
+ if (aHandle->mFileSize < aEOFPos) {
+ int64_t freeSpace = -1;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() - "
+ "GetDiskSpaceAvailable() failed! [rv=0x%08x]", rv));
+ } else {
+ uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
+ if (freeSpace - aEOFPos + aHandle->mFileSize < limit) {
+ LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() - Low free space"
+ ", refusing to write! [freeSpace=%lld, limit=%u]", 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, &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%08x]", 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(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%08x]", rv));
+ } else {
+ UpdateSmartCacheSize(freeSpace);
+ }
+
+ uint32_t cacheUsage;
+ rv = CacheIndex::GetCacheSize(&cacheUsage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10;
+ 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=%lld, freeSpaceLimit=%u]", cacheUsage, cacheLimit,
+ freeSpace, freeSpaceLimit));
+ return NS_OK;
+ }
+
+ LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded "
+ "limit. Starting overlimit eviction. [cacheSize=%u, limit=%u]",
+ cacheUsage, cacheLimit));
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod(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 = -1;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Do not change smart size.
+ LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - "
+ "GetDiskSpaceAvailable() failed! [rv=0x%08x]", rv));
+ } else {
+ UpdateSmartCacheSize(freeSpace);
+ }
+
+ uint32_t cacheUsage;
+ rv = CacheIndex::GetCacheSize(&cacheUsage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10;
+ uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
+
+ if (cacheUsage > cacheLimit) {
+ LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over "
+ "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit));
+ } else if (freeSpace != 1 && freeSpace < freeSpaceLimit) {
+ LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Free space under "
+ "limit. [freeSpace=%lld, freeSpaceLimit=%u]", freeSpace,
+ freeSpaceLimit));
+ } else {
+ LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size and "
+ "free space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
+ "freeSpace=%lld, freeSpaceLimit=%u]", cacheUsage, cacheLimit,
+ freeSpace, freeSpaceLimit));
+ 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%08x]", 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%08x]", 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;
+ uint32_t expTime = nsICacheEntry::NO_EXPIRATION_TIME;
+ rv = CacheIndex::UpdateEntry(&hash, &frecency, &expTime, 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;
+ }
+ }
+ }
+
+ NS_NOTREACHED("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(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:
+ 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(NS_LITERAL_CSTRING(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)
+{
+ 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>
+ (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo, aPinned);
+
+ rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
+{
+ 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;
+ }
+ }
+
+ // 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];
+
+ if (aLoadContextInfo) {
+ bool equals;
+ rv = CacheFileUtils::KeyMatchesLoadContextInfo(handle->Key(),
+ aLoadContextInfo,
+ &equals);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
+ "handle! [handle=%p, key=%s]", handle, handle->Key().get()));
+ MOZ_CRASH("Unexpected error!");
+ }
+
+ if (!equals) {
+ 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);
+
+ 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;
+ ev = NewRunnableMethod(
+ 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;
+}
+
+nsresult
+CacheFileIOManager::CacheIndexStateChangedInternal()
+{
+ if (mShuttingDown) {
+ // ignore notification during shutdown
+ return NS_OK;
+ }
+
+ if (!mContextEvictor) {
+ return NS_OK;
+ }
+
+ mContextEvictor->CacheIndexStateChanged();
+ return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::TrashDirectory(nsIFile *aFile)
+{
+ nsAutoCString path;
+ aFile->GetNativePath(path);
+ LOG(("CacheFileIOManager::TrashDirectory() [file=%s]", path.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%08x]",
+ 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<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
+ MOZ_ASSERT(ioTarget);
+
+ rv = timer->SetTarget(ioTarget);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = timer->InitWithFuncCallback(CacheFileIOManager::OnTrashTimer, nullptr,
+ kRemoveTrashStartDelay - elapsed,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTrashTimer.swap(timer);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod(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);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = mTrashDir->GetDirectoryEntries(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv)) {
+ mTrashDirEnumerator = do_QueryInterface(enumerator, &rv);
+ 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
+ } else {
+ 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()) {
+ nsAutoCString path;
+ file->GetNativePath(path);
+ 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]", path.get()));
+ }
+ }
+ file->Remove(isDir);
+ }
+ }
+
+ NS_NOTREACHED("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<nsISimpleEnumerator> iter;
+ rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ nsCOMPtr<nsISupports> elem;
+
+ while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+ rv = iter->GetNext(getter_AddRefs(elem));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
+ if (!file) {
+ continue;
+ }
+
+ 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, NS_LITERAL_CSTRING(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=%llx, "
+ "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 uint32_t *aExpirationTime)
+{
+ LOG(("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
+ "expirationTime=%s]", aHandle,
+ aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
+ aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).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, aExpirationTime);
+ 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(NS_LITERAL_CSTRING(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(NS_LITERAL_CSTRING(DOOMED_DIR));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(NS_LITERAL_CSTRING("dummyleaf"));
+ 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<nsISimpleEnumerator> 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();
+
+ if (!CacheObserver::CacheFSReported()) {
+ uint32_t fsType = 4; // Other OS
+
+#ifdef XP_WIN
+ nsAutoString target;
+ nsresult rv = mCacheDirectory->GetTarget(target);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ wchar_t volume_path[MAX_PATH + 1] = { 0 };
+ if (!::GetVolumePathNameW(target.get(),
+ volume_path,
+ mozilla::ArrayLength(volume_path))) {
+ return NS_OK;
+ }
+
+ wchar_t fsName[6] = { 0 };
+ if (!::GetVolumeInformationW(volume_path, nullptr, 0, nullptr, nullptr,
+ nullptr, fsName,
+ mozilla::ArrayLength(fsName))) {
+ return NS_OK;
+ }
+
+ if (wcscmp(fsName, L"NTFS") == 0) {
+ fsType = 0;
+ } else if (wcscmp(fsName, L"FAT32") == 0) {
+ fsType = 1;
+ } else if (wcscmp(fsName, L"FAT") == 0) {
+ fsType = 2;
+ } else {
+ fsType = 3;
+ }
+#endif
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_FS_TYPE, fsType);
+ CacheObserver::SetCacheFSReported();
+ }
+
+ 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%08x", 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%08x", 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()) {
+ nsAutoCString path;
+ file->GetNativePath(path);
+ LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s",
+ path.get()));
+ }
+
+ rv = file->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("CacheFileIOManager::SyncRemoveDir() - Removing failed! [rv=0x%08x]",
+ 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%08x]",
+ 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 uint32_t availKB)
+{
+ uint32_t maxSize = kMaxCacheSizeKB;
+
+ if (availKB > 100 * 1024 * 1024) {
+ return maxSize; // skip computing if we're over 100 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);
+
+ // .5% of space above 25 GB
+ if (avail10MBs > 2500) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
+ avail10MBs = 2500;
+ }
+ // 1% of space between 7GB -> 25 GB
+ if (avail10MBs > 700) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
+ avail10MBs = 700;
+ }
+ // 5% of space between 500 MB -> 7 GB
+ if (avail10MBs > 50) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
+ 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.
+
+ // 20% of space up to 500 MB (10 MB min)
+ sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
+#else
+ // 40% of space up to 500 MB (50 MB min)
+ sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
+#endif
+
+ return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
+}
+
+nsresult
+CacheFileIOManager::UpdateSmartCacheSize(int64_t aFreeSpace)
+{
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ nsresult rv;
+
+ if (!CacheObserver::UseNewCache()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ 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%08x]", rv));
+ return rv;
+ }
+
+ mLastSmartSizeTime = TimeStamp::NowLoRes();
+
+ uint32_t smartSize = SmartCacheSize(static_cast<uint32_t>(aFreeSpace / 1024) +
+ cacheUsage);
+
+ if (smartSize == (CacheObserver::DiskCacheCapacity() >> 10)) {
+ // Smart size has not changed.
+ return NS_OK;
+ }
+
+ CacheObserver::SetDiskCacheCapacity(smartSize << 10);
+
+ 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)
+ : mMonitor("SizeOfHandlesRunnable.mMonitor")
+ , mMallocSizeOf(mallocSizeOf)
+ , mHandles(handles)
+ , mSpecialHandles(specialHandles)
+ {
+ }
+
+ 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..5ac812da5d
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -0,0 +1,489 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "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 : 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;
+ int64_t 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);
+ nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority,
+ CacheFileHandle::PinningStatus aPinning, CacheFileHandle **_retval);
+ 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)
+ {
+ NS_NOTREACHED("HandleHashKey copy constructor is forbidden!");
+ }
+ ~HandleHashKey()
+ {
+ MOZ_COUNT_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 : public nsITimerCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ 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);
+
+ static nsresult InitIndexEntry(CacheFileHandle *aHandle,
+ OriginAttrsHash aOriginAttrsHash,
+ bool aAnonymous,
+ bool aPinning);
+ static nsresult UpdateIndexEntry(CacheFileHandle *aHandle,
+ const uint32_t *aFrecency,
+ const uint32_t *aExpirationTime);
+
+ 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();
+ nsresult 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);
+ nsresult 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);
+
+ 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);
+ nsresult UnscheduleMetadataWriteInternal(CacheFile * aFile);
+ nsresult ShutdownMetadataWriteSchedulingInternal();
+
+ static nsresult CacheIndexStateChanged();
+ nsresult 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;
+ 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..26ca575370
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.cpp
@@ -0,0 +1,723 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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()
+{
+ NS_PRECONDITION(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) {
+ 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(mozilla::net::CacheFileChunkListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+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));
+ MOZ_COUNT_CTOR(CacheFileInputStream);
+
+ if (mAlternativeData) {
+ mPos = mFile->mAltDataOffset;
+ }
+}
+
+CacheFileInputStream::~CacheFileInputStream()
+{
+ LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this));
+ MOZ_COUNT_DTOR(CacheFileInputStream);
+ 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%08x]", this, 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%08x]", this, 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=%lld, rv=0x%08x]",
+ this, *_retval, 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;
+
+ *_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%08x]", this, mStatus));
+
+ if NS_FAILED(mStatus)
+ return mStatus;
+
+ return NS_OK;
+ }
+
+ EnsureCorrectChunk(false);
+
+ while (true) {
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ if (!mChunk) {
+ if (mListeningForChunk == -1) {
+ return NS_OK;
+ } else {
+ 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) {
+ if (hnd.DataSize() != mChunk->DataSize()) {
+ // New data was written to this chunk while the lock was released.
+ continue;
+ }
+
+ // The last chunk is released after the caller closes this stream.
+ EnsureCorrectChunk(false);
+
+ if (mChunk && aCount) {
+ // We have the next chunk! Go on.
+ continue;
+ }
+ }
+ }
+
+ if (mClosed) {
+ // The stream was closed from aWriter, do the cleanup.
+ CleanUp();
+ }
+
+ rv = NS_OK;
+ } else {
+ if (mFile->OutputStreamExists(mAlternativeData)) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ rv = NS_OK;
+ }
+ }
+
+ break;
+ }
+
+ LOG(("CacheFileInputStream::ReadSegments() [this=%p, rv=0x%08x, retval=%d]",
+ this, 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%08x]",
+ this, aStatus));
+
+ return CloseWithStatusLocked(aStatus);
+}
+
+nsresult
+CacheFileInputStream::CloseWithStatusLocked(nsresult aStatus)
+{
+ LOG(("CacheFileInputStream::CloseWithStatusLocked() [this=%p, "
+ "aStatus=0x%08x]", this, 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 NS_OK;
+ }
+
+ mClosed = true;
+ mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
+
+ if (!mInReadSegments) {
+ CleanUp();
+ }
+
+ return NS_OK;
+}
+
+void
+CacheFileInputStream::CleanUp()
+{
+ MOZ_ASSERT(!mInReadSegments);
+ MOZ_ASSERT(mClosed);
+
+ if (mChunk) {
+ ReleaseChunk();
+ }
+
+ // TODO propagate error from input stream to other streams ???
+
+ MaybeNotifyListener();
+
+ mFile->ReleaseOutsideLock(mCacheEntryHandle.forget());
+}
+
+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=%lld]",
+ 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=%lld]", this, mPos));
+ return NS_OK;
+}
+
+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=%lld]", this, *_retval));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::SetEOF()
+{
+ MOZ_ASSERT(false, "Don't call SetEOF on cache input stream");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// 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%08x, "
+ "idx=%d, chunk=%p]", this, 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=%lld]",
+ 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;
+ }
+ else {
+ 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(mChunk.forget());
+}
+
+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;
+ } else {
+ 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 %lld [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%08x]", this, chunkIdx, 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() - mPos;
+ if (retval <= 0 && NS_FAILED(mChunk->GetStatus())) {
+ CloseWithStatusLocked(mChunk->GetStatus());
+ }
+
+ LOG(("CacheFileInputStream::CanRead() [this=%p, canRead=%lld]",
+ 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 = do_GetMainThread();
+ }
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> asyncCallback =
+ NS_NewInputStreamReadyEvent(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%08x, mChunk=%p, mListeningForChunk=%lld, "
+ "mWaitingForUpdate=%d]", this, mCallback.get(), mClosed, 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..ab632f3687
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.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 CacheFileInputStream__h__
+#define CacheFileInputStream__h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.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
+
+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; };
+
+private:
+ virtual ~CacheFileInputStream();
+
+ nsresult 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..3814b4c878
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -0,0 +1,1095 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "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()));
+
+ MOZ_COUNT_CTOR(CacheFileMetadata);
+ 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()));
+
+ MOZ_COUNT_CTOR(CacheFileMetadata);
+ 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));
+
+ MOZ_COUNT_CTOR(CacheFileMetadata);
+ memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
+}
+
+CacheFileMetadata::~CacheFileMetadata()
+{
+ LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this));
+
+ MOZ_COUNT_DTOR(CacheFileMetadata);
+ 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;
+}
+
+nsresult
+CacheFileMetadata::GetKey(nsACString &_retval)
+{
+ _retval = mKey;
+ return NS_OK;
+}
+
+nsresult
+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 NS_OK;
+ }
+
+ 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=%lld]", this, size));
+
+ InitEmptyMetadata();
+ aListener->OnMetadataRead(NS_OK);
+ return NS_OK;
+ }
+
+ // 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=%lld, filesize=%lld [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%08x]",
+ this, rv));
+
+ mListener = nullptr;
+ InitEmptyMetadata();
+ aListener->OnMetadataRead(NS_OK);
+ return NS_OK;
+ }
+
+ return 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);
+ 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++;
+ 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%08x]", this, 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) {
+ // Point to the value part
+ const char *value = data + strlen(data) + 1;
+ MOZ_ASSERT(value < limit, "Metadata elements corrupted");
+ if (strcmp(data, aKey) == 0) {
+ LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
+ this, aKey));
+ return value;
+ }
+
+ // Skip value part
+ data = value + strlen(value) + 1;
+ }
+ MOZ_ASSERT(data == limit, "Metadata elements corrupted");
+ 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;
+}
+
+nsresult
+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");
+
+ return NS_OK;
+}
+
+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::AddFlags(uint32_t aFlags)
+{
+ MarkDirty(false);
+ mMetaHdr.mFlags |= aFlags;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::RemoveFlags(uint32_t aFlags)
+{
+ MarkDirty(false);
+ mMetaHdr.mFlags &= ~aFlags;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetFlags(uint32_t *_retval)
+{
+ *_retval = mMetaHdr.mFlags;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime)
+{
+ LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
+ this, aExpirationTime));
+
+ MarkDirty(false);
+ mMetaHdr.mExpirationTime = aExpirationTime;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetExpirationTime(uint32_t *_retval)
+{
+ *_retval = mMetaHdr.mExpirationTime;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::SetFrecency(uint32_t aFrecency)
+{
+ LOG(("CacheFileMetadata::SetFrecency() [this=%p, frecency=%f]",
+ this, (double)aFrecency));
+
+ MarkDirty(false);
+ mMetaHdr.mFrecency = aFrecency;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetFrecency(uint32_t *_retval)
+{
+ *_retval = mMetaHdr.mFrecency;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetLastModified(uint32_t *_retval)
+{
+ *_retval = mMetaHdr.mLastModified;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetLastFetched(uint32_t *_retval)
+{
+ *_retval = mMetaHdr.mLastFetched;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetFetchCount(uint32_t *_retval)
+{
+ *_retval = mMetaHdr.mFetchCount;
+ return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::OnFetched()
+{
+ MarkDirty(false);
+
+ mMetaHdr.mLastFetched = NOW_SECONDS();
+ ++mMetaHdr.mFetchCount;
+ return NS_OK;
+}
+
+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%08x]",
+ this, aHandle, 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%08x]",
+ this, aHandle, 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%08x]", this, 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);
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_CACHE_METADATA_FIRST_READ_SIZE, mBufSize);
+ } 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=%lld]", 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=%lld]", 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%08x]", this, rv));
+
+ InitEmptyMetadata();
+
+ mListener.swap(listener);
+ listener->OnMetadataRead(NS_OK);
+ return NS_OK;
+ }
+
+ return NS_OK;
+ }
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_METADATA_SIZE,
+ 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);
+ n += mallocSizeOf(mWriteBuf);
+ // 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..97a0fff575
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.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 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 "nsAutoPtr.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 : 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);
+
+ nsresult GetKey(nsACString &_retval);
+
+ nsresult 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::NeckoOriginAttributes 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);
+ nsresult Visit(nsICacheEntryMetaDataVisitor *aVisitor);
+
+ CacheHash::Hash16_t GetHash(uint32_t aIndex);
+ nsresult SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash);
+
+ nsresult AddFlags(uint32_t aFlags);
+ nsresult RemoveFlags(uint32_t aFlags);
+ nsresult GetFlags(uint32_t *_retval);
+ nsresult SetExpirationTime(uint32_t aExpirationTime);
+ nsresult GetExpirationTime(uint32_t *_retval);
+ nsresult SetFrecency(uint32_t aFrecency);
+ nsresult GetFrecency(uint32_t *_retval);
+ nsresult GetLastModified(uint32_t *_retval);
+ nsresult GetLastFetched(uint32_t *_retval);
+ nsresult GetFetchCount(uint32_t *_retval);
+ // Called by upper layers to indicate the entry this metadata belongs
+ // with has been fetched, i.e. delivered to the consumer.
+ nsresult 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::NeckoOriginAttributes 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..a3d414b8fa
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -0,0 +1,483 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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()
+{
+ NS_PRECONDITION(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(mozilla::net::CacheFileChunkListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+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));
+ MOZ_COUNT_CTOR(CacheFileOutputStream);
+
+ if (mAlternativeData) {
+ mPos = mFile->mAltDataOffset;
+ }
+}
+
+CacheFileOutputStream::~CacheFileOutputStream()
+{
+ LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this));
+ MOZ_COUNT_DTOR(CacheFileOutputStream);
+}
+
+// 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%08x]", this, 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, failing and "
+ "dooming the entry. [this=%p]", this));
+
+ mFile->DoomLocked(nullptr);
+ CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ // We use 64-bit offset when accessing the file, unfortunatelly 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 while it "
+ "isn't too big according to CacheObserver::EntryIsTooBig(). Failing "
+ "and dooming the entry. [this=%p]", this));
+
+ mFile->DoomLocked(nullptr);
+ 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%08x]",
+ this, aStatus));
+
+ return CloseWithStatusLocked(aStatus);
+}
+
+nsresult
+CacheFileOutputStream::CloseWithStatusLocked(nsresult aStatus)
+{
+ LOG(("CacheFileOutputStream::CloseWithStatusLocked() [this=%p, "
+ "aStatus=0x%08x]", this, 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=%lld]",
+ 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=%lld]", this, mPos));
+ return NS_OK;
+}
+
+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=%lld]", this, *_retval));
+ 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;
+}
+
+// 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()));
+
+ mFile->ReleaseOutsideLock(mChunk.forget());
+}
+
+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;
+ }
+ else {
+ 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%08x]", this, chunkIdx, 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 = do_GetMainThread();
+ }
+ }
+
+ 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..c283d702b6
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.h
@@ -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/. */
+
+#ifndef CacheFileOutputStream__h__
+#define CacheFileOutputStream__h__
+
+#include "nsIAsyncOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.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
+
+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..d43e958bff
--- /dev/null
+++ b/netwerk/cache2/CacheFileUtils.cpp
@@ -0,0 +1,575 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsAutoPtr.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)
+ // Initialize attributes to their default values
+ , originAttribs(0, false)
+ , isAnonymous(false)
+ // Initialize the cache key to a zero length by default
+ , lastTag(0)
+ {
+ }
+
+private:
+ // Results
+ NeckoOriginAttributes 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
+ static bool TagChar(const char aChar)
+ {
+ return aChar >= ' ' && aChar <= '~';
+ }
+
+ 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
+ if (!ReadInteger(&originAttribs.mAppId)) {
+ 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 nsCSubstring &aKey,
+ nsCSubstring *aIdEnhance,
+ nsCSubstring *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.
+ */
+
+ NeckoOriginAttributes 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, nsCSubstring const & 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_LITERAL_CSTRING(","), NS_LITERAL_CSTRING(",,"));
+ 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)
+{}
+
+ValidityPair&
+ValidityPair::operator=(const ValidityPair& aOther)
+{
+ mOffset = aOther.mOffset;
+ mLen = aOther.mLen;
+ return *this;
+}
+
+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: %u", 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();
+ }
+ }
+}
+
+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;
+ }
+
+ *_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..3371c3eb56
--- /dev/null
+++ b/netwerk/cache2/CacheFileUtils.h
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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;
+class nsACString;
+
+namespace mozilla {
+namespace net {
+namespace CacheFileUtils {
+
+extern const char *kAltDataKey;
+
+already_AddRefed<nsILoadContextInfo>
+ParseKey(const nsCSubstring &aKey,
+ nsCSubstring *aIdEnhance = nullptr,
+ nsCSubstring *aURISpec = nullptr);
+
+void
+AppendKeyPrefix(nsILoadContextInfo *aInfo, nsACString &_retval);
+
+void
+AppendTagWithValue(nsACString & aTarget, char const aTag, nsCSubstring const & 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);
+
+ // 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];
+};
+
+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..1f816e3471
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.cpp
@@ -0,0 +1,206 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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;
+
+// length = PL_strlen(key);
+ /* 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); MOZ_FALLTHROUGH;
+ case 10: c += (uint32_t(k[9])<<16); MOZ_FALLTHROUGH;
+ case 9 : c += (uint32_t(k[8])<<8); MOZ_FALLTHROUGH;
+ /* the low-order byte of c is reserved for the length */
+ case 8 : b += (uint32_t(k[7])<<24); MOZ_FALLTHROUGH;
+ case 7 : b += (uint32_t(k[6])<<16); MOZ_FALLTHROUGH;
+ case 6 : b += (uint32_t(k[5])<<8); MOZ_FALLTHROUGH;
+ case 5 : b += k[4]; MOZ_FALLTHROUGH;
+ case 4 : a += (uint32_t(k[3])<<24); MOZ_FALLTHROUGH;
+ case 3 : a += (uint32_t(k[2])<<16); MOZ_FALLTHROUGH;
+ case 2 : a += (uint32_t(k[1])<<8); MOZ_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; MOZ_FALLTHROUGH;
+ case 2: mBuf += data[1] << 8; MOZ_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..2af3fed808
--- /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() {}
+
+ 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..b96f03216c
--- /dev/null
+++ b/netwerk/cache2/CacheIOThread.cpp
@@ -0,0 +1,646 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsThreadUtils.h"
+#include "mozilla/IOInterposer.h"
+
+#ifdef XP_WIN
+#include <windows.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::ID 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
+}
+
+} // anon
+
+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
+ typedef BOOL(WINAPI* TCancelSynchronousIo)(HANDLE hThread);
+ TCancelSynchronousIo mCancelSynchronousIo;
+ // 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()
+ : mCancelSynchronousIo(NULL)
+ , mThread(NULL)
+ , mEvent(NULL)
+{
+ HMODULE kernel32_dll = GetModuleHandle("kernel32.dll");
+ if (!kernel32_dll) {
+ return;
+ }
+
+ FARPROC ptr = GetProcAddress(kernel32_dll, "CancelSynchronousIo");
+ if (!ptr) {
+ return;
+ }
+
+ mCancelSynchronousIo = reinterpret_cast<TCancelSynchronousIo>(ptr);
+
+ mEvent = ::CreateEvent(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
+ BOOL result = ::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 = mCancelSynchronousIo(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() { }
+BlockingIOWatcher::~BlockingIOWatcher() { }
+void BlockingIOWatcher::InitThread() { }
+void BlockingIOWatcher::WatchAndCancel(Monitor&) { }
+void BlockingIOWatcher::NotifyOperationDone() { }
+
+#endif
+
+} // 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)
+#ifdef DEBUG
+, mInsideLoop(true)
+#endif
+{
+ for (uint32_t i = 0; i < LAST_LEVEL; ++i) {
+ mQueueLength[i] = 0;
+ }
+
+ sSelf = this;
+}
+
+CacheIOThread::~CacheIOThread()
+{
+ if (mXPCOMThread) {
+ nsIThread *thread = mXPCOMThread;
+ thread->Release();
+ }
+
+ sSelf = nullptr;
+#ifdef DEBUG
+ for (uint32_t level = 0; level < LAST_LEVEL; ++level) {
+ MOZ_ASSERT(!mEventQueue[level].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);
+
+ 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)
+{
+ PR_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();
+
+ // This creates nsThread for this PRThread
+ nsCOMPtr<nsIThread> xpcomThread = NS_GetCurrentThread();
+
+ 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);
+
+ 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;
+ }
+
+ lock.Wait(PR_INTERVAL_NO_TIMEOUT);
+
+ } 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;
+ events.SwapElements(mEventQueue[aLevel]);
+ EventQueue::size_type length = events.Length();
+
+ mCurrentlyExecutingLevel = aLevel;
+
+ bool returnEvents = false;
+ bool reportTelementry = 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 (reportTelementry) {
+ reportTelementry = false;
+ CacheIOTelemetry::Report(aLevel, length);
+ }
+
+ // Drop any previous flagging, only an event on the current level may set
+ // this flag.
+ mRerunCurrentEvent = false;
+
+ events[index]->Run();
+
+ MOZ_ASSERT(mBlockingIOWatcher);
+ mBlockingIOWatcher->NotifyOperationDone();
+
+ if (mRerunCurrentEvent) {
+ // The event handler yields to higher priority events and wants to rerun.
+ returnEvents = true;
+ break;
+ }
+
+ --mQueueLength[aLevel];
+
+ // Release outside the lock.
+ events[index] = nullptr;
+ }
+ }
+
+ if (returnEvents)
+ mEventQueue[aLevel].InsertElementsAt(0, events.Elements() + index, length - index);
+}
+
+bool CacheIOThread::EventsPending(uint32_t aLastLevel)
+{
+ return mLowestLevelWaiting < aLastLevel || mHasXPCOMEvents;
+}
+
+NS_IMETHODIMP CacheIOThread::OnDispatchedEvent(nsIThreadInternal *thread)
+{
+ 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;
+ n += mallocSizeOf(mThread);
+ for (uint32_t level = 0; level < LAST_LEVEL; ++level) {
+ n += mEventQueue[level].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..ea71f7df0a
--- /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 "nsAutoPtr.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;
+}
+
+class CacheIOThread : 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);
+
+ /**
+ * 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;
+#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..4525bbe6d6
--- /dev/null
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -0,0 +1,3810 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsISimpleEnumerator.h"
+#include "nsIDirectoryEnumerator.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/Telemetry.h"
+#include "mozilla/Unused.h"
+
+
+#define kMinUnwrittenChanges 300
+#define kMinDumpInterval 20000 // in milliseconds
+#define kMaxBufSize 16384
+#define kIndexVersion 0x00000003
+#define kUpdateIndexStartDelay 50000 // in milliseconds
+
+#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
+
+/**
+ * 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;
+ 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);
+ mIndex->AddRecordToIterators(entry->mRec);
+ } else if (!entry && mOldRecord) {
+ mIndex->mFrecencyArray.RemoveRecord(mOldRecord);
+ mIndex->RemoveRecordFromIterators(mOldRecord);
+ } else if (entry && mOldRecord) {
+ if (entry->mRec != mOldRecord) {
+ // record has a different address, we have to replace it
+ mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec);
+
+ if (entry->mRec->mFrecency == mOldFrecency) {
+ // If frecency hasn't changed simply replace the pointer
+ mIndex->mFrecencyArray.ReplaceRecord(mOldRecord, entry->mRec);
+ } else {
+ // Remove old pointer and insert the new one at the end of the array
+ mIndex->mFrecencyArray.RemoveRecord(mOldRecord);
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec);
+ }
+ } else if (entry->mRec->mFrecency != mOldFrecency) {
+ // Move the element at the end of the array
+ mIndex->mFrecencyArray.RemoveRecord(entry->mRec);
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec);
+ }
+ } 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);
+ }
+ MOZ_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 : 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() {}
+
+ 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_THREADSAFE
+
+
+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)
+{
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::CacheIndex [this=%p]", this));
+ MOZ_COUNT_CTOR(CacheIndex);
+ MOZ_ASSERT(!gInstance, "multiple CacheIndex instances!");
+}
+
+CacheIndex::~CacheIndex()
+{
+ sLock.AssertCurrentThreadOwns();
+ LOG(("CacheIndex::~CacheIndex [this=%p]", this));
+ MOZ_COUNT_DTOR(CacheIndex);
+
+ 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 = idx.forget();
+ 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%08x]", rv));
+ i++;
+ }
+ }
+
+ index->mShuttingDown = true;
+
+ if (index->mState == READY) {
+ return NS_OK; // nothing to do
+ }
+
+ nsCOMPtr<nsIRunnable> event;
+ event = NewRunnableMethod(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 = 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);
+ MOZ_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=%llx, 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 (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) {
+ 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 uint32_t *aExpirationTime,
+ const uint32_t *aSize)
+{
+ LOG(("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, "
+ "frecency=%s, expirationTime=%s, size=%s]", LOGSHA1(aHash),
+ aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
+ aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).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 (!HasEntryChanged(entry, aFrecency, aExpirationTime, aSize)) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(entry->IsFresh());
+ MOZ_ASSERT(entry->IsInitialized());
+ entry->MarkDirty();
+
+ if (aFrecency) {
+ entry->SetFrecency(*aFrecency);
+ }
+
+ if (aExpirationTime) {
+ entry->SetExpirationTime(*aExpirationTime);
+ }
+
+ 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_NOT_AVAILABLE;
+ }
+
+ // 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 (aExpirationTime) {
+ updated->SetExpirationTime(*aExpirationTime);
+ }
+
+ 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(NS_LITERAL_CSTRING(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%08x]", 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, bool *_pinned)
+{
+ 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, _pinned);
+}
+
+// static
+nsresult
+CacheIndex::HasEntry(const SHA1Sum::Hash &hash, EntryStatus *_retval, bool *_pinned)
+{
+ StaticMutexAutoLock lock(sLock);
+
+ RefPtr<CacheIndex> index = gInstance;
+
+ if (!index) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!index->IsIndexUsable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (_pinned) {
+ *_pinned = false;
+ }
+
+ const CacheIndexEntry *entry = nullptr;
+
+ switch (index->mState) {
+ case READING:
+ case WRITING:
+ entry = index->mPendingUpdates.GetEntry(hash);
+ MOZ_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 (_pinned && entry->IsPinned()) {
+ *_pinned = true;
+ }
+ }
+ }
+
+ 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;
+ }
+
+ 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 (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 from frecency "
+ "array [hash=%08x%08x%08x%08x%08x, cnt=%u, frecency=%u]",
+ LOGSHA1(&hash), *aCnt, foundRecord->mFrecency));
+
+ 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;
+ }
+
+ if (!aInfo) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aSize = 0;
+ *aCount = 0;
+
+ for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexRecord *record = iter.Get();
+ if (!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([]() -> 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 %p", *_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=%llx, "
+ "anonymous=%d; actual values: originAttrsHash=%llx, 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 uint32_t *aExpirationTime,
+ const uint32_t *aSize)
+{
+ if (aFrecency && *aFrecency != aEntry->GetFrecency()) {
+ return true;
+ }
+
+ if (aExpirationTime && *aExpirationTime != aEntry->GetExpirationTime()) {
+ 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(*update->Hash());
+ 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(NS_LITERAL_CSTRING(TEMP_INDEX_NAME),
+ CacheFileIOManager::SPECIAL_FILE |
+ CacheFileIOManager::CREATE,
+ mIndexFileOpener);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08x]", 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);
+
+ 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%08x]", 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;
+}
+
+nsresult
+CacheIndex::RemoveFile(const nsACString &aName)
+{
+ MOZ_ASSERT(mState == SHUTDOWN);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(aName, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheIndex::RemoveFile() - Cannot remove old entry file from disk."
+ "[name=%s]", PromiseFlatCString(aName).get()));
+ NS_WARNING("Cannot remove old entry file from the disk");
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+CacheIndex::RemoveAllIndexFiles()
+{
+ LOG(("CacheIndex::RemoveAllIndexFiles()"));
+ RemoveFile(NS_LITERAL_CSTRING(INDEX_NAME));
+ RemoveJournalAndTempFile();
+}
+
+void
+CacheIndex::RemoveJournalAndTempFile()
+{
+ LOG(("CacheIndex::RemoveJournalAndTempFile()"));
+ RemoveFile(NS_LITERAL_CSTRING(TEMP_INDEX_NAME));
+ RemoveFile(NS_LITERAL_CSTRING(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(NS_LITERAL_CSTRING(TEMP_INDEX_NAME));
+
+ nsCOMPtr<nsIFile> indexFile;
+ rv = GetFile(NS_LITERAL_CSTRING(INDEX_NAME), getter_AddRefs(indexFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> logFile;
+ rv = GetFile(NS_LITERAL_CSTRING(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(NS_LITERAL_CSTRING(INDEX_NAME),
+ CacheFileIOManager::SPECIAL_FILE |
+ CacheFileIOManager::OPEN,
+ mIndexFileOpener);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
+ "failed [rv=0x%08x, file=%s]", rv, INDEX_NAME));
+ FinishRead(false);
+ return;
+ }
+
+ mJournalFileOpener = new FileOpenHelper(this);
+ rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(JOURNAL_NAME),
+ CacheFileIOManager::SPECIAL_FILE |
+ CacheFileIOManager::OPEN,
+ mJournalFileOpener);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
+ "failed [rv=0x%08x, file=%s]", rv, JOURNAL_NAME));
+ FinishRead(false);
+ }
+
+ mTmpFileOpener = new FileOpenHelper(this);
+ rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(TEMP_INDEX_NAME),
+ CacheFileIOManager::SPECIAL_FILE |
+ CacheFileIOManager::OPEN,
+ mTmpFileOpener);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
+ "failed [rv=0x%08x, file=%s]", 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%08x]", 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);
+ }
+
+ 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%08x]", rv));
+ FinishRead(false);
+ return;
+ } else {
+ 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%08x]", 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%08x]", rv));
+ FinishRead(false);
+ return;
+ } else {
+ 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(NS_LITERAL_CSTRING(TEMP_INDEX_NAME));
+ RemoveFile(NS_LITERAL_CSTRING(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);
+
+ nsresult rv;
+
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+ MOZ_ASSERT(ioTarget);
+
+ rv = timer->SetTarget(ioTarget);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = timer->InitWithFuncCallback(CacheIndex::DelayedUpdate, nullptr,
+ aDelay, nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mUpdateTimer.swap(timer);
+ return NS_OK;
+}
+
+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(NS_LITERAL_CSTRING(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;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = file->GetDirectoryEntries(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDirEnumerator = do_QueryInterface(enumerator, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void
+CacheIndex::InitEntryFromDiskData(CacheIndexEntry *aEntry,
+ CacheFileMetadata *aMetaData,
+ int64_t aFileSize)
+{
+ aEntry->InitNew();
+ aEntry->MarkDirty();
+ aEntry->MarkFresh();
+
+ aEntry->Init(GetOriginAttrsHash(aMetaData->OriginAttributes()),
+ aMetaData->IsAnonymous(),
+ aMetaData->Pinned());
+
+ uint32_t expirationTime;
+ aMetaData->GetExpirationTime(&expirationTime);
+ aEntry->SetExpirationTime(expirationTime);
+
+ uint32_t frecency;
+ aMetaData->GetFrecency(&frecency);
+ aEntry->SetFrecency(frecency);
+
+ aEntry->SetFileSize(static_cast<uint32_t>(
+ std::min(static_cast<int64_t>(PR_UINT32_MAX),
+ (aFileSize + 0x3FF) >> 10)));
+}
+
+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;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ {
+ // Do not do IO under the lock.
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
+ }
+ 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;
+ }
+
+ 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);
+ InitEntryFromDiskData(entry, meta, size);
+ LOG(("CacheIndex::BuildIndex() - Added entry to index. [hash=%s]",
+ leaf.get()));
+ entry->Log();
+ }
+ }
+
+ NS_NOTREACHED("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;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ {
+ // Do not do IO under the lock.
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
+ }
+ 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;
+ }
+
+ 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=%u, "
+ "lastModifiedTime=%u]", 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()));
+ file->Remove(false);
+ if (entry) {
+ entry->MarkRemoved();
+ entry->MarkFresh();
+ entry->MarkDirty();
+ }
+ } else {
+ entry = mIndex.PutEntry(hash);
+ InitEntryFromDiskData(entry, meta, size);
+ LOG(("CacheIndex::UpdateIndex() - Added/updated entry to/in index. "
+ "[hash=%s]", leaf.get()));
+ entry->Log();
+ }
+ }
+
+ NS_NOTREACHED("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;
+ }
+
+ if ((mState == READING || mState == BUILDING || mState == UPDATING) &&
+ aNewState == READY) {
+ ReportHashStats();
+ }
+
+ // 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;
+}
+
+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;
+}
+
+nsresult
+CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
+ CacheFileHandle *aHandle, nsresult aResult)
+{
+ LOG(("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
+ "result=0x%08x]", aOpener, aHandle, aResult));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+
+ MOZ_RELEASE_ASSERT(IsIndexUsable());
+
+ if (mState == READY && mShuttingDown) {
+ return NS_OK;
+ }
+
+ 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%08x]", 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;
+ } else {
+ 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, NS_LITERAL_CSTRING(TEMP_INDEX_NAME), this);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::"
+ "RenameFile() failed synchronously [rv=0x%08x]", rv));
+ FinishRead(false);
+ break;
+ }
+ } else {
+ StartReadingIndex();
+ }
+
+ break;
+ default:
+ MOZ_ASSERT(false, "Unexpected state!");
+ }
+
+ return NS_OK;
+}
+
+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%08x]", aHandle,
+ 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,
+ NS_LITERAL_CSTRING(INDEX_NAME),
+ this);
+ if (NS_FAILED(rv)) {
+ LOG(("CacheIndex::OnDataWritten() - CacheFileIOManager::"
+ "RenameFile() failed synchronously [rv=0x%08x]", 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%08x]", aHandle,
+ 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%08x]", aHandle,
+ 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);
+}
+
+namespace {
+
+class HashComparator
+{
+public:
+ bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
+ return memcmp(&a->mHash, &b->mHash, sizeof(SHA1Sum::Hash)) == 0;
+ }
+ bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
+ return memcmp(&a->mHash, &b->mHash, sizeof(SHA1Sum::Hash)) < 0;
+ }
+};
+
+void
+ReportHashSizeMatch(const SHA1Sum::Hash *aHash1, const SHA1Sum::Hash *aHash2)
+{
+ const uint32_t *h1 = reinterpret_cast<const uint32_t *>(aHash1);
+ const uint32_t *h2 = reinterpret_cast<const uint32_t *>(aHash2);
+
+ for (uint32_t i = 0; i < 5; ++i) {
+ if (h1[i] != h2[i]) {
+ uint32_t bitsDiff = h1[i] ^ h2[i];
+ bitsDiff = NetworkEndian::readUint32(&bitsDiff);
+
+ // count leading zeros in bitsDiff
+ static const uint8_t debruijn32[32] =
+ { 0, 31, 9, 30, 3, 8, 13, 29, 2, 5, 7, 21, 12, 24, 28, 19,
+ 1, 10, 4, 14, 6, 22, 25, 20, 11, 15, 23, 26, 16, 27, 17, 18};
+
+ bitsDiff |= bitsDiff>>1;
+ bitsDiff |= bitsDiff>>2;
+ bitsDiff |= bitsDiff>>4;
+ bitsDiff |= bitsDiff>>8;
+ bitsDiff |= bitsDiff>>16;
+ bitsDiff++;
+
+ uint8_t hashSizeMatch = debruijn32[bitsDiff*0x076be629>>27] + (i<<5);
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HASH_STATS, hashSizeMatch);
+
+ return;
+ }
+ }
+
+ MOZ_ASSERT(false, "Found a collision in the index!");
+}
+
+} // namespace
+
+void
+CacheIndex::ReportHashStats()
+{
+ // We're gathering the hash stats only once, exclude too small caches.
+ if (CacheObserver::HashStatsReported() || mFrecencyArray.Length() < 15000) {
+ return;
+ }
+
+ nsTArray<CacheIndexRecord *> records;
+ for (auto iter = mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
+ records.AppendElement(iter.Get());
+ }
+
+ records.Sort(HashComparator());
+
+ for (uint32_t i = 1; i < records.Length(); i++) {
+ ReportHashSizeMatch(&records[i-1]->mHash, &records[i]->mHash);
+ }
+
+ CacheObserver::SetHashStatsReported();
+}
+
+// static
+void
+CacheIndex::OnAsyncEviction(bool aEvicting)
+{
+ RefPtr<CacheIndex> index = gInstance;
+ if (!index) {
+ return;
+ }
+
+ StaticMutexAutoLock lock(sLock);
+ 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..dc72c346f7
--- /dev/null
+++ b/netwerk/cache2/CacheIndex.h
@@ -0,0 +1,1153 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsTHashtable.h"
+#include "nsThreadUtils.h"
+#include "nsWeakReference.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIFile;
+class nsIDirectoryEnumerator;
+class nsITimer;
+
+
+#ifdef DEBUG
+#define DEBUG_STATS 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+class CacheFileMetadata;
+class FileOpenHelper;
+class CacheIndexIterator;
+
+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;
+} CacheIndexHeader;
+
+static_assert(
+ sizeof(CacheIndexHeader::mVersion) + sizeof(CacheIndexHeader::mTimeStamp) +
+ sizeof(CacheIndexHeader::mIsDirty) == sizeof(CacheIndexHeader),
+ "Unexpected sizeof(CacheIndexHeader)!");
+
+struct CacheIndexRecord {
+ SHA1Sum::Hash mHash;
+ uint32_t mFrecency;
+ OriginAttrsHash mOriginAttrsHash;
+ uint32_t mExpirationTime;
+
+ /*
+ * 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 0011 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)
+ , mExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
+ , mFlags(0)
+ {}
+};
+
+static_assert(
+ sizeof(CacheIndexRecord::mHash) + sizeof(CacheIndexRecord::mFrecency) +
+ sizeof(CacheIndexRecord::mOriginAttrsHash) + sizeof(CacheIndexRecord::mExpirationTime) +
+ 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 = new CacheIndexRecord();
+ LOG(("CacheIndexEntry::CacheIndexEntry() - Created record [rec=%p]", mRec.get()));
+ memcpy(&mRec->mHash, aKey, sizeof(SHA1Sum::Hash));
+ }
+ CacheIndexEntry(const CacheIndexEntry& aOther)
+ {
+ NS_NOTREACHED("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->mExpirationTime = aOther.mRec->mExpirationTime;
+ mRec->mOriginAttrsHash = aOther.mRec->mOriginAttrsHash;
+ mRec->mFlags = aOther.mRec->mFlags;
+ return *this;
+ }
+
+ void InitNew()
+ {
+ mRec->mFrecency = 0;
+ mRec->mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
+ mRec->mOriginAttrsHash = 0;
+ mRec->mFlags = 0;
+ }
+
+ void Init(OriginAttrsHash aOriginAttrsHash, bool aAnonymous, bool aPinned)
+ {
+ MOZ_ASSERT(mRec->mFrecency == 0);
+ MOZ_ASSERT(mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME);
+ MOZ_ASSERT(mRec->mOriginAttrsHash == 0);
+ // 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 SetExpirationTime(uint32_t aExpirationTime)
+ {
+ mRec->mExpirationTime = aExpirationTime;
+ }
+ uint32_t GetExpirationTime() const { return mRec->mExpirationTime; }
+
+ // 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(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::writeUint32(ptr, mRec->mExpirationTime); ptr += sizeof(uint32_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->mExpirationTime = NetworkEndian::readUint32(ptr); ptr += sizeof(uint32_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=%llx, frecency=%u, expirationTime=%u, size=%u]",
+ this, LOGSHA1(mRec->mHash), IsFresh(), IsInitialized(), IsRemoved(),
+ IsDirty(), Anonymous(), OriginAttrsHash(), GetFrecency(),
+ GetExpirationTime(), GetFileSize()));
+ }
+
+ static bool RecordMatchesLoadContextInfo(CacheIndexRecord *aRec,
+ nsILoadContextInfo *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;
+
+ 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;
+
+ static const uint32_t kReservedMask = 0x03000000;
+
+ // FileSize in kilobytes
+ static const uint32_t kFileSizeMask = 0x00FFFFFF;
+
+ nsAutoPtr<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 | kExpirationUpdatedMask |
+ kFileSizeUpdatedMask;
+ CacheIndexEntry::InitNew();
+ }
+
+ void SetFrecency(uint32_t aFrecency)
+ {
+ mUpdateFlags |= kFrecencyUpdatedMask;
+ CacheIndexEntry::SetFrecency(aFrecency);
+ }
+
+ void SetExpirationTime(uint32_t aExpirationTime)
+ {
+ mUpdateFlags |= kExpirationUpdatedMask;
+ CacheIndexEntry::SetExpirationTime(aExpirationTime);
+ }
+
+ 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;
+ }
+ if (mUpdateFlags & kExpirationUpdatedMask) {
+ aDst->mRec->mExpirationTime = mRec->mExpirationTime;
+ }
+ aDst->mRec->mOriginAttrsHash = mRec->mOriginAttrsHash;
+ if (mUpdateFlags & kFileSizeUpdatedMask) {
+ aDst->mRec->mFlags = mRec->mFlags;
+ } else {
+ // Copy all flags except file size.
+ aDst->mRec->mFlags &= kFileSizeMask;
+ aDst->mRec->mFlags |= (mRec->mFlags & ~kFileSizeMask);
+ }
+ }
+
+private:
+ static const uint32_t kFrecencyUpdatedMask = 0x00000001;
+ static const uint32_t kExpirationUpdatedMask = 0x00000002;
+ static const uint32_t kFileSizeUpdatedMask = 0x00000004;
+
+ 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
+ {
+ }
+
+ bool operator==(const CacheIndexStats& aOther) const
+ {
+ 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;
+ }
+
+#ifdef DEBUG
+ bool StateLogged() {
+ return mStateLogged;
+ }
+#endif
+
+ uint32_t Count() {
+ MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Count() - state logged!");
+ return mCount;
+ }
+
+ 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;
+ }
+
+ 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);
+ mCount--;
+ 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();
+ }
+ }
+ }
+ }
+ }
+
+ void AfterChange(const CacheIndexEntry *aEntry) {
+ MOZ_ASSERT(mStateLogged, "CacheIndexStats::AfterChange() - state not "
+ "logged!");
+#ifdef DEBUG
+ mStateLogged = false;
+#endif
+ if (aEntry) {
+ ++mCount;
+ 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();
+ }
+ }
+ }
+ }
+
+#ifdef DEBUG_STATS
+ if (!mDisableLogging) {
+ LOG(("CacheIndexStats::AfterChange()"));
+ Log();
+ }
+#endif
+ }
+
+private:
+ uint32_t mCount;
+ uint32_t mNotInitialized;
+ uint32_t mRemoved;
+ uint32_t mDirty;
+ uint32_t mFresh;
+ uint32_t mEmpty;
+ uint32_t mSize;
+#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 : 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 uint32_t *aExpirationTime,
+ 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 _pinned is non-null, it's filled with pinning status of the entry.
+ static nsresult HasEntry(const nsACString &aKey, EntryStatus *_retval,
+ bool *_pinned = nullptr);
+ static nsresult HasEntry(const SHA1Sum::Hash &hash, EntryStatus *_retval,
+ bool *_pinned = 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.
+ 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);
+
+ // 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;
+
+ virtual ~CacheIndex();
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) override;
+ nsresult 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 uint32_t *aExpirationTime,
+ 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);
+ nsresult 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();
+ void 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;
+
+ void ReportHashStats();
+
+ 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);
+ 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)
+ : mObserver(aWeakObserver) { }
+ virtual ~DiskConsumptionObserver() {
+ if (mObserver && !NS_IsMainThread()) {
+ NS_ReleaseOnMainThread(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;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheIndexContextIterator.cpp b/netwerk/cache2/CacheIndexContextIterator.cpp
new file mode 100644
index 0000000000..5f3cb7bd7c
--- /dev/null
+++ b/netwerk/cache2/CacheIndexContextIterator.cpp
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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)
+{
+}
+
+CacheIndexContextIterator::~CacheIndexContextIterator()
+{
+}
+
+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..32eb9c4789
--- /dev/null
+++ b/netwerk/cache2/CacheIndexContextIterator.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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();
+
+private:
+ virtual void AddRecord(CacheIndexRecord *aRecord);
+ 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..0d56ec81f5
--- /dev/null
+++ b/netwerk/cache2/CacheIndexIterator.cpp
@@ -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 "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[mRecords.Length() - 1]->mHash, sizeof(SHA1Sum::Hash));
+ mRecords.RemoveElementAt(mRecords.Length() - 1);
+
+ 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%08x]", this,
+ 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..9fe96989ec
--- /dev/null
+++ b/netwerk/cache2/CacheIndexIterator.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 CacheIndexIterator__h__
+#define CacheIndexIterator__h__
+
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.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..862c3316ff
--- /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..7191d47926
--- /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..1eb76e8c5b
--- /dev/null
+++ b/netwerk/cache2/CacheObserver.cpp
@@ -0,0 +1,581 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 {
+
+CacheObserver* CacheObserver::sSelf = nullptr;
+
+static uint32_t const kDefaultUseNewCache = 1; // Use the new cache by default
+uint32_t CacheObserver::sUseNewCache = kDefaultUseNewCache;
+
+static bool sUseNewCacheTemp = false; // Temp trigger to not lose early adopters
+
+static int32_t const kAutoDeleteCacheVersion = -1; // Auto-delete off by default
+static int32_t sAutoDeleteCacheVersion = kAutoDeleteCacheVersion;
+
+static int32_t const kDefaultHalfLifeExperiment = -1; // Disabled
+int32_t CacheObserver::sHalfLifeExperiment = kDefaultHalfLifeExperiment;
+
+static float const kDefaultHalfLifeHours = 1.0F; // 1 hour
+float CacheObserver::sHalfLifeHours = kDefaultHalfLifeHours;
+
+static bool const kDefaultUseDiskCache = true;
+bool CacheObserver::sUseDiskCache = kDefaultUseDiskCache;
+
+static bool const kDefaultUseMemoryCache = true;
+bool CacheObserver::sUseMemoryCache = kDefaultUseMemoryCache;
+
+static uint32_t const kDefaultMetadataMemoryLimit = 250; // 0.25 MB
+uint32_t CacheObserver::sMetadataMemoryLimit = kDefaultMetadataMemoryLimit;
+
+static int32_t const kDefaultMemoryCacheCapacity = -1; // autodetect
+int32_t CacheObserver::sMemoryCacheCapacity = kDefaultMemoryCacheCapacity;
+// Cache of the calculated memory capacity based on the system memory size
+int32_t CacheObserver::sAutoMemoryCacheCapacity = -1;
+
+static uint32_t const kDefaultDiskCacheCapacity = 250 * 1024; // 250 MB
+Atomic<uint32_t,Relaxed> CacheObserver::sDiskCacheCapacity
+ (kDefaultDiskCacheCapacity);
+
+static uint32_t const kDefaultDiskFreeSpaceSoftLimit = 5 * 1024; // 5MB
+uint32_t CacheObserver::sDiskFreeSpaceSoftLimit = kDefaultDiskFreeSpaceSoftLimit;
+
+static uint32_t const kDefaultDiskFreeSpaceHardLimit = 1024; // 1MB
+uint32_t CacheObserver::sDiskFreeSpaceHardLimit = kDefaultDiskFreeSpaceHardLimit;
+
+static bool const kDefaultSmartCacheSizeEnabled = false;
+bool CacheObserver::sSmartCacheSizeEnabled = kDefaultSmartCacheSizeEnabled;
+
+static uint32_t const kDefaultPreloadChunkCount = 4;
+uint32_t CacheObserver::sPreloadChunkCount = kDefaultPreloadChunkCount;
+
+static int32_t const kDefaultMaxMemoryEntrySize = 4 * 1024; // 4 MB
+int32_t CacheObserver::sMaxMemoryEntrySize = kDefaultMaxMemoryEntrySize;
+
+static int32_t const kDefaultMaxDiskEntrySize = 50 * 1024; // 50 MB
+int32_t CacheObserver::sMaxDiskEntrySize = kDefaultMaxDiskEntrySize;
+
+static uint32_t const kDefaultMaxDiskChunksMemoryUsage = 10 * 1024; // 10MB
+uint32_t CacheObserver::sMaxDiskChunksMemoryUsage = kDefaultMaxDiskChunksMemoryUsage;
+
+static uint32_t const kDefaultMaxDiskPriorityChunksMemoryUsage = 10 * 1024; // 10MB
+uint32_t CacheObserver::sMaxDiskPriorityChunksMemoryUsage = kDefaultMaxDiskPriorityChunksMemoryUsage;
+
+static uint32_t const kDefaultCompressionLevel = 1;
+uint32_t CacheObserver::sCompressionLevel = kDefaultCompressionLevel;
+
+static bool kDefaultSanitizeOnShutdown = false;
+bool CacheObserver::sSanitizeOnShutdown = kDefaultSanitizeOnShutdown;
+
+static bool kDefaultClearCacheOnShutdown = false;
+bool CacheObserver::sClearCacheOnShutdown = kDefaultClearCacheOnShutdown;
+
+static bool kDefaultCacheFSReported = false;
+bool CacheObserver::sCacheFSReported = kDefaultCacheFSReported;
+
+static bool kDefaultHashStatsReported = false;
+bool CacheObserver::sHashStatsReported = kDefaultHashStatsReported;
+
+static uint32_t const kDefaultMaxShutdownIOLag = 2; // seconds
+Atomic<uint32_t, Relaxed> CacheObserver::sMaxShutdownIOLag(kDefaultMaxShutdownIOLag);
+
+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();
+ NS_ADDREF(sSelf);
+
+ 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, "clear-origin-attributes-data", true);
+ obs->AddObserver(sSelf, "memory-pressure", true);
+
+ return NS_OK;
+}
+
+// static
+nsresult
+CacheObserver::Shutdown()
+{
+ if (!sSelf) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ NS_RELEASE(sSelf);
+ return NS_OK;
+}
+
+void
+CacheObserver::AttachToPreferences()
+{
+ sAutoDeleteCacheVersion = mozilla::Preferences::GetInt(
+ "browser.cache.auto_delete_cache_version", kAutoDeleteCacheVersion);
+
+ mozilla::Preferences::AddUintVarCache(
+ &sUseNewCache, "browser.cache.use_new_backend", kDefaultUseNewCache);
+ mozilla::Preferences::AddBoolVarCache(
+ &sUseNewCacheTemp, "browser.cache.use_new_backend_temp", false);
+
+ mozilla::Preferences::AddBoolVarCache(
+ &sUseDiskCache, "browser.cache.disk.enable", kDefaultUseDiskCache);
+ mozilla::Preferences::AddBoolVarCache(
+ &sUseMemoryCache, "browser.cache.memory.enable", kDefaultUseMemoryCache);
+
+ mozilla::Preferences::AddUintVarCache(
+ &sMetadataMemoryLimit, "browser.cache.disk.metadata_memory_limit", kDefaultMetadataMemoryLimit);
+
+ mozilla::Preferences::AddAtomicUintVarCache(
+ &sDiskCacheCapacity, "browser.cache.disk.capacity", kDefaultDiskCacheCapacity);
+ mozilla::Preferences::AddBoolVarCache(
+ &sSmartCacheSizeEnabled, "browser.cache.disk.smart_size.enabled", kDefaultSmartCacheSizeEnabled);
+ mozilla::Preferences::AddIntVarCache(
+ &sMemoryCacheCapacity, "browser.cache.memory.capacity", kDefaultMemoryCacheCapacity);
+
+ mozilla::Preferences::AddUintVarCache(
+ &sDiskFreeSpaceSoftLimit, "browser.cache.disk.free_space_soft_limit", kDefaultDiskFreeSpaceSoftLimit);
+ mozilla::Preferences::AddUintVarCache(
+ &sDiskFreeSpaceHardLimit, "browser.cache.disk.free_space_hard_limit", kDefaultDiskFreeSpaceHardLimit);
+
+ mozilla::Preferences::AddUintVarCache(
+ &sPreloadChunkCount, "browser.cache.disk.preload_chunk_count", kDefaultPreloadChunkCount);
+
+ mozilla::Preferences::AddIntVarCache(
+ &sMaxDiskEntrySize, "browser.cache.disk.max_entry_size", kDefaultMaxDiskEntrySize);
+ mozilla::Preferences::AddIntVarCache(
+ &sMaxMemoryEntrySize, "browser.cache.memory.max_entry_size", kDefaultMaxMemoryEntrySize);
+
+ mozilla::Preferences::AddUintVarCache(
+ &sMaxDiskChunksMemoryUsage, "browser.cache.disk.max_chunks_memory_usage", kDefaultMaxDiskChunksMemoryUsage);
+ mozilla::Preferences::AddUintVarCache(
+ &sMaxDiskPriorityChunksMemoryUsage, "browser.cache.disk.max_priority_chunks_memory_usage", kDefaultMaxDiskPriorityChunksMemoryUsage);
+
+ // http://mxr.mozilla.org/mozilla-central/source/netwerk/cache/nsCacheEntryDescriptor.cpp#367
+ mozilla::Preferences::AddUintVarCache(
+ &sCompressionLevel, "browser.cache.compression_level", kDefaultCompressionLevel);
+
+ mozilla::Preferences::GetComplex(
+ "browser.cache.disk.parent_directory", NS_GET_IID(nsIFile),
+ getter_AddRefs(mCacheParentDirectoryOverride));
+
+ // First check the default value. If it is at -1, the experient
+ // is turned off. If it is at 0, then use the user pref value
+ // instead.
+ sHalfLifeExperiment = mozilla::Preferences::GetDefaultInt(
+ "browser.cache.frecency_experiment", kDefaultHalfLifeExperiment);
+
+ if (sHalfLifeExperiment == 0) {
+ // Default preferences indicate we want to run the experiment,
+ // hence read the user value.
+ sHalfLifeExperiment = mozilla::Preferences::GetInt(
+ "browser.cache.frecency_experiment", sHalfLifeExperiment);
+ }
+
+ if (sHalfLifeExperiment == 0) {
+ // The experiment has not yet been initialized but is engaged, do
+ // the initialization now.
+ srand(time(NULL));
+ sHalfLifeExperiment = (rand() % 4) + 1;
+ // Store the experiemnt value, since we need it not to change between
+ // browser sessions.
+ mozilla::Preferences::SetInt(
+ "browser.cache.frecency_experiment", sHalfLifeExperiment);
+ }
+
+ switch (sHalfLifeExperiment) {
+ case 1: // The experiment is engaged
+ sHalfLifeHours = 0.083F; // ~5 mintues
+ break;
+ case 2:
+ sHalfLifeHours = 0.25F; // 15 mintues
+ break;
+ case 3:
+ sHalfLifeHours = 1.0F;
+ break;
+ case 4:
+ sHalfLifeHours = 6.0F;
+ break;
+
+ case -1:
+ default: // The experiment is off or broken
+ sHalfLifeExperiment = -1;
+ sHalfLifeHours = std::max(0.01F, std::min(1440.0F, mozilla::Preferences::GetFloat(
+ "browser.cache.frecency_half_life_hours", kDefaultHalfLifeHours)));
+ break;
+ }
+
+ mozilla::Preferences::AddBoolVarCache(
+ &sSanitizeOnShutdown, "privacy.sanitize.sanitizeOnShutdown", kDefaultSanitizeOnShutdown);
+ mozilla::Preferences::AddBoolVarCache(
+ &sClearCacheOnShutdown, "privacy.clearOnShutdown.cache", kDefaultClearCacheOnShutdown);
+
+ mozilla::Preferences::AddAtomicUintVarCache(
+ &sMaxShutdownIOLag, "browser.cache.max_shutdown_io_lag", kDefaultMaxShutdownIOLag);
+}
+
+// static
+uint32_t CacheObserver::MemoryCacheCapacity()
+{
+ if (sMemoryCacheCapacity >= 0)
+ return sMemoryCacheCapacity << 10;
+
+ if (sAutoMemoryCacheCapacity != -1)
+ return sAutoMemoryCacheCapacity;
+
+ static 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) {
+ capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
+ if (capacity > 32)
+ capacity = 32;
+ capacity <<= 20;
+ }
+
+ // Result is in bytes.
+ return sAutoMemoryCacheCapacity = capacity;
+}
+
+// static
+bool CacheObserver::UseNewCache()
+{
+ uint32_t useNewCache = sUseNewCache;
+
+ if (sUseNewCacheTemp)
+ useNewCache = 1;
+
+ switch (useNewCache) {
+ case 0: // use the old cache backend
+ return false;
+
+ case 1: // use the new cache backend
+ return true;
+ }
+
+ return true;
+}
+
+// static
+void
+CacheObserver::SetDiskCacheCapacity(uint32_t aCapacity)
+{
+ sDiskCacheCapacity = aCapacity >> 10;
+
+ if (!sSelf) {
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ sSelf->StoreDiskCacheCapacity();
+ } else {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(sSelf, &CacheObserver::StoreDiskCacheCapacity);
+ NS_DispatchToMainThread(event);
+ }
+}
+
+void
+CacheObserver::StoreDiskCacheCapacity()
+{
+ mozilla::Preferences::SetInt("browser.cache.disk.capacity",
+ sDiskCacheCapacity);
+}
+
+// static
+void
+CacheObserver::SetCacheFSReported()
+{
+ sCacheFSReported = true;
+
+ if (!sSelf) {
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ sSelf->StoreCacheFSReported();
+ } else {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(sSelf, &CacheObserver::StoreCacheFSReported);
+ NS_DispatchToMainThread(event);
+ }
+}
+
+void
+CacheObserver::StoreCacheFSReported()
+{
+ mozilla::Preferences::SetInt("browser.cache.disk.filesystem_reported",
+ sCacheFSReported);
+}
+
+// static
+void
+CacheObserver::SetHashStatsReported()
+{
+ sHashStatsReported = true;
+
+ if (!sSelf) {
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ sSelf->StoreHashStatsReported();
+ } else {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(sSelf, &CacheObserver::StoreHashStatsReported);
+ NS_DispatchToMainThread(event);
+ }
+}
+
+void
+CacheObserver::StoreHashStatsReported()
+{
+ mozilla::Preferences::SetInt("browser.cache.disk.hashstats_reported",
+ sHashStatsReported);
+}
+
+// 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);
+}
+
+namespace {
+namespace CacheStorageEvictHelper {
+
+nsresult ClearStorage(bool const aPrivate,
+ bool const aAnonymous,
+ NeckoOriginAttributes &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(NeckoOriginAttributes &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;
+}
+
+} // CacheStorageEvictHelper
+} // anon
+
+// static
+bool CacheObserver::EntryIsTooBig(int64_t aSize, bool aUsingDisk)
+{
+ // If custom limit is set, check it.
+ int64_t preferredLimit = aUsingDisk ? sMaxDiskEntrySize : sMaxMemoryEntrySize;
+
+ // 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 (>> 3) of the respective capacity.
+ int64_t derivedLimit = aUsingDisk
+ ? (static_cast<int64_t>(DiskCacheCapacity() >> 3))
+ : (static_cast<int64_t>(MemoryCacheCapacity() >> 3));
+
+ if (aSize > derivedLimit)
+ return true;
+
+ return false;
+}
+
+// static
+bool CacheObserver::IsPastShutdownIOLag()
+{
+#ifdef DEBUG
+ return false;
+#endif
+
+ if (sShutdownDemandedTime == PR_INTERVAL_NO_TIMEOUT ||
+ sMaxShutdownIOLag == UINT32_MAX) {
+ return false;
+ }
+
+ static const PRIntervalTime kMaxShutdownIOLag =
+ PR_SecondsToInterval(sMaxShutdownIOLag);
+
+ 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")) {
+ uint32_t activeVersion = UseNewCache() ? 1 : 0;
+ CacheStorageService::CleaupCacheDirectories(sAutoDeleteCacheVersion, activeVersion);
+ 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, "clear-origin-attributes-data")) {
+ NeckoOriginAttributes oa;
+ if (!oa.Init(nsDependentString(aData))) {
+ NS_ERROR("Could not parse NeckoOriginAttributes JSON in clear-origin-attributes-data notification");
+ return NS_OK;
+ }
+
+ nsresult rv = CacheStorageEvictHelper::Run(oa);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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..62e5bbc6ec
--- /dev/null
+++ b/netwerk/cache2/CacheObserver.h
@@ -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/. */
+
+#ifndef CacheObserver__h__
+#define CacheObserver__h__
+
+#include "nsIObserver.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+class CacheObserver : public nsIObserver
+ , public nsSupportsWeakReference
+{
+ virtual ~CacheObserver() {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static nsresult Init();
+ static nsresult Shutdown();
+ static CacheObserver* Self() { return sSelf; }
+
+ // Access to preferences
+ static bool UseNewCache();
+ static bool UseDiskCache()
+ { return sUseDiskCache; }
+ static bool UseMemoryCache()
+ { return sUseMemoryCache; }
+ static uint32_t MetadataMemoryLimit() // result in bytes.
+ { return sMetadataMemoryLimit << 10; }
+ static uint32_t MemoryCacheCapacity(); // result in bytes.
+ static uint32_t DiskCacheCapacity() // result in bytes.
+ { return sDiskCacheCapacity << 10; }
+ static void SetDiskCacheCapacity(uint32_t); // parameter in bytes.
+ static uint32_t DiskFreeSpaceSoftLimit() // result in bytes.
+ { return sDiskFreeSpaceSoftLimit << 10; }
+ static uint32_t DiskFreeSpaceHardLimit() // result in bytes.
+ { return sDiskFreeSpaceHardLimit << 10; }
+ static bool SmartCacheSizeEnabled()
+ { return sSmartCacheSizeEnabled; }
+ static uint32_t PreloadChunkCount()
+ { return sPreloadChunkCount; }
+ static uint32_t MaxMemoryEntrySize() // result in bytes.
+ { return sMaxMemoryEntrySize << 10; }
+ static uint32_t MaxDiskEntrySize() // result in bytes.
+ { return sMaxDiskEntrySize << 10; }
+ static uint32_t MaxDiskChunksMemoryUsage(bool aPriority) // result in bytes.
+ { return aPriority ? sMaxDiskPriorityChunksMemoryUsage << 10
+ : sMaxDiskChunksMemoryUsage << 10; }
+ static uint32_t CompressionLevel()
+ { return sCompressionLevel; }
+ static uint32_t HalfLifeSeconds()
+ { return sHalfLifeHours * 60.0F * 60.0F; }
+ static int32_t HalfLifeExperiment()
+ { return sHalfLifeExperiment; }
+ static bool ClearCacheOnShutdown()
+ { return sSanitizeOnShutdown && sClearCacheOnShutdown; }
+ static bool CacheFSReported()
+ { return sCacheFSReported; }
+ static void SetCacheFSReported();
+ static bool HashStatsReported()
+ { return sHashStatsReported; }
+ static void SetHashStatsReported();
+ static void ParentDirOverride(nsIFile ** aDir);
+
+ static bool EntryIsTooBig(int64_t aSize, bool aUsingDisk);
+
+ static uint32_t MaxShutdownIOLag()
+ { return sMaxShutdownIOLag; }
+ static bool IsPastShutdownIOLag();
+
+ static bool ShuttingDown()
+ { return sShutdownDemandedTime != PR_INTERVAL_NO_TIMEOUT; }
+
+private:
+ static CacheObserver* sSelf;
+
+ void StoreDiskCacheCapacity();
+ void StoreCacheFSReported();
+ void StoreHashStatsReported();
+ void AttachToPreferences();
+
+ static uint32_t sUseNewCache;
+ static bool sUseMemoryCache;
+ static bool sUseDiskCache;
+ static uint32_t sMetadataMemoryLimit;
+ static int32_t sMemoryCacheCapacity;
+ static int32_t sAutoMemoryCacheCapacity;
+ static Atomic<uint32_t, Relaxed> sDiskCacheCapacity;
+ static uint32_t sDiskFreeSpaceSoftLimit;
+ static uint32_t sDiskFreeSpaceHardLimit;
+ static bool sSmartCacheSizeEnabled;
+ static uint32_t sPreloadChunkCount;
+ static int32_t sMaxMemoryEntrySize;
+ static int32_t sMaxDiskEntrySize;
+ static uint32_t sMaxDiskChunksMemoryUsage;
+ static uint32_t sMaxDiskPriorityChunksMemoryUsage;
+ static uint32_t sCompressionLevel;
+ static float sHalfLifeHours;
+ static int32_t sHalfLifeExperiment;
+ static bool sSanitizeOnShutdown;
+ static bool sClearCacheOnShutdown;
+ static bool sCacheFSReported;
+ static bool sHashStatsReported;
+ static Atomic<uint32_t, Relaxed> sMaxShutdownIOLag;
+ 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..1d84195a40
--- /dev/null
+++ b/netwerk/cache2/CacheStorage.cpp
@@ -0,0 +1,245 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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 "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(CacheStorage, nsICacheStorage)
+
+CacheStorage::CacheStorage(nsILoadContextInfo* aInfo,
+ bool aAllowDisk,
+ bool aLookupAppCache,
+ bool aSkipSizeCheck,
+ bool aPinning)
+: mLoadContextInfo(GetLoadContextInfo(aInfo))
+, mWriteToDisk(aAllowDisk)
+, mLookupAppCache(aLookupAppCache)
+, mSkipSizeCheck(aSkipSizeCheck)
+, mPinning(aPinning)
+{
+}
+
+CacheStorage::~CacheStorage()
+{
+}
+
+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 = aURI->CloneIgnoringRef(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 = aURI->CloneIgnoringRef(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 = aURI->CloneIgnoringRef(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::AsyncDoomURI(nsIURI *aURI, const nsACString & aIdExtension,
+ nsICacheEntryDoomCallback* aCallback)
+{
+ if (!CacheStorageService::Self())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = aURI->CloneIgnoringRef(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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheStorage.h b/netwerk/cache2/CacheStorage.h
new file mode 100644
index 0000000000..85c5bccdbb
--- /dev/null
+++ b/netwerk/cache2/CacheStorage.h
@@ -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/. */
+
+#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"
+#include "nsICacheEntryDoomCallback.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();
+
+ 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->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..67ef4c5267
--- /dev/null
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -0,0 +1,2290 @@
+/* -*- 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 "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWeakReference.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+void AppendMemoryStorageID(nsAutoCString &key)
+{
+ key.Append('/');
+ key.Append('M');
+}
+
+} // 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|appId|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
+{
+ switch (mType) {
+ case DISK:
+ return CacheObserver::MetadataMemoryLimit();
+ case MEMORY:
+ return CacheObserver::MemoryCacheCapacity();
+ }
+
+ MOZ_CRASH("Bad pool type");
+ return 0;
+}
+
+NS_IMPL_ISUPPORTS(CacheStorageService,
+ nsICacheStorageService,
+ nsIMemoryReporter,
+ nsITimerCallback,
+ nsICacheTesting)
+
+CacheStorageService* CacheStorageService::sSelf = nullptr;
+
+CacheStorageService::CacheStorageService()
+: mLock("CacheStorageService.mLock")
+, mForcedValidEntriesLock("CacheStorageService.mForcedValidEntriesLock")
+, mShutdown(false)
+, mDiskPool(MemoryPool::DISK)
+, mMemoryPool(MemoryPool::MEMORY)
+{
+ CacheFileIOManager::Init();
+
+ 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(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)
+ : mService(CacheStorageService::Self())
+ , mCallback(aVisitor)
+ , mSize(0)
+ , mNotifyStorage(true)
+ , mVisitEntries(aVisitEntries)
+ , mCancel(false)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ virtual ~WalkCacheRunnable()
+ {
+ if (mCallback) {
+ ProxyReleaseMainThread(mCallback);
+ }
+ }
+
+ RefPtr<CacheStorageService> mService;
+ nsCOMPtr<nsICacheStorageVisitor> mCallback;
+
+ uint64_t mSize;
+
+ bool mNotifyStorage : 1;
+ bool mVisitEntries : 1;
+
+ 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;
+
+ CacheEntryTable* entries;
+ if (sGlobalEntryTables->Get(mContextKey, &entries)) {
+ for (auto iter = entries->Iter(); !iter.Done(); iter.Next()) {
+ CacheEntry* entry = iter.UserData();
+
+ // Ignore disk entries
+ if (entry->IsUsingDisk()) {
+ continue;
+ }
+
+ 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 (mNotifyStorage) {
+ LOG((" storage"));
+
+ // Second, notify overall storage info
+ mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize,
+ CacheObserver::MemoryCacheCapacity(), nullptr);
+ if (!mVisitEntries)
+ return NS_OK; // done
+
+ mNotifyStorage = false;
+
+ } else {
+ LOG((" entry [left=%d, 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(mCallback);
+ }
+
+ virtual void OnEntryInfo(const nsACString & aURISpec, const nsACString & aIdEnhance,
+ int64_t aDataSize, int32_t aFetchCount,
+ uint32_t aLastModifiedTime, uint32_t aExpirationTime,
+ bool aPinned) 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);
+ 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)
+ {
+ }
+
+ 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)
+ : mWalker(aWalker)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), mURISpec);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ rv = mWalker->mCallback->OnCacheEntryInfo(
+ uri, mIdEnhance, mDataSize, mFetchCount,
+ mLastModifiedTime, mExpirationTime, mPinned);
+ 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;
+ };
+
+ 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 (mVisitEntries) {
+ // both onStorageInfo and onCompleted are expected
+ NS_DispatchToMainThread(this);
+ }
+ return NS_DispatchToMainThread(this);
+ }
+
+ mSize = size << 10;
+
+ // Invoke onCacheStorageInfo with valid information.
+ NS_DispatchToMainThread(this);
+
+ if (!mVisitEntries) {
+ return NS_OK; // done
+ }
+
+ mPass = ITERATE_METADATA;
+ MOZ_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 (mNotifyStorage) {
+ nsCOMPtr<nsIFile> dir;
+ CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir));
+ mCallback->OnCacheStorageInfo(mCount, mSize, CacheObserver::DiskCacheCapacity(), dir);
+ mNotifyStorage = 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) 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;
+
+ 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(uint32_t aVersion, uint32_t aActive);
+
+private:
+ CleaupCacheDirectoriesRunnable(uint32_t aVersion, uint32_t aActive)
+ : mVersion(aVersion), mActive(aActive)
+ {
+ nsCacheService::GetDiskCacheDirectory(getter_AddRefs(mCache1Dir));
+ CacheFileIOManager::GetCacheDirectory(getter_AddRefs(mCache2Dir));
+#if defined(MOZ_WIDGET_ANDROID)
+ CacheFileIOManager::GetProfilelessCacheDirectory(getter_AddRefs(mCache2Profileless));
+#endif
+ }
+
+ virtual ~CleaupCacheDirectoriesRunnable() {}
+ uint32_t mVersion, mActive;
+ nsCOMPtr<nsIFile> mCache1Dir, mCache2Dir;
+#if defined(MOZ_WIDGET_ANDROID)
+ nsCOMPtr<nsIFile> mCache2Profileless;
+#endif
+};
+
+// static
+bool CleaupCacheDirectoriesRunnable::Post(uint32_t aVersion, uint32_t aActive)
+{
+ // CleaupCacheDirectories is called regardless what cache version is set up to use.
+ // To obtain the cache1 directory we must unfortunatelly instantiate the old cache
+ // service despite it may not be used at all... This also initialize 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(aVersion, aActive);
+ 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
+
+ // Delete the non-active version cache data right now
+ if (mVersion == mActive) {
+ return NS_OK;
+ }
+
+ switch (mVersion) {
+ case 0:
+ if (mCache1Dir) {
+ nsDeleteDir::DeleteDir(mCache1Dir, true, 30000);
+ }
+ break;
+ case 1:
+ if (mCache2Dir) {
+ nsDeleteDir::DeleteDir(mCache2Dir, true, 30000);
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+} // namespace
+
+// static
+void CacheStorageService::CleaupCacheDirectories(uint32_t aVersion, uint32_t aActive)
+{
+ // Make sure we schedule just once in case CleaupCacheDirectories gets called
+ // multiple times from some reason.
+ static bool runOnce = CleaupCacheDirectoriesRunnable::Post(aVersion, aActive);
+ 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);
+}
+
+// nsICacheStorageService
+
+NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+ nsICacheStorage * *_retval)
+{
+ NS_ENSURE_ARG(aLoadContextInfo);
+ NS_ENSURE_ARG(_retval);
+
+ nsCOMPtr<nsICacheStorage> storage;
+ if (CacheObserver::UseNewCache()) {
+ storage = new CacheStorage(aLoadContextInfo, false, false, false, false);
+ }
+ else {
+ storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
+ }
+
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::DiskCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+ bool aLookupAppCache,
+ nsICacheStorage * *_retval)
+{
+ NS_ENSURE_ARG(aLoadContextInfo);
+ 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;
+ if (CacheObserver::UseNewCache()) {
+ storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache, false /* size limit */, false /* don't pin */);
+ }
+ else {
+ storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr);
+ }
+
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::PinningCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+ nsICacheStorage * *_retval)
+{
+ NS_ENSURE_ARG(aLoadContextInfo);
+ NS_ENSURE_ARG(_retval);
+
+ if (!CacheObserver::UseNewCache()) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // 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(aLoadContextInfo);
+ NS_ENSURE_ARG(_retval);
+
+ nsCOMPtr<nsICacheStorage> storage;
+ if (CacheObserver::UseNewCache()) {
+ // 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);
+ }
+ else {
+ storage = new _OldStorage(aLoadContextInfo, true, false, true, 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;
+ if (CacheObserver::UseNewCache()) {
+ storage = new CacheStorage(aLoadContextInfo, false, false, true /* skip size checks for synthesized cache */, false /* no pinning */);
+ }
+ else {
+ storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
+ }
+
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::Clear()
+{
+ nsresult rv;
+
+ if (CacheObserver::UseNewCache()) {
+ // 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);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ nsCOMPtr<nsICacheService> serv =
+ do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = serv->EvictEntries(nsICache::STORE_ANYWHERE);
+ 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;
+
+ if (CacheObserver::UseNewCache()) {
+ rv = CacheIndex::AsyncGetDiskConsumption(aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rv = _OldGetDiskConsumption::Get(aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget)
+{
+ NS_ENSURE_ARG(aEventTarget);
+
+ if (CacheObserver::UseNewCache()) {
+ nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+ ioTarget.forget(aEventTarget);
+ }
+ else {
+ nsresult rv;
+
+ nsCOMPtr<nsICacheService> serv =
+ do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = serv->GetCacheIOTarget(aEventTarget);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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 {
+ 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, aEntry);
+ return true;
+}
+
+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;
+}
+
+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());
+ AppendMemoryStorageID(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());
+ AppendMemoryStorageID(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.
+ if (mPurgeTimer)
+ 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(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 = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (mPurgeTimer) {
+ nsresult rv;
+ rv = mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT);
+ LOG((" timer init rv=0x%08x", rv));
+ }
+}
+
+NS_IMETHODIMP
+CacheStorageService::Notify(nsITimer* aTimer)
+{
+ LOG(("CacheStorageService::Notify"));
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (aTimer == mPurgeTimer) {
+ mPurgeTimer = nullptr;
+
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &CacheStorageService::PurgeOverMemoryLimit);
+ Dispatch(event);
+ }
+
+ 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();
+ }
+
+ bool frecencyNeedsSort = true;
+
+ // 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(frecencyNeedsSort, CacheEntry::PURGE_DATA_ONLY_DISK_BACKED);
+ }
+
+ if (mMemorySize > memoryLimit) {
+ LOG((" metadata consumtion over the limit, abandon disk backed entries"));
+ PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED);
+ }
+#endif
+
+ if (mMemorySize > memoryLimit) {
+ LOG((" memory data consumption over the limit, abandon any entry"));
+ PurgeByFrecency(frecencyNeedsSort, 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(bool &aFrecencyNeedsSort, uint32_t aWhat)
+{
+ MOZ_ASSERT(IsOnManagementThread());
+
+ if (aFrecencyNeedsSort) {
+ mFrecencyArray.Sort(FrecencyComparator());
+ aFrecencyNeedsSort = false;
+ }
+
+ uint32_t const memoryLimit = Limit();
+
+ for (uint32_t i = 0; mMemorySize > memoryLimit && i < mFrecencyArray.Length();) {
+ if (CacheIOThread::YieldAndRerun())
+ 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;
+ }
+}
+
+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(nsCSubstring const& aContextKey,
+ const nsACString & aURI,
+ const nsACString & aIdExtension,
+ bool aWriteToDisk,
+ bool aSkipSizeCheck,
+ bool aPin,
+ bool aReplace,
+ CacheEntryHandle** aResult)
+{
+ nsresult rv;
+
+ nsAutoCString entryKey;
+ rv = CacheEntry::HashingKey(EmptyCString(), 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, 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()) {
+ AppendMemoryStorageID(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(EmptyCString(), 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%08x", rv));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = status == CacheIndex::EXISTS;
+ LOG((" %sfound in index", *aResult ? "" : "not "));
+ return NS_OK;
+}
+
+namespace {
+
+class CacheEntryDoomByKeyCallback : public CacheFileIOListener
+ , public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ explicit CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
+ : mCallback(aCallback) { }
+
+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(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(EmptyCString(), 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) : 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(nsCSubstring const& 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);
+ AppendMemoryStorageID(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);
+ }
+ } 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.
+ nsAutoPtr<CacheEntryTable> memoryEntries;
+ sGlobalEntryTables->RemoveAndForget(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) : 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(EmptyCString(), 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(EmptyCString(), 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();
+
+ 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());
+}
+
+// static
+uint32_t CacheStorageService::CacheQueueSize(bool highPriority)
+{
+ RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
+ MOZ_ASSERT(thread);
+ return thread->QueueSize(highPriority);
+}
+
+// Telementry 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);
+ }
+
+ 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);
+ }
+ }
+
+ // These key names are not privacy-sensitive.
+ aHandleReport->Callback(
+ EmptyCString(),
+ nsPrintfCString("explicit/network/cache2/%s-storage(%s)",
+ table->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk",
+ iter1.Key().BeginReading()),
+ nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, size,
+ NS_LITERAL_CSTRING("Memory used by the cache storage."),
+ 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..f40459d842
--- /dev/null
+++ b/netwerk/cache2/CacheStorageService.h
@@ -0,0 +1,422 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "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 {
+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:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHESTORAGESERVICE
+ NS_DECL_NSIMEMORYREPORTER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSICACHETESTING
+
+ CacheStorageService();
+
+ void Shutdown();
+ void DropPrivateBrowsingEntries();
+
+ // Takes care of deleting any pending trashes for both cache1 and cache2
+ // as well as the cache directory of an inactive cache version when requested.
+ static void CleaupCacheDirectories(uint32_t aVersion, uint32_t aActive);
+
+ 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) = 0;
+ };
+
+ // Invokes OnEntryInfo for the given aEntry, synchronously.
+ static void GetCacheEntryInfo(CacheEntry* aEntry, EntryInfoCallback *aVisitor);
+
+ 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(nsCSubstring const& aContextKey,
+ nsILoadContextInfo* aContext,
+ bool aDiskStorage,
+ bool aPin,
+ nsICacheEntryDoomCallback* aCallback);
+ nsresult AddStorageEntry(nsCSubstring const& aContextKey,
+ const nsACString & aURI,
+ const nsACString & aIdExtension,
+ bool aWriteToDisk,
+ bool aSkipSizeCheck,
+ bool aPin,
+ bool aReplace,
+ CacheEntryHandle** aResult);
+
+ static CacheStorageService* sSelf;
+
+ mozilla::Mutex mLock;
+ mozilla::Mutex mForcedValidEntriesLock;
+
+ bool 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(bool &aFrecencyNeedsSort, 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;
+
+ class PurgeFromMemoryRunnable : public Runnable
+ {
+ public:
+ PurgeFromMemoryRunnable(CacheStorageService* aService, uint32_t aWhat)
+ : mService(aService), mWhat(aWhat) { }
+
+ private:
+ virtual ~PurgeFromMemoryRunnable() { }
+
+ 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() : mMon("IOThreadSuspender"), mSignaled(false) { }
+ void Notify();
+ private:
+ virtual ~IOThreadSuspender() { }
+ NS_IMETHOD Run() override;
+
+ Monitor mMon;
+ bool mSignaled;
+ };
+
+ RefPtr<IOThreadSuspender> mActiveIOSuspender;
+};
+
+template<class T>
+void ProxyRelease(nsCOMPtr<T> &object, nsIThread* thread)
+{
+ NS_ProxyRelease(thread, object.forget());
+}
+
+template<class T>
+void ProxyReleaseMainThread(nsCOMPtr<T> &object)
+{
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ ProxyRelease(object, mainThread);
+}
+
+} // 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..81df88df0e
--- /dev/null
+++ b/netwerk/cache2/OldWrappers.cpp
@@ -0,0 +1,1155 @@
+// 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/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) : mCB(cb)
+ {
+ MOZ_COUNT_CTOR(DoomCallbackSynchronizer);
+ }
+ nsresult Dispatch();
+
+private:
+ virtual ~DoomCallbackSynchronizer()
+ {
+ MOZ_COUNT_DTOR(DoomCallbackSynchronizer);
+ }
+
+ 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)
+ {
+ MOZ_COUNT_CTOR(DoomCallbackWrapper);
+ }
+
+private:
+ virtual ~DoomCallbackWrapper()
+ {
+ MOZ_COUNT_DTOR(DoomCallbackWrapper);
+ }
+
+ 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();
+ }
+
+ MOZ_COUNT_DTOR(_OldVisitCallbackWrapper);
+}
+
+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->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.
+ nsXPIDLCString clientId;
+ rv = entryInfo->GetClientID(getter_Copies(clientId));
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ if (mLoadInfo->IsPrivate() !=
+ StringBeginsWith(clientId, NS_LITERAL_CSTRING("HTTP-memory-only-PB"))) {
+ return NS_OK;
+ }
+
+ nsAutoCString cacheKey, enhanceId;
+ rv = entryInfo->GetKey(cacheKey);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ if (StringBeginsWith(cacheKey, NS_LITERAL_CSTRING("anon&"))) {
+ if (!mLoadInfo->IsAnonymous())
+ return NS_OK;
+
+ cacheKey = Substring(cacheKey, 5, cacheKey.Length());
+ } else if (mLoadInfo->IsAnonymous()) {
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(cacheKey, NS_LITERAL_CSTRING("id="))) {
+ 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, NS_LITERAL_CSTRING("uri="))) {
+ 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);
+
+ *_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)
+ : 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)
+{
+ MOZ_COUNT_CTOR(_OldCacheEntryWrapper);
+ LOG(("Creating _OldCacheEntryWrapper %p for descriptor %p", this, desc));
+}
+
+_OldCacheEntryWrapper::_OldCacheEntryWrapper(nsICacheEntryInfo* info)
+: mOldDesc(nullptr), mOldInfo(info)
+{
+ MOZ_COUNT_CTOR(_OldCacheEntryWrapper);
+ LOG(("Creating _OldCacheEntryWrapper %p for info %p", this, info));
+}
+
+_OldCacheEntryWrapper::~_OldCacheEntryWrapper()
+{
+ MOZ_COUNT_DTOR(_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::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,
+ 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() {}
+
+ 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 {
+
+nsresult
+GetCacheSessionNameForStoragePolicy(
+ nsCSubstring const &scheme,
+ nsCacheStoragePolicy storagePolicy,
+ bool isPrivate,
+ NeckoOriginAttributes 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;
+ }
+ }
+ // WYCIWYG
+ else if (scheme.EqualsLiteral("wyciwyg")) {
+ if (isPrivate)
+ sessionName.AssignLiteral("wyciwyg-private");
+ else
+ sessionName.AssignLiteral("wyciwyg");
+ }
+ // 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);
+
+ return NS_OK;
+}
+
+nsresult
+GetCacheSession(nsCSubstring const &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 {
+ rv = GetCacheSessionNameForStoragePolicy(
+ aScheme,
+ storagePolicy,
+ aLoadInfo->IsPrivate(),
+ aLoadInfo->OriginAttributesPtr(),
+ clientId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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(nsCSubstring const& aScheme,
+ nsCSubstring const& aCacheKey,
+ nsICacheEntryOpenCallback* aCallback,
+ nsIApplicationCache* aAppCache,
+ nsILoadContextInfo* aLoadInfo,
+ bool aWriteToDisk,
+ uint32_t aFlags)
+ : 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)
+{
+ MOZ_COUNT_CTOR(_OldCacheLoad);
+}
+
+_OldCacheLoad::~_OldCacheLoad()
+{
+ ProxyReleaseMainThread(mAppCache);
+ MOZ_COUNT_DTOR(_OldCacheLoad);
+}
+
+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 this is only nsWyciwygChannel.
+
+ // 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%08x", 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 = mCallback.forget();
+ mCacheThread = nullptr;
+ nsCOMPtr<nsICacheEntry> entry = mCacheEntry.forget();
+
+ rv = cb->OnCacheEntryAvailable(entry, mNew, mAppCache, mStatus);
+
+ if (NS_FAILED(rv) && entry) {
+ LOG((" cb->OnCacheEntryAvailable failed with rv=0x%08x", 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%08x, result=%d",
+ mCacheEntry.get(), mCallback.get(), mAppCache.get(), 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)
+{
+ MOZ_COUNT_CTOR(_OldStorage);
+}
+
+_OldStorage::~_OldStorage()
+{
+ MOZ_COUNT_DTOR(_OldStorage);
+}
+
+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(EmptyCString(),
+ 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(NS_LITERAL_CSTRING("http"),
+ mWriteToDisk, mLoadInfo, mAppCache,
+ getter_AddRefs(session));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = session->EvictEntries();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCacheSession(NS_LITERAL_CSTRING("wyciwyg"),
+ mWriteToDisk, mLoadInfo, mAppCache,
+ getter_AddRefs(session));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = session->EvictEntries();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This clears any data from scheme other then http, wyciwyg or ftp
+ rv = GetCacheSession(EmptyCString(),
+ 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;
+}
+
+// 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 = aURI->CloneIgnoringRef(getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = noRefURI->GetAsciiSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aCacheKey.IsEmpty()) {
+ aCacheKey.AppendLiteral("uri=");
+ }
+ }
+ else if (aScheme.EqualsLiteral("wyciwyg")) {
+ rv = aURI->GetSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ rv = aURI->GetAsciiSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aCacheKey.Append(uriSpec);
+
+ return NS_OK;
+}
+
+nsresult _OldStorage::ChooseApplicationCache(nsCSubstring const &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..f85b0741ac
--- /dev/null
+++ b/netwerk/cache2/OldWrappers.h
@@ -0,0 +1,284 @@
+// 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, nsIOutputStream * *_retval) override
+ {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval) override
+ {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD GetPredictedDataSize(int64_t *aPredictedDataSize) override
+ {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER :
+ mOldDesc->GetPredictedDataSize(aPredictedDataSize);
+ }
+ NS_IMETHOD SetPredictedDataSize(int64_t aPredictedDataSize) override
+ {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER :
+ mOldDesc->SetPredictedDataSize(aPredictedDataSize);
+ }
+ 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 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 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 OpenInputStream(int64_t offset, nsIInputStream * *_retval) override;
+ NS_IMETHOD OpenOutputStream(int64_t offset, 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;
+};
+
+
+class _OldCacheLoad : public Runnable
+ , public nsICacheListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSICACHELISTENER
+
+ _OldCacheLoad(nsCSubstring const& aScheme,
+ nsCSubstring const& 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(nsCSubstring const &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)
+ {
+ MOZ_COUNT_CTOR(_OldVisitCallbackWrapper);
+ }
+
+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() {}
+ 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..4fc6db59de
--- /dev/null
+++ b/netwerk/cache2/moz.build
@@ -0,0 +1,59 @@
+# -*- 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 += [
+ 'nsICacheEntry.idl',
+ 'nsICacheEntryDoomCallback.idl',
+ 'nsICacheEntryOpenCallback.idl',
+ 'nsICacheStorage.idl',
+ 'nsICacheStorageService.idl',
+ 'nsICacheStorageVisitor.idl',
+ 'nsICacheTesting.idl',
+]
+
+XPIDL_MODULE = 'necko_cache2'
+
+EXPORTS += [
+ 'CacheObserver.h',
+ 'CacheStorageService.h',
+]
+
+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',
+ 'CacheStorage.cpp',
+ 'CacheStorageService.cpp',
+ 'OldWrappers.cpp',
+]
+
+# AppCacheStorage.cpp cannot be built in unified mode because it uses plarena.h.
+SOURCES += [
+ 'AppCacheStorage.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+ '/netwerk/cache',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/cache2/nsICacheEntry.idl b/netwerk/cache2/nsICacheEntry.idl
new file mode 100644
index 0000000000..1885b9423c
--- /dev/null
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -0,0 +1,292 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsICache.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsICacheEntryDoomCallback;
+
+interface nsICacheListener;
+interface nsIFile;
+interface nsICacheEntryMetaDataVisitor;
+
+[scriptable, uuid(607c2a2c-0a48-40b9-a956-8cf2bb9857cf)]
+interface nsICacheEntry : nsISupports
+{
+ /**
+ * 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;
+
+ /**
+ * 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);
+
+ /**
+ * 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.
+ *
+ * @return blocking, buffered output stream.
+ */
+ nsIOutputStream openOutputStream(in long long offset);
+
+ /**
+ * 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/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();
+
+ /**
+ * 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;
+
+ /**
+ * Opens and returns an output stream that a consumer may use to save an
+ * alternate representation of the data.
+ * @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.
+ */
+ nsIOutputStream openAlternativeOutputStream(in ACString type);
+
+ /**
+ * 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);
+
+ /****************************************************************************
+ * 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..68409bb7eb
--- /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..f500a300b9
--- /dev/null
+++ b/netwerk/cache2/nsICacheStorage.idl
@@ -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/. */
+
+#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);
+
+ /**
+ * 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..2be9b1bab0
--- /dev/null
+++ b/netwerk/cache2/nsICacheStorageService.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 "nsISupports.idl"
+
+interface nsICacheStorage;
+interface nsILoadContextInfo;
+interface nsIApplicationCache;
+interface nsIEventTarget;
+interface nsICacheStorageConsumptionObserver;
+
+/**
+ * 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 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);
+};
+
+[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..d6720acce7
--- /dev/null
+++ b/netwerk/cache2/nsICacheStorageVisitor.idl
@@ -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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIFile;
+
+[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);
+
+ /**
+ */
+ 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/CookieServiceChild.cpp b/netwerk/cookie/CookieServiceChild.cpp
new file mode 100644
index 0000000000..9a13b445cf
--- /dev/null
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/CookieServiceChild.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+// Pref string constants
+static const char kPrefCookieBehavior[] = "network.cookie.cookieBehavior";
+static const char kPrefThirdPartySession[] =
+ "network.cookie.thirdparty.sessionOnly";
+
+static CookieServiceChild *gCookieService;
+
+CookieServiceChild*
+CookieServiceChild::GetSingleton()
+{
+ if (!gCookieService)
+ gCookieService = new CookieServiceChild();
+
+ NS_ADDREF(gCookieService);
+ return gCookieService;
+}
+
+NS_IMPL_ISUPPORTS(CookieServiceChild,
+ nsICookieService,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+CookieServiceChild::CookieServiceChild()
+ : mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT)
+ , mThirdPartySession(false)
+{
+ NS_ASSERTION(IsNeckoChild(), "not a child process");
+
+ // This corresponds to Release() in DeallocPCookieService.
+ NS_ADDREF_THIS();
+
+ // Create a child PCookieService actor.
+ NeckoChild::InitNeckoChild();
+ gNeckoChild->SendPCookieServiceConstructor(this);
+
+ // Init our prefs and observer.
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ NS_WARNING_ASSERTION(prefBranch, "no prefservice");
+ if (prefBranch) {
+ prefBranch->AddObserver(kPrefCookieBehavior, this, true);
+ prefBranch->AddObserver(kPrefThirdPartySession, this, true);
+ PrefChanged(prefBranch);
+ }
+}
+
+CookieServiceChild::~CookieServiceChild()
+{
+ gCookieService = nullptr;
+}
+
+void
+CookieServiceChild::PrefChanged(nsIPrefBranch *aPrefBranch)
+{
+ int32_t val;
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
+ mCookieBehavior =
+ val >= nsICookieService::BEHAVIOR_ACCEPT &&
+ val <= nsICookieService::BEHAVIOR_LIMIT_FOREIGN
+ ? val : nsICookieService::BEHAVIOR_ACCEPT;
+
+ bool boolval;
+ if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
+ mThirdPartySession = !!boolval;
+
+ if (!mThirdPartyUtil && RequireThirdPartyCheck()) {
+ mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
+ NS_ASSERTION(mThirdPartyUtil, "require ThirdPartyUtil service");
+ }
+}
+
+bool
+CookieServiceChild::RequireThirdPartyCheck()
+{
+ return mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
+ mCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN ||
+ mThirdPartySession;
+}
+
+nsresult
+CookieServiceChild::GetCookieStringInternal(nsIURI *aHostURI,
+ nsIChannel *aChannel,
+ char **aCookieString,
+ bool aFromHttp)
+{
+ NS_ENSURE_ARG(aHostURI);
+ NS_ENSURE_ARG_POINTER(aCookieString);
+
+ *aCookieString = nullptr;
+
+ // 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;
+
+ // Determine whether the request is foreign. Failure is acceptable.
+ bool isForeign = true;
+ if (RequireThirdPartyCheck())
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
+
+ URIParams uriParams;
+ SerializeURI(aHostURI, uriParams);
+
+ mozilla::NeckoOriginAttributes attrs;
+ if (aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (loadInfo) {
+ attrs = loadInfo->GetOriginAttributes();
+ }
+ }
+
+ // Synchronously call the parent.
+ nsAutoCString result;
+ SendGetCookieString(uriParams, !!isForeign, aFromHttp, attrs, &result);
+ if (!result.IsEmpty())
+ *aCookieString = ToNewCString(result);
+
+ return NS_OK;
+}
+
+nsresult
+CookieServiceChild::SetCookieStringInternal(nsIURI *aHostURI,
+ nsIChannel *aChannel,
+ const char *aCookieString,
+ const char *aServerTime,
+ bool aFromHttp)
+{
+ NS_ENSURE_ARG(aHostURI);
+ NS_ENSURE_ARG_POINTER(aCookieString);
+
+ // 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;
+
+ // Determine whether the request is foreign. Failure is acceptable.
+ bool isForeign = true;
+ if (RequireThirdPartyCheck())
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
+
+ nsDependentCString cookieString(aCookieString);
+ nsDependentCString serverTime;
+ if (aServerTime)
+ serverTime.Rebind(aServerTime);
+
+ URIParams uriParams;
+ SerializeURI(aHostURI, uriParams);
+
+ mozilla::NeckoOriginAttributes attrs;
+ if (aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (loadInfo) {
+ attrs = loadInfo->GetOriginAttributes();
+ }
+ }
+
+ // Synchronously call the parent.
+ SendSetCookieString(uriParams, !!isForeign, cookieString, serverTime,
+ aFromHttp, attrs);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ NS_ASSERTION(strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
+ "not a pref change topic!");
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ if (prefBranch)
+ PrefChanged(prefBranch);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::GetCookieString(nsIURI *aHostURI,
+ nsIChannel *aChannel,
+ char **aCookieString)
+{
+ return GetCookieStringInternal(aHostURI, aChannel, aCookieString, false);
+}
+
+NS_IMETHODIMP
+CookieServiceChild::GetCookieStringFromHttp(nsIURI *aHostURI,
+ nsIURI *aFirstURI,
+ nsIChannel *aChannel,
+ char **aCookieString)
+{
+ return GetCookieStringInternal(aHostURI, aChannel, aCookieString, true);
+}
+
+NS_IMETHODIMP
+CookieServiceChild::SetCookieString(nsIURI *aHostURI,
+ nsIPrompt *aPrompt,
+ const char *aCookieString,
+ nsIChannel *aChannel)
+{
+ return SetCookieStringInternal(aHostURI, aChannel, aCookieString,
+ nullptr, false);
+}
+
+NS_IMETHODIMP
+CookieServiceChild::SetCookieStringFromHttp(nsIURI *aHostURI,
+ nsIURI *aFirstURI,
+ nsIPrompt *aPrompt,
+ const char *aCookieString,
+ const char *aServerTime,
+ nsIChannel *aChannel)
+{
+ return SetCookieStringInternal(aHostURI, aChannel, aCookieString,
+ aServerTime, true);
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/cookie/CookieServiceChild.h b/netwerk/cookie/CookieServiceChild.h
new file mode 100644
index 0000000000..bc6efedf41
--- /dev/null
+++ b/netwerk/cookie/CookieServiceChild.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_net_CookieServiceChild_h__
+#define mozilla_net_CookieServiceChild_h__
+
+#include "mozilla/net/PCookieServiceChild.h"
+#include "nsICookieService.h"
+#include "nsIObserver.h"
+#include "nsIPrefBranch.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+class CookieServiceChild : public PCookieServiceChild
+ , public nsICookieService
+ , public nsIObserver
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOOKIESERVICE
+ NS_DECL_NSIOBSERVER
+
+ CookieServiceChild();
+
+ static CookieServiceChild* GetSingleton();
+
+protected:
+ virtual ~CookieServiceChild();
+
+ void SerializeURIs(nsIURI *aHostURI,
+ nsIChannel *aChannel,
+ nsCString &aHostSpec,
+ nsCString &aHostCharset,
+ nsCString &aOriginatingSpec,
+ nsCString &aOriginatingCharset);
+
+ nsresult GetCookieStringInternal(nsIURI *aHostURI,
+ nsIChannel *aChannel,
+ char **aCookieString,
+ bool aFromHttp);
+
+ nsresult SetCookieStringInternal(nsIURI *aHostURI,
+ nsIChannel *aChannel,
+ const char *aCookieString,
+ const char *aServerTime,
+ bool aFromHttp);
+
+ void PrefChanged(nsIPrefBranch *aPrefBranch);
+
+ bool RequireThirdPartyCheck();
+
+ nsCOMPtr<mozIThirdPartyUtil> mThirdPartyUtil;
+ uint8_t mCookieBehavior;
+ bool mThirdPartySession;
+};
+
+} // 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..005ef44b4a
--- /dev/null
+++ b/netwerk/cookie/CookieServiceParent.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/CookieServiceParent.h"
+#include "mozilla/dom/PContentParent.h"
+#include "mozilla/net/NeckoParent.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsCookieService.h"
+#include "nsIChannel.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla::ipc;
+using mozilla::BasePrincipal;
+using mozilla::NeckoOriginAttributes;
+using mozilla::PrincipalOriginAttributes;
+using mozilla::dom::PContentParent;
+using mozilla::net::NeckoParent;
+
+namespace {
+
+// Ignore failures from this function, as they only affect whether we do or
+// don't show a dialog box in private browsing mode if the user sets a pref.
+void
+CreateDummyChannel(nsIURI* aHostURI, NeckoOriginAttributes& aAttrs, bool aIsPrivate,
+ nsIChannel** aChannel)
+{
+ MOZ_ASSERT(aAttrs.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
+
+ PrincipalOriginAttributes attrs;
+ attrs.InheritFromNecko(aAttrs);
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(aHostURI, attrs);
+ if (!principal) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> dummyURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(dummyURI), "about:blank");
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // The following channel is never openend, so it does not matter what
+ // securityFlags we pass; let's follow the principle of least privilege.
+ nsCOMPtr<nsIChannel> dummyChannel;
+ NS_NewChannel(getter_AddRefs(dummyChannel), dummyURI, principal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_INVALID);
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(dummyChannel);
+ if (!pbChannel) {
+ return;
+ }
+
+ pbChannel->SetPrivate(aIsPrivate);
+ dummyChannel.forget(aChannel);
+ return;
+}
+
+}
+
+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 nsCookieService instance directly, so we can call internal methods.
+ mCookieService =
+ already_AddRefed<nsCookieService>(nsCookieService::GetSingleton());
+ NS_ASSERTION(mCookieService, "couldn't get nsICookieService");
+}
+
+CookieServiceParent::~CookieServiceParent()
+{
+}
+
+void
+CookieServiceParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ // Nothing needed here. Called right before destructor since this is a
+ // non-refcounted class.
+}
+
+bool
+CookieServiceParent::RecvGetCookieString(const URIParams& aHost,
+ const bool& aIsForeign,
+ const bool& aFromHttp,
+ const NeckoOriginAttributes& aAttrs,
+ nsCString* aResult)
+{
+ if (!mCookieService)
+ return true;
+
+ // Deserialize URI. Having a host URI is mandatory and should always be
+ // provided by the child; thus we consider failure fatal.
+ nsCOMPtr<nsIURI> hostURI = DeserializeURI(aHost);
+ if (!hostURI)
+ return false;
+
+ bool isPrivate = aAttrs.mPrivateBrowsingId > 0;
+ mCookieService->GetCookieStringInternal(hostURI, aIsForeign, aFromHttp, aAttrs,
+ isPrivate, *aResult);
+ return true;
+}
+
+bool
+CookieServiceParent::RecvSetCookieString(const URIParams& aHost,
+ const bool& aIsForeign,
+ const nsCString& aCookieString,
+ const nsCString& aServerTime,
+ const bool& aFromHttp,
+ const NeckoOriginAttributes& aAttrs)
+{
+ if (!mCookieService)
+ return true;
+
+ // Deserialize URI. Having a host URI is mandatory and should always be
+ // provided by the child; thus we consider failure fatal.
+ nsCOMPtr<nsIURI> hostURI = DeserializeURI(aHost);
+ if (!hostURI)
+ return false;
+
+ bool isPrivate = aAttrs.mPrivateBrowsingId > 0;
+
+ // This is a gross hack. We've already computed everything we need to know
+ // for whether to set this cookie or not, but we need to communicate all of
+ // this information through to nsICookiePermission, which indirectly
+ // computes the information from the channel. We only care about the
+ // aIsPrivate argument as nsCookieService::SetCookieStringInternal deals
+ // with aIsForeign before we have to worry about nsCookiePermission trying
+ // to use the channel to inspect it.
+ nsCOMPtr<nsIChannel> dummyChannel;
+ CreateDummyChannel(hostURI, const_cast<NeckoOriginAttributes&>(aAttrs),
+ isPrivate, getter_AddRefs(dummyChannel));
+
+ // NB: dummyChannel could be null if something failed in CreateDummyChannel.
+ nsDependentCString cookieString(aCookieString, 0);
+ mCookieService->SetCookieStringInternal(hostURI, aIsForeign, cookieString,
+ aServerTime, aFromHttp, aAttrs,
+ isPrivate, dummyChannel);
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/cookie/CookieServiceParent.h b/netwerk/cookie/CookieServiceParent.h
new file mode 100644
index 0000000000..7be2c97e9d
--- /dev/null
+++ b/netwerk/cookie/CookieServiceParent.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 nsCookieService;
+namespace mozilla { class NeckoOriginAttributes; }
+
+namespace mozilla {
+namespace net {
+
+class CookieServiceParent : public PCookieServiceParent
+{
+public:
+ CookieServiceParent();
+ virtual ~CookieServiceParent();
+
+protected:
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool RecvGetCookieString(const URIParams& aHost,
+ const bool& aIsForeign,
+ const bool& aFromHttp,
+ const NeckoOriginAttributes& aAttrs,
+ nsCString* aResult) override;
+
+ virtual bool RecvSetCookieString(const URIParams& aHost,
+ const bool& aIsForeign,
+ const nsCString& aCookieString,
+ const nsCString& aServerTime,
+ const bool& aFromHttp,
+ const NeckoOriginAttributes& aAttrs) override;
+
+ RefPtr<nsCookieService> mCookieService;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieServiceParent_h
+
diff --git a/netwerk/cookie/PCookieService.ipdl b/netwerk/cookie/PCookieService.ipdl
new file mode 100644
index 0000000000..7d01096d4e
--- /dev/null
+++ b/netwerk/cookie/PCookieService.ipdl
@@ -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 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;
+
+using mozilla::NeckoOriginAttributes from "mozilla/ipc/BackgroundUtils.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 and nsICookieManager2 interfaces) 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:
+
+ /*
+ * Get the complete cookie string associated with the URI. This is a sync
+ * call in order to avoid race conditions -- for instance, an HTTP response
+ * on the parent and script access on the child.
+ *
+ * @param host
+ * Same as the 'aURI' argument to nsICookieService.getCookieString.
+ * @param isForeign
+ * True if the the request is third party, for purposes of allowing
+ * access to cookies. This should be obtained from
+ * mozIThirdPartyUtil.isThirdPartyChannel. Third party requests may be
+ * rejected depending on user preferences; if those checks are
+ * disabled, this parameter is ignored.
+ * @param fromHttp
+ * Whether the result is for an HTTP request header. This should be
+ * true for nsICookieService.getCookieStringFromHttp calls, false
+ * otherwise.
+ * @param attrs
+ * The origin attributes from the HTTP channel or document that the
+ * cookie is being set on.
+ *
+ * @see nsICookieService.getCookieString
+ * @see nsICookieService.getCookieStringFromHttp
+ * @see mozIThirdPartyUtil.isThirdPartyChannel
+ *
+ * @return the resulting cookie string.
+ */
+ nested(inside_cpow) sync GetCookieString(URIParams host,
+ bool isForeign,
+ bool fromHttp,
+ NeckoOriginAttributes attrs)
+ returns (nsCString result);
+
+ /*
+ * Set a cookie string.
+ *
+ * @param host
+ * Same as the 'aURI' argument to nsICookieService.setCookieString.
+ * @param isForeign
+ * True if the the request is third party, for purposes of allowing
+ * access to cookies. This should be obtained from
+ * mozIThirdPartyUtil.isThirdPartyChannel. Third party requests may be
+ * rejected depending on user preferences; if those checks are
+ * disabled, this parameter is ignored.
+ * @param cookieString
+ * Same as the 'aCookie' argument to nsICookieService.setCookieString.
+ * @param serverTime
+ * Same as the 'aServerTime' argument to
+ * nsICookieService.setCookieStringFromHttp. If the string is empty or
+ * null (e.g. for non-HTTP requests), the current local time is used.
+ * @param fromHttp
+ * Whether the result is for an HTTP request header. This should be
+ * true for nsICookieService.setCookieStringFromHttp calls, false
+ * otherwise.
+ * @param attrs
+ * The origin attributes from the HTTP channel or document that the
+ * cookie is being set on.
+ *
+ * @see nsICookieService.setCookieString
+ * @see nsICookieService.setCookieStringFromHttp
+ * @see mozIThirdPartyUtil.isThirdPartyChannel
+ */
+ nested(inside_cpow) async SetCookieString(URIParams host,
+ bool isForeign,
+ nsCString cookieString,
+ nsCString serverTime,
+ bool fromHttp,
+ NeckoOriginAttributes attrs);
+
+ async __delete__();
+};
+
+}
+}
+
diff --git a/netwerk/cookie/moz.build b/netwerk/cookie/moz.build
new file mode 100644
index 0000000000..207790008e
--- /dev/null
+++ b/netwerk/cookie/moz.build
@@ -0,0 +1,55 @@
+# -*- 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/.
+
+# export required interfaces, even if --disable-cookies has been given
+XPIDL_SOURCES += [
+ 'nsICookie.idl',
+ 'nsICookie2.idl',
+ 'nsICookieManager.idl',
+ 'nsICookieManager2.idl',
+ 'nsICookiePermission.idl',
+ 'nsICookieService.idl',
+]
+
+XPIDL_MODULE = 'necko_cookie'
+
+if CONFIG['NECKO_COOKIES']:
+ EXPORTS.mozilla.net = [
+ 'CookieServiceChild.h',
+ 'CookieServiceParent.h',
+ ]
+ UNIFIED_SOURCES += [
+ 'CookieServiceChild.cpp',
+ 'CookieServiceParent.cpp',
+ 'nsCookie.cpp',
+ ]
+ # nsCookieService.cpp can't be unified because of symbol conflicts
+ SOURCES += [
+ 'nsCookieService.cpp',
+ ]
+ LOCAL_INCLUDES += [
+ '/intl/uconv',
+ ]
+
+ XPCSHELL_TESTS_MANIFESTS += [
+ 'test/unit/xpcshell.ini',
+ 'test/unit_ipc/xpcshell.ini',
+ ]
+
+ BROWSER_CHROME_MANIFESTS += [
+ 'test/browser/browser.ini',
+ ]
+
+IPDL_SOURCES = [
+ 'PCookieService.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/cookie/nsCookie.cpp b/netwerk/cookie/nsCookie.cpp
new file mode 100644
index 0000000000..5afe6fe80b
--- /dev/null
+++ b/netwerk/cookie/nsCookie.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ToJSValue.h"
+#include "nsAutoPtr.h"
+#include "nsCookie.h"
+#include "nsUTF8ConverterService.h"
+#include <stdlib.h>
+
+/******************************************************************************
+ * nsCookie:
+ * string helper impl
+ ******************************************************************************/
+
+// copy aSource strings into contiguous storage provided in aDest1,
+// providing terminating nulls for each destination string.
+static inline void
+StrBlockCopy(const nsACString &aSource1,
+ const nsACString &aSource2,
+ const nsACString &aSource3,
+ const nsACString &aSource4,
+ char *&aDest1,
+ char *&aDest2,
+ char *&aDest3,
+ char *&aDest4,
+ char *&aDestEnd)
+{
+ char *toBegin = aDest1;
+ nsACString::const_iterator fromBegin, fromEnd;
+
+ *copy_string(aSource1.BeginReading(fromBegin), aSource1.EndReading(fromEnd), toBegin) = char(0);
+ aDest2 = ++toBegin;
+ *copy_string(aSource2.BeginReading(fromBegin), aSource2.EndReading(fromEnd), toBegin) = char(0);
+ aDest3 = ++toBegin;
+ *copy_string(aSource3.BeginReading(fromBegin), aSource3.EndReading(fromEnd), toBegin) = char(0);
+ aDest4 = ++toBegin;
+ *copy_string(aSource4.BeginReading(fromBegin), aSource4.EndReading(fromEnd), toBegin) = char(0);
+ aDestEnd = toBegin;
+}
+
+/******************************************************************************
+ * nsCookie:
+ * creation helper
+ ******************************************************************************/
+
+// This is a counter that keeps track of the last used creation time, each time
+// we create a new nsCookie. 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
+nsCookie::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;
+}
+
+nsCookie *
+nsCookie::Create(const nsACString &aName,
+ const nsACString &aValue,
+ const nsACString &aHost,
+ const nsACString &aPath,
+ int64_t aExpiry,
+ int64_t aLastAccessed,
+ int64_t aCreationTime,
+ bool aIsSession,
+ bool aIsSecure,
+ bool aIsHttpOnly,
+ const OriginAttributes& aOriginAttributes)
+{
+ // Ensure mValue contains a valid UTF-8 sequence. Otherwise XPConnect will
+ // truncate the string after the first invalid octet.
+ RefPtr<nsUTF8ConverterService> converter = new nsUTF8ConverterService();
+ nsAutoCString aUTF8Value;
+ converter->ConvertStringToUTF8(aValue, "UTF-8", false, true, 1, aUTF8Value);
+
+ // find the required string buffer size, adding 4 for the terminating nulls
+ const uint32_t stringLength = aName.Length() + aUTF8Value.Length() +
+ aHost.Length() + aPath.Length() + 4;
+
+ // allocate contiguous space for the nsCookie and its strings -
+ // we store the strings in-line with the nsCookie to save allocations
+ void *place = ::operator new(sizeof(nsCookie) + stringLength);
+ if (!place)
+ return nullptr;
+
+ // assign string members
+ char *name, *value, *host, *path, *end;
+ name = static_cast<char *>(place) + sizeof(nsCookie);
+ StrBlockCopy(aName, aUTF8Value, aHost, aPath,
+ name, value, host, path, end);
+
+ // If the creationTime given to us is higher than the running maximum, update
+ // our maximum.
+ if (aCreationTime > gLastCreationTime)
+ gLastCreationTime = aCreationTime;
+
+ // construct the cookie. placement new, oh yeah!
+ return new (place) nsCookie(name, value, host, path, end,
+ aExpiry, aLastAccessed, aCreationTime,
+ aIsSession, aIsSecure, aIsHttpOnly,
+ aOriginAttributes);
+}
+
+size_t
+nsCookie::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // There is no need to measure the sizes of the individual string
+ // members, since the strings are stored in-line with the nsCookie.
+ return aMallocSizeOf(this);
+}
+
+bool
+nsCookie::IsStale() const
+{
+ int64_t currentTimeInUsec = PR_Now();
+
+ return currentTimeInUsec - LastAccessed() > mCookieStaleThreshold * PR_USEC_PER_SEC;
+}
+
+/******************************************************************************
+ * nsCookie:
+ * xpcom impl
+ ******************************************************************************/
+
+// xpcom getters
+NS_IMETHODIMP nsCookie::GetName(nsACString &aName) { aName = Name(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetValue(nsACString &aValue) { aValue = Value(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetHost(nsACString &aHost) { aHost = Host(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetRawHost(nsACString &aHost) { aHost = RawHost(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetPath(nsACString &aPath) { aPath = Path(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetExpiry(int64_t *aExpiry) { *aExpiry = Expiry(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetIsSession(bool *aIsSession) { *aIsSession = IsSession(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetIsDomain(bool *aIsDomain) { *aIsDomain = IsDomain(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetIsSecure(bool *aIsSecure) { *aIsSecure = IsSecure(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetIsHttpOnly(bool *aHttpOnly) { *aHttpOnly = IsHttpOnly(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetStatus(nsCookieStatus *aStatus) { *aStatus = 0; return NS_OK; }
+NS_IMETHODIMP nsCookie::GetPolicy(nsCookiePolicy *aPolicy) { *aPolicy = 0; return NS_OK; }
+NS_IMETHODIMP nsCookie::GetCreationTime(int64_t *aCreation){ *aCreation = CreationTime(); return NS_OK; }
+NS_IMETHODIMP nsCookie::GetLastAccessed(int64_t *aTime) { *aTime = LastAccessed(); return NS_OK; }
+
+NS_IMETHODIMP
+nsCookie::GetOriginAttributes(JSContext *aCx, JS::MutableHandle<JS::Value> aVal)
+{
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// compatibility method, for use with the legacy nsICookie interface.
+// here, expires == 0 denotes a session cookie.
+NS_IMETHODIMP
+nsCookie::GetExpires(uint64_t *aExpires)
+{
+ if (IsSession()) {
+ *aExpires = 0;
+ } else {
+ *aExpires = Expiry() > 0 ? Expiry() : 1;
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsCookie, nsICookie2, nsICookie)
diff --git a/netwerk/cookie/nsCookie.h b/netwerk/cookie/nsCookie.h
new file mode 100644
index 0000000000..812db3f328
--- /dev/null
+++ b/netwerk/cookie/nsCookie.h
@@ -0,0 +1,140 @@
+/* -*- 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 nsCookie_h__
+#define nsCookie_h__
+
+#include "nsICookie.h"
+#include "nsICookie2.h"
+#include "nsString.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Preferences.h"
+
+using mozilla::OriginAttributes;
+
+/**
+ * The nsCookie class is the main cookie storage medium for use within cookie
+ * code. It implements nsICookie2, which extends nsICookie, a frozen interface
+ * for xpcom access of cookie objects.
+ */
+
+/******************************************************************************
+ * nsCookie:
+ * implementation
+ ******************************************************************************/
+
+class nsCookie : public nsICookie2
+{
+ public:
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOOKIE
+ NS_DECL_NSICOOKIE2
+
+ private:
+ // for internal use only. see nsCookie::Create().
+ nsCookie(const char *aName,
+ const char *aValue,
+ const char *aHost,
+ const char *aPath,
+ const char *aEnd,
+ int64_t aExpiry,
+ int64_t aLastAccessed,
+ int64_t aCreationTime,
+ bool aIsSession,
+ bool aIsSecure,
+ bool aIsHttpOnly,
+ const OriginAttributes& aOriginAttributes)
+ : mName(aName)
+ , mValue(aValue)
+ , mHost(aHost)
+ , mPath(aPath)
+ , mEnd(aEnd)
+ , mExpiry(aExpiry)
+ , mLastAccessed(aLastAccessed)
+ , mCreationTime(aCreationTime)
+ // Defaults to 60s
+ , mCookieStaleThreshold(mozilla::Preferences::GetInt("network.cookie.staleThreshold", 60))
+ , mIsSession(aIsSession)
+ , mIsSecure(aIsSecure)
+ , mIsHttpOnly(aIsHttpOnly)
+ , mOriginAttributes(aOriginAttributes)
+ {
+ }
+
+ public:
+ // Generate a unique and monotonically increasing creation time. See comment
+ // in nsCookie.cpp.
+ static int64_t GenerateUniqueCreationTime(int64_t aCreationTime);
+
+ // public helper to create an nsCookie object. use |operator delete|
+ // to destroy an object created by this method.
+ static nsCookie * Create(const nsACString &aName,
+ const nsACString &aValue,
+ const nsACString &aHost,
+ const nsACString &aPath,
+ int64_t aExpiry,
+ int64_t aLastAccessed,
+ int64_t aCreationTime,
+ bool aIsSession,
+ bool aIsSecure,
+ bool aIsHttpOnly,
+ const OriginAttributes& aOriginAttributes);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // fast (inline, non-xpcom) getters
+ inline const nsDependentCString Name() const { return nsDependentCString(mName, mValue - 1); }
+ inline const nsDependentCString Value() const { return nsDependentCString(mValue, mHost - 1); }
+ inline const nsDependentCString Host() const { return nsDependentCString(mHost, mPath - 1); }
+ inline const nsDependentCString RawHost() const { return nsDependentCString(IsDomain() ? mHost + 1 : mHost, mPath - 1); }
+ inline const nsDependentCString Path() const { return nsDependentCString(mPath, mEnd); }
+ inline int64_t Expiry() const { return mExpiry; } // in seconds
+ inline int64_t LastAccessed() const { return mLastAccessed; } // in microseconds
+ inline int64_t CreationTime() const { return mCreationTime; } // in microseconds
+ inline bool IsSession() const { return mIsSession; }
+ inline bool IsDomain() const { return *mHost == '.'; }
+ inline bool IsSecure() const { return mIsSecure; }
+ inline bool IsHttpOnly() const { return mIsHttpOnly; }
+
+ // setters
+ inline void SetExpiry(int64_t aExpiry) { mExpiry = aExpiry; }
+ inline void SetLastAccessed(int64_t aTime) { mLastAccessed = aTime; }
+ inline void SetIsSession(bool aIsSession) { mIsSession = aIsSession; }
+ // Set the creation time manually, overriding the monotonicity checks in
+ // Create(). Use with caution!
+ inline void SetCreationTime(int64_t aTime) { mCreationTime = aTime; }
+
+ bool IsStale() const;
+
+ protected:
+ virtual ~nsCookie() {}
+
+ private:
+ // member variables
+ // we use char* ptrs to store the strings in a contiguous block,
+ // so we save on the overhead of using nsCStrings. However, we
+ // store a terminating null for each string, so we can hand them
+ // out as nsAFlatCStrings.
+ //
+ // Please update SizeOfIncludingThis if this strategy changes.
+ const char *mName;
+ const char *mValue;
+ const char *mHost;
+ const char *mPath;
+ const char *mEnd;
+ int64_t mExpiry;
+ int64_t mLastAccessed;
+ int64_t mCreationTime;
+ int64_t mCookieStaleThreshold;
+ bool mIsSession;
+ bool mIsSecure;
+ bool mIsHttpOnly;
+ mozilla::OriginAttributes mOriginAttributes;
+};
+
+#endif // nsCookie_h__
diff --git a/netwerk/cookie/nsCookieService.cpp b/netwerk/cookie/nsCookieService.cpp
new file mode 100644
index 0000000000..cf1d91e2df
--- /dev/null
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -0,0 +1,5164 @@
+/* -*- 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 "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/net/CookieServiceChild.h"
+#include "mozilla/net/NeckoCommon.h"
+
+#include "nsCookieService.h"
+#include "nsContentUtils.h"
+#include "nsIServiceManager.h"
+
+#include "nsIIOService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIScriptError.h"
+#include "nsICookiePermission.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsILineInputStream.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIIDNService.h"
+#include "mozIThirdPartyUtil.h"
+
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsIMutableArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsEnumeratorUtils.h"
+#include "nsAutoPtr.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "prprf.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIInputStream.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsNetCID.h"
+#include "mozilla/storage.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Telemetry.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "nsIConsoleService.h"
+#include "nsVariant.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+// Create key from baseDomain that will access the default cookie namespace.
+// TODO: When we figure out what the API will look like for nsICookieManager{2}
+// on content processes (see bug 777620), change to use the appropriate app
+// namespace. For now those IDLs aren't supported on child processes.
+#define DEFAULT_APP_KEY(baseDomain) \
+ nsCookieKey(baseDomain, NeckoOriginAttributes())
+
+/******************************************************************************
+ * nsCookieService impl:
+ * useful types & constants
+ ******************************************************************************/
+
+static nsCookieService *gCookieService;
+
+// XXX_hack. See bug 178993.
+// This is a hack to hide HttpOnly cookies from older browsers
+#define HTTP_ONLY_PREFIX "#HttpOnly_"
+
+#define COOKIES_FILE "cookies.sqlite"
+#define COOKIES_SCHEMA_VERSION 7
+
+// parameter indexes; see EnsureReadDomain, EnsureReadComplete and
+// ReadCookieDBListener::HandleResult
+#define IDX_NAME 0
+#define IDX_VALUE 1
+#define IDX_HOST 2
+#define IDX_PATH 3
+#define IDX_EXPIRY 4
+#define IDX_LAST_ACCESSED 5
+#define IDX_CREATION_TIME 6
+#define IDX_SECURE 7
+#define IDX_HTTPONLY 8
+#define IDX_BASE_DOMAIN 9
+#define IDX_ORIGIN_ATTRIBUTES 10
+
+static const int64_t kCookiePurgeAge =
+ int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
+
+#define OLD_COOKIE_FILE_NAME "cookies.txt"
+
+#undef LIMIT
+#define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
+
+#undef ADD_TEN_PERCENT
+#define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
+
+// 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 kMaxNumberOfCookies = 3000;
+static const uint32_t kMaxCookiesPerHost = 150;
+static const uint32_t kMaxBytesPerCookie = 4096;
+static const uint32_t kMaxBytesPerPath = 1024;
+
+// pref string constants
+static const char kPrefCookieBehavior[] = "network.cookie.cookieBehavior";
+static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
+static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost";
+static const char kPrefCookiePurgeAge[] = "network.cookie.purgeAge";
+static const char kPrefThirdPartySession[] = "network.cookie.thirdparty.sessionOnly";
+static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
+
+// For telemetry COOKIE_LEAVE_SECURE_ALONE
+#define BLOCKED_SECURE_SET_FROM_HTTP 0
+#define BLOCKED_DOWNGRADE_SECURE 1
+#define DOWNGRADE_SECURE_FROM_SECURE 2
+#define EVICTED_NEWER_INSECURE 3
+#define EVICTED_OLDEST_COOKIE 4
+#define EVICTED_PREFERRED_COOKIE 5
+#define EVICTING_SECURE_BLOCKED 6
+
+static void
+bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
+ const nsCookieKey &aKey,
+ const nsCookie *aCookie);
+
+// struct for temporarily storing cookie attributes during header parsing
+struct nsCookieAttributes
+{
+ nsAutoCString name;
+ nsAutoCString value;
+ nsAutoCString host;
+ nsAutoCString path;
+ nsAutoCString expires;
+ nsAutoCString maxage;
+ int64_t expiryTime;
+ bool isSession;
+ bool isSecure;
+ bool isHttpOnly;
+};
+
+// stores the nsCookieEntry 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 nsListIter
+{
+ // default (non-initializing) constructor.
+ nsListIter() = default;
+
+ // explicit constructor to a given iterator state with entryclass 'aEntry'
+ // and index 'aIndex'.
+ explicit
+ nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex)
+ : entry(aEntry)
+ , index(aIndex)
+ {
+ }
+
+ // get the nsCookie * the iterator currently points to.
+ nsCookie * Cookie() const
+ {
+ return entry->GetCookies()[index];
+ }
+
+ nsCookieEntry *entry;
+ nsCookieEntry::IndexType index;
+};
+
+/******************************************************************************
+ * Cookie logging handlers
+ * used for logging in nsCookieService
+ ******************************************************************************/
+
+// logging handlers
+#ifdef MOZ_LOGGING
+// in order to do logging, the following environment variables need to be set:
+//
+// set MOZ_LOG=cookie:3 -- shows rejected cookies
+// set MOZ_LOG=cookie:4 -- shows accepted and rejected cookies
+// set MOZ_LOG_FILE=cookie.log
+//
+#include "mozilla/Logging.h"
+#endif
+
+// define logging macros for convenience
+#define SET_COOKIE true
+#define GET_COOKIE false
+
+static LazyLogModule gCookieLog("cookie");
+
+#define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
+#define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
+
+#define COOKIE_LOGEVICTED(a, details) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) \
+ 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
+
+static void
+LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *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));
+
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
+ char timeString[40];
+ PR_FormatTimeUSEnglish(timeString, 40, "%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
+LogCookie(nsCookie *aCookie)
+{
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
+ char timeString[40];
+ PR_FormatTimeUSEnglish(timeString, 40, "%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, 40, "%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, 40, "%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"));
+ }
+}
+
+static void
+LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *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));
+ 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
+LogEvicted(nsCookie *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"));
+}
+
+// inline wrappers to make passing in nsAFlatCStrings easier
+static inline void
+LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason)
+{
+ LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
+}
+
+static inline void
+LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie, bool aReplacing)
+{
+ LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
+}
+
+#ifdef DEBUG
+#define NS_ASSERT_SUCCESS(res) \
+ PR_BEGIN_MACRO \
+ nsresult __rv = res; /* Do not evaluate |res| more than once! */ \
+ if (NS_FAILED(__rv)) { \
+ char *msg = PR_smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%X", \
+ #res, __rv); \
+ NS_ASSERTION(NS_SUCCEEDED(__rv), msg); \
+ PR_smprintf_free(msg); \
+ } \
+ PR_END_MACRO
+#else
+#define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
+#endif
+
+/******************************************************************************
+ * DBListenerErrorHandler impl:
+ * Parent class for our async storage listeners that handles the logging of
+ * errors.
+ ******************************************************************************/
+class DBListenerErrorHandler : public mozIStorageStatementCallback
+{
+protected:
+ explicit DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { }
+ RefPtr<DBState> mDBState;
+ 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.
+ gCookieService->HandleCorruptDB(mDBState);
+
+ 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(DBState* dbState) : DBListenerErrorHandler(dbState) { }
+ NS_IMETHOD HandleResult(mozIStorageResultSet*) override
+ {
+ NS_NOTREACHED("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 corruptFlag say
+ // so.
+ if (mDBState->corruptFlag == DBState::REBUILDING &&
+ aReason == mozIStorageStatementCallback::REASON_FINISHED) {
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
+ mDBState->corruptFlag = DBState::OK;
+ }
+ 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(DBState* dbState) : DBListenerErrorHandler(dbState) { }
+ NS_IMETHOD HandleResult(mozIStorageResultSet*) override
+ {
+ NS_NOTREACHED("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(DBState* dbState) : DBListenerErrorHandler(dbState) { }
+ NS_IMETHOD HandleResult(mozIStorageResultSet*) override
+ {
+ NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
+ return NS_OK;
+ }
+ NS_IMETHOD HandleCompletion(uint16_t aReason) override
+ {
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
+
+/******************************************************************************
+ * ReadCookieDBListener impl:
+ * mozIStorageStatementCallback used to track asynchronous removal operations.
+ ******************************************************************************/
+class ReadCookieDBListener final : public DBListenerErrorHandler
+{
+private:
+ const char *GetOpType() override { return "READ"; }
+ bool mCanceled;
+
+ ~ReadCookieDBListener() = default;
+
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit ReadCookieDBListener(DBState* dbState)
+ : DBListenerErrorHandler(dbState)
+ , mCanceled(false)
+ {
+ }
+
+ void Cancel() { mCanceled = true; }
+
+ NS_IMETHOD HandleResult(mozIStorageResultSet *aResult) override
+ {
+ nsCOMPtr<mozIStorageRow> row;
+
+ while (true) {
+ DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
+ NS_ASSERT_SUCCESS(rv);
+
+ if (!row)
+ break;
+
+ CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
+ row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain);
+
+ nsAutoCString suffix;
+ row->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
+ DebugOnly<bool> success = tuple->key.mOriginAttributes.PopulateFromSuffix(suffix);
+ MOZ_ASSERT(success);
+
+ tuple->cookie =
+ gCookieService->GetCookieFromRow(row, tuple->key.mOriginAttributes);
+ }
+
+ return NS_OK;
+ }
+ NS_IMETHOD HandleCompletion(uint16_t aReason) override
+ {
+ // Process the completion of the read operation. If we have been canceled,
+ // we cannot assume that the cookieservice still has an open connection
+ // or that it even refers to the same database, so we must return early.
+ // Conversely, the cookieservice guarantees that if we have not been
+ // canceled, the database connection is still alive and we can safely
+ // operate on it.
+
+ if (mCanceled) {
+ // We may receive a REASON_FINISHED after being canceled;
+ // tweak the reason accordingly.
+ aReason = mozIStorageStatementCallback::REASON_CANCELED;
+ }
+
+ switch (aReason) {
+ case mozIStorageStatementCallback::REASON_FINISHED:
+ gCookieService->AsyncReadComplete();
+ break;
+ case mozIStorageStatementCallback::REASON_CANCELED:
+ // Nothing more to do here. The partially read data has already been
+ // thrown away.
+ COOKIE_LOGSTRING(LogLevel::Debug, ("Read canceled"));
+ break;
+ case mozIStorageStatementCallback::REASON_ERROR:
+ // Nothing more to do here. DBListenerErrorHandler::HandleError()
+ // can handle it.
+ COOKIE_LOGSTRING(LogLevel::Debug, ("Read error"));
+ break;
+ default:
+ NS_NOTREACHED("invalid reason");
+ }
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
+
+/******************************************************************************
+ * CloseCookieDBListener imp:
+ * Static mozIStorageCompletionCallback used to notify when the database is
+ * successfully closed.
+ ******************************************************************************/
+class CloseCookieDBListener final : public mozIStorageCompletionCallback
+{
+ ~CloseCookieDBListener() = default;
+
+public:
+ explicit CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
+ RefPtr<DBState> mDBState;
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Complete(nsresult, nsISupports*) override
+ {
+ gCookieService->HandleDBClosed(mDBState);
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
+
+namespace {
+
+class AppClearDataObserver final : public nsIObserver {
+
+ ~AppClearDataObserver() = default;
+
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIObserver implementation.
+ NS_IMETHOD
+ Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
+ {
+ MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_CLEAR_ORIGIN_DATA));
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsICookieManager2> cookieManager
+ = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
+ MOZ_ASSERT(cookieManager);
+
+ return cookieManager->RemoveCookiesWithOriginAttributes(nsDependentString(aData), EmptyCString());
+ }
+};
+
+NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
+
+} // namespace
+
+size_t
+nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+size_t
+nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
+
+ amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < mCookies.Length(); ++i) {
+ amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+size_t
+CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t amount = 0;
+
+ amount += key.SizeOfExcludingThis(aMallocSizeOf);
+ amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
+
+ return amount;
+}
+
+size_t
+DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t amount = 0;
+
+ amount += aMallocSizeOf(this);
+ amount += hostTable.SizeOfExcludingThis(aMallocSizeOf);
+ amount += hostArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < hostArray.Length(); ++i) {
+ amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+ amount += readSet.SizeOfExcludingThis(aMallocSizeOf);
+
+ return amount;
+}
+
+/******************************************************************************
+ * nsCookieService impl:
+ * singleton instance ctor/dtor methods
+ ******************************************************************************/
+
+nsICookieService*
+nsCookieService::GetXPCOMSingleton()
+{
+ if (IsNeckoChild())
+ return CookieServiceChild::GetSingleton();
+
+ return GetSingleton();
+}
+
+nsCookieService*
+nsCookieService::GetSingleton()
+{
+ NS_ASSERTION(!IsNeckoChild(), "not a parent process");
+
+ if (gCookieService) {
+ NS_ADDREF(gCookieService);
+ return gCookieService;
+ }
+
+ // Create a new singleton nsCookieService.
+ // 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 nsCookieService();
+ if (gCookieService) {
+ NS_ADDREF(gCookieService);
+ if (NS_FAILED(gCookieService->Init())) {
+ NS_RELEASE(gCookieService);
+ }
+ }
+
+ return gCookieService;
+}
+
+/* static */ void
+nsCookieService::AppClearDataObserverInit()
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ nsCOMPtr<nsIObserver> obs = new AppClearDataObserver();
+ observerService->AddObserver(obs, TOPIC_CLEAR_ORIGIN_DATA,
+ /* ownsWeak= */ false);
+}
+
+/******************************************************************************
+ * nsCookieService impl:
+ * public methods
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsCookieService,
+ nsICookieService,
+ nsICookieManager,
+ nsICookieManager2,
+ nsIObserver,
+ nsISupportsWeakReference,
+ nsIMemoryReporter)
+
+nsCookieService::nsCookieService()
+ : mDBState(nullptr)
+ , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT)
+ , mThirdPartySession(false)
+ , mLeaveSecureAlone(true)
+ , mMaxNumberOfCookies(kMaxNumberOfCookies)
+ , mMaxCookiesPerHost(kMaxCookiesPerHost)
+ , mCookiePurgeAge(kCookiePurgeAge)
+{
+}
+
+nsresult
+nsCookieService::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 pref and observer
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ prefBranch->AddObserver(kPrefCookieBehavior, this, true);
+ prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
+ prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true);
+ prefBranch->AddObserver(kPrefCookiePurgeAge, this, true);
+ prefBranch->AddObserver(kPrefThirdPartySession, this, true);
+ prefBranch->AddObserver(kCookieLeaveSecurityAlone, this, true);
+ PrefChanged(prefBranch);
+ }
+
+ mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Init our default, and possibly private DBStates.
+ InitDBStates();
+
+ RegisterWeakMemoryReporter(this);
+
+ nsCOMPtr<nsIObserverService> os = mozilla::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);
+
+ mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
+ if (!mPermissionService) {
+ NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
+ COOKIE_LOGSTRING(LogLevel::Warning, ("Init(): nsICookiePermission implementation not available"));
+ }
+
+ return NS_OK;
+}
+
+void
+nsCookieService::InitDBStates()
+{
+ NS_ASSERTION(!mDBState, "already have a DBState");
+ NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
+ NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
+
+ // Create a new default DBState and set our current one.
+ mDefaultDBState = new DBState();
+ mDBState = mDefaultDBState;
+
+ mPrivateDBState = new DBState();
+
+ // Get our cookie file.
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mDefaultDBState->cookieFile));
+ if (NS_FAILED(rv)) {
+ // We've already set up our DBStates appropriately; nothing more to do.
+ COOKIE_LOGSTRING(LogLevel::Warning,
+ ("InitDBStates(): couldn't get cookie file"));
+ return;
+ }
+ mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
+
+ // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
+ // do so.
+ OpenDBResult result = TryInitDB(false);
+ if (result == RESULT_RETRY) {
+ // Database may be corrupt. Synchronously close the connection, clean up the
+ // default DBState, and try again.
+ COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBStates(): retrying TryInitDB()"));
+ CleanupCachedStatements();
+ CleanupDefaultDBConnection();
+ result = 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,
+ ("InitDBStates(): 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.
+ CleanupCachedStatements();
+ CleanupDefaultDBConnection();
+ }
+}
+
+namespace {
+
+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 appId, inIsolatedMozBrowser;
+
+ rv = aFunctionArguments->GetInt32(0, &appId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create an originAttributes object by appId and inIsolatedMozBrowser.
+ // Then create the originSuffix string from this object.
+ NeckoOriginAttributes attrs(appId, (inIsolatedMozBrowser ? 1 : 0));
+ nsAutoCString suffix;
+ attrs.CreateSuffix(suffix);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsAUTF8String(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+class SetAppIdFromOriginAttributesSQLFunction final : public mozIStorageFunction
+{
+ ~SetAppIdFromOriginAttributesSQLFunction() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
+
+NS_IMETHODIMP
+SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
+{
+ nsresult rv;
+ nsAutoCString suffix;
+ NeckoOriginAttributes 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.mAppId);
+ 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;
+ NeckoOriginAttributes 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;
+}
+
+} // namespace
+
+/* 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 DBState.
+ */
+OpenDBResult
+nsCookieService::TryInitDB(bool aRecreateDB)
+{
+ NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
+ NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
+ NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
+ NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
+
+ // 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;
+ mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
+ rv = backupFile->MoveToNative(nullptr,
+ NS_LITERAL_CSTRING(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(mDefaultDBState->cookieFile);
+
+ // 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(mDefaultDBState->cookieFile,
+ getter_AddRefs(mDefaultDBState->dbConn));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+
+ // Set up our listeners.
+ mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
+ mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
+ mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
+ mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
+
+ // Grow cookie db in 512KB increments
+ mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
+
+ bool tableExists = false;
+ mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
+ &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 = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Start a transaction for the whole migration block.
+ mozStorageTransaction transaction(mDefaultDBState->dbConn, 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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ // Fall through to the next upgrade.
+ MOZ_FALLTHROUGH;
+
+ case 2:
+ {
+ // Add the baseDomain column and index to the table.
+ rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
+ 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 = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ nsCOMPtr<mozIStorageStatement> update;
+ rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
+ getter_AddRefs(update));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ nsCString baseDomain, 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 = GetBaseDomainFromHost(host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ mozStorageStatementScoper scoper(update);
+
+ rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
+ baseDomain);
+ NS_ASSERT_SUCCESS(rv);
+ rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"),
+ id);
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = update->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+
+ // Create an index on baseDomain.
+ rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ // Fall through to the next upgrade.
+ MOZ_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 = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+ "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 = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_cookies WHERE id = :id"),
+ 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, host1, 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, host2, 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(NS_LITERAL_CSTRING("id"),
+ id1);
+ NS_ASSERT_SUCCESS(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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE UNIQUE INDEX moz_uniqueid "
+ "ON moz_cookies (name, host, path)"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ // Fall through to the next upgrade.
+ MOZ_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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX moz_basedomain"));
+ 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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_cookies_old"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 5"));
+ }
+ // Fall through to the next upgrade.
+ MOZ_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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX moz_basedomain"));
+ 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);
+
+ NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName,
+ "CONVERT_TO_ORIGIN_ATTRIBUTES");
+
+ rv = mDefaultDBState->dbConn->CreateFunction(convertToOriginAttrsName,
+ 2, convertToOriginAttrs);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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 = mDefaultDBState->dbConn->RemoveFunction(convertToOriginAttrsName);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Drop old table
+ rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_cookies_old"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 6"));
+ }
+ MOZ_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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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);
+
+ NS_NAMED_LITERAL_CSTRING(setAppIdName, "SET_APP_ID");
+
+ rv = mDefaultDBState->dbConn->CreateFunction(setAppIdName, 1, setAppId);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ nsCOMPtr<mozIStorageFunction>
+ setInBrowser(new SetInBrowserFromOriginAttributesSQLFunction());
+ NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
+
+ NS_NAMED_LITERAL_CSTRING(setInBrowserName, "SET_IN_BROWSER");
+
+ rv = mDefaultDBState->dbConn->CreateFunction(setInBrowserName, 1,
+ setInBrowser);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
+ "inBrowserElement = SET_IN_BROWSER(originAttributes);"
+ ));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mDefaultDBState->dbConn->RemoveFunction(setAppIdName);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mDefaultDBState->dbConn->RemoveFunction(setInBrowserName);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("Upgraded database to schema version 7"));
+ }
+
+ // No more upgrades. Update the schema version.
+ rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ MOZ_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 = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ // fall through to downgrade check
+ MOZ_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 = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT "
+ "id, "
+ "baseDomain, "
+ "originAttributes, "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly "
+ "FROM moz_cookies"), getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv))
+ break;
+
+ // our columns aren't there - drop the table!
+ rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_cookies"));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+ }
+ break;
+ }
+ }
+
+ // make operations on the table asynchronous, for performance
+ mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA synchronous = OFF"));
+
+ // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
+ // 16 pages (around 500KB).
+ mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
+ mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA wal_autocheckpoint = 16"));
+
+ // cache frequently used statements (for insertion, deletion, and updating)
+ rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_cookies ("
+ "baseDomain, "
+ "originAttributes, "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly"
+ ") VALUES ("
+ ":baseDomain, "
+ ":originAttributes, "
+ ":name, "
+ ":value, "
+ ":host, "
+ ":path, "
+ ":expiry, "
+ ":lastAccessed, "
+ ":creationTime, "
+ ":isSecure, "
+ ":isHttpOnly"
+ ")"),
+ getter_AddRefs(mDefaultDBState->stmtInsert));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_cookies "
+ "WHERE name = :name AND host = :host AND path = :path"),
+ getter_AddRefs(mDefaultDBState->stmtDelete));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
+ "WHERE name = :name AND host = :host AND path = :path"),
+ getter_AddRefs(mDefaultDBState->stmtUpdate));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // 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();
+
+ nsCOMPtr<nsIFile> oldCookieFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(oldCookieFile));
+ if (NS_FAILED(rv)) return RESULT_OK;
+
+ // Import cookies, and clean up the old file regardless of success or failure.
+ // Note that we have to switch out our DBState temporarily, in case we're in
+ // private browsing mode; otherwise ImportCookies() won't be happy.
+ DBState* initialState = mDBState;
+ mDBState = mDefaultDBState;
+ oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME));
+ ImportCookies(oldCookieFile);
+ oldCookieFile->Remove(false);
+ mDBState = initialState;
+
+ return RESULT_OK;
+}
+
+// Sets the schema version and creates the moz_cookies table.
+nsresult
+nsCookieService::CreateTable()
+{
+ // Set the schema version, before creating the table.
+ nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
+ COOKIES_SCHEMA_VERSION);
+ 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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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)"
+ ")"));
+ if (NS_FAILED(rv)) return rv;
+
+ // Create an index on baseDomain.
+ return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
+ "originAttributes)"));
+}
+
+// Sets the schema version and creates the moz_cookies table.
+nsresult
+nsCookieService::CreateTableForSchemaVersion6()
+{
+ // Set the schema version, before creating the table.
+ nsresult rv = mDefaultDBState->dbConn->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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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 mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
+ "originAttributes)"));
+}
+
+// Sets the schema version and creates the moz_cookies table.
+nsresult
+nsCookieService::CreateTableForSchemaVersion5()
+{
+ // Set the schema version, before creating the table.
+ nsresult rv = mDefaultDBState->dbConn->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 = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "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 mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
+ "appId, "
+ "inBrowserElement)"));
+}
+
+void
+nsCookieService::CloseDBStates()
+{
+ // Null out our private and pointer DBStates regardless.
+ mPrivateDBState = nullptr;
+ mDBState = nullptr;
+
+ // If we don't have a default DBState, we're done.
+ if (!mDefaultDBState)
+ return;
+
+ // Cleanup cached statements before we can close anything.
+ CleanupCachedStatements();
+
+ if (mDefaultDBState->dbConn) {
+ // Cancel any pending read. No further results will be received by our
+ // read listener.
+ if (mDefaultDBState->pendingRead) {
+ CancelAsyncRead(true);
+ }
+
+ // Asynchronously close the connection. We will null it below.
+ mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
+ }
+
+ CleanupDefaultDBConnection();
+
+ mDefaultDBState = nullptr;
+}
+
+// Null out the statements.
+// This must be done before closing the connection.
+void
+nsCookieService::CleanupCachedStatements()
+{
+ mDefaultDBState->stmtInsert = nullptr;
+ mDefaultDBState->stmtDelete = nullptr;
+ mDefaultDBState->stmtUpdate = 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
+nsCookieService::CleanupDefaultDBConnection()
+{
+ MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up");
+ MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up");
+ MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up");
+
+ // Null out the database connections. If 'dbConn' 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.
+ mDefaultDBState->dbConn = nullptr;
+ mDefaultDBState->syncConn = nullptr;
+
+ // Manually null out our listeners. This is necessary because they hold a
+ // strong ref to the DBState itself. They'll stay alive until whatever
+ // statements are still executing complete.
+ mDefaultDBState->readListener = nullptr;
+ mDefaultDBState->insertListener = nullptr;
+ mDefaultDBState->updateListener = nullptr;
+ mDefaultDBState->removeListener = nullptr;
+ mDefaultDBState->closeListener = nullptr;
+}
+
+void
+nsCookieService::HandleDBClosed(DBState* aDBState)
+{
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("HandleDBClosed(): DBState %x closed", aDBState));
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+
+ switch (aDBState->corruptFlag) {
+ case DBState::OK: {
+ // Database is healthy. Notify of closure.
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
+ }
+ break;
+ }
+ case DBState::CLOSING_FOR_REBUILD: {
+ // Our close finished. Start the rebuild, and notify of db closure later.
+ RebuildCorruptDB(aDBState);
+ break;
+ }
+ case DBState::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;
+ aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
+ nsresult rv = backupFile->MoveToNative(nullptr,
+ NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
+
+ COOKIE_LOGSTRING(LogLevel::Warning,
+ ("HandleDBClosed(): DBState %x encountered error rebuilding db; move to "
+ "'cookies.sqlite.bak-rebuild' gave rv 0x%x", aDBState, rv));
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
+ }
+ break;
+ }
+ }
+}
+
+void
+nsCookieService::HandleCorruptDB(DBState* aDBState)
+{
+ if (mDefaultDBState != aDBState) {
+ // We've either closed the state or we've switched profiles. It's getting
+ // a bit late to rebuild -- bail instead.
+ COOKIE_LOGSTRING(LogLevel::Warning,
+ ("HandleCorruptDB(): DBState %x is already closed, aborting", aDBState));
+ return;
+ }
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("HandleCorruptDB(): DBState %x has corruptFlag %u", aDBState,
+ aDBState->corruptFlag));
+
+ // Mark the database corrupt, so the close listener can begin reconstructing
+ // it.
+ switch (mDefaultDBState->corruptFlag) {
+ case DBState::OK: {
+ // Move to 'closing' state.
+ mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
+
+ // Cancel any pending read and close the database. If we do have an
+ // in-flight read we want to throw away all the results so far -- we have no
+ // idea how consistent the database is. Note that we may have already
+ // canceled the read but not emptied our readSet; do so now.
+ mDefaultDBState->readSet.Clear();
+ if (mDefaultDBState->pendingRead) {
+ CancelAsyncRead(true);
+ mDefaultDBState->syncConn = nullptr;
+ }
+
+ CleanupCachedStatements();
+ mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
+ CleanupDefaultDBConnection();
+ break;
+ }
+ case DBState::CLOSING_FOR_REBUILD: {
+ // We had an error while waiting for close completion. That's OK, just
+ // ignore it -- we're rebuilding anyway.
+ return;
+ }
+ case DBState::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 (mDefaultDBState->dbConn) {
+ mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
+ }
+ CleanupDefaultDBConnection();
+ break;
+ }
+ }
+}
+
+void
+nsCookieService::RebuildCorruptDB(DBState* aDBState)
+{
+ NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
+ NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
+ "should be in CLOSING_FOR_REBUILD state");
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+
+ aDBState->corruptFlag = DBState::REBUILDING;
+
+ if (mDefaultDBState != aDBState) {
+ // We've either closed the state or we've switched profiles. It's getting
+ // a bit late to rebuild -- bail instead. In any case, we were waiting
+ // on rebuild completion to notify of the db closure, which won't happen --
+ // do so now.
+ COOKIE_LOGSTRING(LogLevel::Warning,
+ ("RebuildCorruptDB(): DBState %x is stale, aborting", aDBState));
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
+ }
+ return;
+ }
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("RebuildCorruptDB(): creating new database"));
+
+ // The database has been closed, and we're ready to rebuild. Open a
+ // connection.
+ OpenDBResult result = TryInitDB(true);
+ 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));
+ CleanupCachedStatements();
+ CleanupDefaultDBConnection();
+ mDefaultDBState->corruptFlag = DBState::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);
+ }
+
+ // Enumerate the hash, and add cookies to the params array.
+ mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ for (auto iter = aDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
+ nsCookieEntry* entry = iter.Get();
+
+ const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
+ for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ nsCookie* cookie = cookies[i];
+
+ if (!cookie->IsSession()) {
+ bindCookieParameters(paramsArray, nsCookieKey(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"));
+ mDefaultDBState->corruptFlag = DBState::OK;
+ return;
+ }
+
+ // Execute the statement. If any errors crop up, we won't try again.
+ DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
+ NS_ASSERT_SUCCESS(rv);
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
+ NS_ASSERT_SUCCESS(rv);
+}
+
+nsCookieService::~nsCookieService()
+{
+ CloseDBStates();
+
+ UnregisterWeakMemoryReporter(this);
+
+ gCookieService = nullptr;
+}
+
+NS_IMETHODIMP
+nsCookieService::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 DBStates before
+ // changing.
+ CloseDBStates();
+
+ } else if (!strcmp(aTopic, "profile-do-change")) {
+ NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
+ NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
+
+ // 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.
+ InitDBStates();
+
+ } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ if (prefBranch)
+ PrefChanged(prefBranch);
+
+ } else if (!strcmp(aTopic, "last-pb-context-exited")) {
+ // Flush all the cookies stored by private browsing contexts
+ mPrivateDBState = new DBState();
+ }
+
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCookieService::GetCookieString(nsIURI *aHostURI,
+ nsIChannel *aChannel,
+ char **aCookie)
+{
+ return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
+}
+
+NS_IMETHODIMP
+nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI,
+ nsIURI *aFirstURI,
+ nsIChannel *aChannel,
+ char **aCookie)
+{
+ return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
+}
+
+nsresult
+nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
+ nsIChannel *aChannel,
+ bool aHttpBound,
+ char** aCookie)
+{
+ NS_ENSURE_ARG(aHostURI);
+ NS_ENSURE_ARG(aCookie);
+
+ // Determine whether the request is foreign. Failure is acceptable.
+ bool isForeign = true;
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
+
+ // Get originAttributes.
+ NeckoOriginAttributes attrs;
+ if (aChannel) {
+ NS_GetOriginAttributes(aChannel, attrs);
+ }
+
+ bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
+
+ nsAutoCString result;
+ GetCookieStringInternal(aHostURI, isForeign, aHttpBound, attrs,
+ isPrivate, result);
+ *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCookieService::SetCookieString(nsIURI *aHostURI,
+ nsIPrompt *aPrompt,
+ const char *aCookieHeader,
+ nsIChannel *aChannel)
+{
+ // The aPrompt argument is deprecated and unused. Avoid introducing new
+ // code that uses this argument by warning if the value is non-null.
+ MOZ_ASSERT(!aPrompt);
+ if (aPrompt) {
+ nsCOMPtr<nsIConsoleService> aConsoleService =
+ do_GetService("@mozilla.org/consoleservice;1");
+ if (aConsoleService) {
+ aConsoleService->LogStringMessage(
+ u"Non-null prompt ignored by nsCookieService.");
+ }
+ }
+ return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
+ false);
+}
+
+NS_IMETHODIMP
+nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI,
+ nsIURI *aFirstURI,
+ nsIPrompt *aPrompt,
+ const char *aCookieHeader,
+ const char *aServerTime,
+ nsIChannel *aChannel)
+{
+ // The aPrompt argument is deprecated and unused. Avoid introducing new
+ // code that uses this argument by warning if the value is non-null.
+ MOZ_ASSERT(!aPrompt);
+ if (aPrompt) {
+ nsCOMPtr<nsIConsoleService> aConsoleService =
+ do_GetService("@mozilla.org/consoleservice;1");
+ if (aConsoleService) {
+ aConsoleService->LogStringMessage(
+ u"Non-null prompt ignored by nsCookieService.");
+ }
+ }
+ return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
+ true);
+}
+
+nsresult
+nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
+ const char *aCookieHeader,
+ const char *aServerTime,
+ nsIChannel *aChannel,
+ bool aFromHttp)
+{
+ NS_ENSURE_ARG(aHostURI);
+ NS_ENSURE_ARG(aCookieHeader);
+
+ // Determine whether the request is foreign. Failure is acceptable.
+ bool isForeign = true;
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
+
+ // Get originAttributes.
+ NeckoOriginAttributes attrs;
+ if (aChannel) {
+ NS_GetOriginAttributes(aChannel, attrs);
+ }
+
+ bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
+
+ nsDependentCString cookieString(aCookieHeader);
+ nsDependentCString serverTime(aServerTime ? aServerTime : "");
+ SetCookieStringInternal(aHostURI, isForeign, cookieString,
+ serverTime, aFromHttp, attrs,
+ isPrivate, aChannel);
+ return NS_OK;
+}
+
+void
+nsCookieService::SetCookieStringInternal(nsIURI *aHostURI,
+ bool aIsForeign,
+ nsDependentCString &aCookieHeader,
+ const nsCString &aServerTime,
+ bool aFromHttp,
+ const NeckoOriginAttributes &aOriginAttrs,
+ bool aIsPrivate,
+ nsIChannel *aChannel)
+{
+ NS_ASSERTION(aHostURI, "null host!");
+
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ return;
+ }
+
+ AutoRestore<DBState*> savePrevDBState(mDBState);
+ mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
+
+ // 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 = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
+ if (NS_FAILED(rv)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "couldn't get base domain from URI");
+ return;
+ }
+
+ nsCookieKey key(baseDomain, aOriginAttrs);
+
+ // check default prefs
+ CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, aCookieHeader.get());
+
+ // fire a notification if third party or if cookie was rejected
+ // (but not if there was an error)
+ switch (cookieStatus) {
+ case STATUS_REJECTED:
+ NotifyRejected(aHostURI);
+ if (aIsForeign) {
+ NotifyThirdParty(aHostURI, false, aChannel);
+ }
+ return; // Stop here
+ case STATUS_REJECTED_WITH_ERROR:
+ return;
+ case STATUS_ACCEPTED: // Fallthrough
+ case STATUS_ACCEPT_SESSION:
+ if (aIsForeign) {
+ NotifyThirdParty(aHostURI, true, aChannel);
+ }
+ break;
+ default:
+ break;
+ }
+
+ // parse server local time. this is not just done here for efficiency
+ // reasons - if there's an error parsing it, and we need to default it
+ // to the current time, we must do it here since the current time in
+ // SetCookieInternal() will change for each cookie processed (e.g. if the
+ // user is prompted).
+ PRTime tempServerTime;
+ int64_t serverTime;
+ PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
+ &tempServerTime);
+ if (result == PR_SUCCESS) {
+ serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
+ } else {
+ serverTime = PR_Now() / PR_USEC_PER_SEC;
+ }
+
+ // process each cookie in the header
+ while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus,
+ aCookieHeader, serverTime, aFromHttp, aChannel)) {
+ // document.cookie can only set one cookie at a time
+ if (!aFromHttp)
+ break;
+ }
+}
+
+// notify observers that a cookie was rejected due to the users' prefs.
+void
+nsCookieService::NotifyRejected(nsIURI *aHostURI)
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
+ }
+}
+
+// notify observers that a third-party cookie was accepted/rejected
+// if the cookie issuer is unknown, it defaults to "?"
+void
+nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os) {
+ return;
+ }
+
+ const char* topic;
+
+ if (mDBState != mPrivateDBState) {
+ // Regular (non-private) browsing
+ if (aIsAccepted) {
+ topic = "third-party-cookie-accepted";
+ } else {
+ topic = "third-party-cookie-rejected";
+ }
+ } else {
+ // Private browsing
+ if (aIsAccepted) {
+ topic = "private-third-party-cookie-accepted";
+ } else {
+ topic = "private-third-party-cookie-rejected";
+ }
+ }
+
+ do {
+ // Attempt to find the host of aChannel.
+ if (!aChannel) {
+ break;
+ }
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ nsAutoCString referringHost;
+ rv = channelURI->GetHost(referringHost);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
+ os->NotifyObservers(aHostURI, topic, referringHostUTF16.get());
+ return;
+ } while (false);
+
+ // This can fail for a number of reasons, in which kind we fallback to "?"
+ os->NotifyObservers(aHostURI, topic, u"?");
+}
+
+// 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
+nsCookieService::NotifyChanged(nsISupports *aSubject,
+ const char16_t *aData)
+{
+ const char* topic = mDBState == mPrivateDBState ?
+ "private-cookie-changed" : "cookie-changed";
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(aSubject, topic, aData);
+ }
+}
+
+already_AddRefed<nsIArray>
+nsCookieService::CreatePurgeList(nsICookie2* aCookie)
+{
+ nsCOMPtr<nsIMutableArray> removedList =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+ removedList->AppendElement(aCookie, false);
+ return removedList.forget();
+}
+
+/******************************************************************************
+ * nsCookieService:
+ * pref observer impl
+ ******************************************************************************/
+
+void
+nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
+{
+ int32_t val;
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
+ mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
+
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
+ mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
+
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
+ mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
+
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
+ mCookiePurgeAge =
+ int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
+ }
+
+ bool boolval;
+ if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
+ mThirdPartySession = boolval;
+
+ if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookieLeaveSecurityAlone, &boolval)))
+ mLeaveSecureAlone = boolval;
+}
+
+/******************************************************************************
+ * nsICookieManager impl:
+ * nsICookieManager
+ ******************************************************************************/
+
+NS_IMETHODIMP
+nsCookieService::RemoveAll()
+{
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RemoveAllFromMemory();
+
+ // clear the cookie file
+ if (mDBState->dbConn) {
+ NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
+
+ // Cancel any pending read. No further results will be received by our
+ // read listener.
+ if (mDefaultDBState->pendingRead) {
+ CancelAsyncRead(true);
+ }
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
+ getter_AddRefs(handle));
+ NS_ASSERT_SUCCESS(rv);
+ } else {
+ // Recreate the database.
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("RemoveAll(): corruption detected with rv 0x%x", rv));
+ HandleCorruptDB(mDefaultDBState);
+ }
+ }
+
+ NotifyChanged(nullptr, u"cleared");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
+{
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ EnsureReadComplete();
+
+ nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
+ for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
+ const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
+ for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ cookieList.AppendObject(cookies[i]);
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnumerator, cookieList);
+}
+
+static nsresult
+InitializeOriginAttributes(NeckoOriginAttributes* aAttrs,
+ JS::HandleValue aOriginAttributes,
+ JSContext* aCx,
+ uint8_t aArgc,
+ const char16_t* aAPI,
+ const char16_t* aInterfaceSuffix)
+{
+ MOZ_ASSERT(aAttrs);
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aAPI);
+ MOZ_ASSERT(aInterfaceSuffix);
+
+ if (aArgc == 0) {
+ const char16_t* params[] = {
+ aAPI,
+ aInterfaceSuffix
+ };
+
+ // This is supposed to be temporary and in 1 or 2 releases we want to
+ // have originAttributes param as mandatory. But for now, we don't want to
+ // break existing addons, so we write a console message to inform the addon
+ // developers about it.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Cookie Manager"),
+ nullptr,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "nsICookieManagerAPIDeprecated",
+ params, ArrayLength(params));
+ } else if (aArgc == 1) {
+ if (!aOriginAttributes.isObject() ||
+ !aAttrs->Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCookieService::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,
+ JSContext* aCx,
+ uint8_t aArgc)
+{
+ MOZ_ASSERT(aArgc == 0 || aArgc == 1);
+
+ NeckoOriginAttributes attrs;
+ nsresult rv = InitializeOriginAttributes(&attrs,
+ aOriginAttributes,
+ aCx,
+ aArgc,
+ u"nsICookieManager2.add()",
+ u"2");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
+ aIsSession, aExpiry, &attrs);
+}
+
+NS_IMETHODIMP_(nsresult)
+nsCookieService::AddNative(const nsACString &aHost,
+ const nsACString &aPath,
+ const nsACString &aName,
+ const nsACString &aValue,
+ bool aIsSecure,
+ bool aIsHttpOnly,
+ bool aIsSession,
+ int64_t aExpiry,
+ NeckoOriginAttributes* aOriginAttributes)
+{
+ if (NS_WARN_IF(!aOriginAttributes)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ 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 = GetBaseDomainFromHost(host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t currentTimeInUsec = PR_Now();
+ nsCookieKey key = nsCookieKey(baseDomain, *aOriginAttributes);
+
+ RefPtr<nsCookie> cookie =
+ nsCookie::Create(aName, aValue, host, aPath,
+ aExpiry,
+ currentTimeInUsec,
+ nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
+ aIsSession,
+ aIsSecure,
+ aIsHttpOnly,
+ key.mOriginAttributes);
+ if (!cookie) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ AddInternal(key, cookie, currentTimeInUsec, nullptr, nullptr, true);
+ return NS_OK;
+}
+
+
+nsresult
+nsCookieService::Remove(const nsACString& aHost, const NeckoOriginAttributes& aAttrs,
+ const nsACString& aName, const nsACString& aPath,
+ bool aBlocked)
+{
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ 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);
+
+ nsAutoCString baseDomain;
+ rv = GetBaseDomainFromHost(host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsListIter matchIter;
+ RefPtr<nsCookie> cookie;
+ if (FindCookie(nsCookieKey(baseDomain, aAttrs),
+ host,
+ PromiseFlatCString(aName),
+ PromiseFlatCString(aPath),
+ matchIter)) {
+ cookie = matchIter.Cookie();
+ RemoveCookieFromList(matchIter);
+ }
+
+ // check if we need to add the host to the permissions blacklist.
+ if (aBlocked && mPermissionService) {
+ // strip off the domain dot, if necessary
+ if (!host.IsEmpty() && host.First() == '.')
+ host.Cut(0, 1);
+
+ host.Insert(NS_LITERAL_CSTRING("http://"), 0);
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), host);
+
+ if (uri)
+ mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
+ }
+
+ if (cookie) {
+ // Everything's done. Notify observers.
+ NotifyChanged(cookie, u"deleted");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCookieService::Remove(const nsACString &aHost,
+ const nsACString &aName,
+ const nsACString &aPath,
+ bool aBlocked,
+ JS::HandleValue aOriginAttributes,
+ JSContext* aCx,
+ uint8_t aArgc)
+{
+ MOZ_ASSERT(aArgc == 0 || aArgc == 1);
+
+ NeckoOriginAttributes attrs;
+ nsresult rv = InitializeOriginAttributes(&attrs,
+ aOriginAttributes,
+ aCx,
+ aArgc,
+ u"nsICookieManager.remove()",
+ u"");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RemoveNative(aHost, aName, aPath, aBlocked, &attrs);
+}
+
+NS_IMETHODIMP_(nsresult)
+nsCookieService::RemoveNative(const nsACString &aHost,
+ const nsACString &aName,
+ const nsACString &aPath,
+ bool aBlocked,
+ NeckoOriginAttributes* aOriginAttributes)
+{
+ if (NS_WARN_IF(!aOriginAttributes)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath, aBlocked);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCookieService::UsePrivateMode(bool aIsPrivate,
+ nsIPrivateModeCallback* aCallback)
+{
+ if (!aCallback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ AutoRestore<DBState*> savePrevDBState(mDBState);
+ mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
+
+ return aCallback->Callback();
+}
+
+/******************************************************************************
+ * nsCookieService impl:
+ * private file I/O functions
+ ******************************************************************************/
+
+// Begin an asynchronous read from the database.
+OpenDBResult
+nsCookieService::Read()
+{
+ // Set up a statement for the read. Note that our query specifies that
+ // 'baseDomain' not be nullptr -- see below for why.
+ nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
+ nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "SELECT "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "baseDomain, "
+ "originAttributes "
+ "FROM moz_cookies "
+ "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Set up a statement to delete any rows with a nullptr 'baseDomain'
+ // column. This takes care of any cookies set by browsers that don't
+ // understand the 'baseDomain' column, where the database schema version
+ // is from one that does. (This would occur when downgrading.)
+ nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
+ rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
+ getter_AddRefs(stmtDeleteNull));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Start a new connection for sync reads, to reduce contention with the
+ // background thread. We need to do this before we kick off write statements,
+ // since they can lock the database and prevent connections from being opened.
+ rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
+ getter_AddRefs(mDefaultDBState->syncConn));
+ NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+ // Init our readSet hash and execute the statements. Note that, after this
+ // point, we cannot fail without altering the cleanup code in InitDBStates()
+ // to handle closing of the now-asynchronous connection.
+ mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
+
+ mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
+ rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
+ getter_AddRefs(mDefaultDBState->pendingRead));
+ NS_ASSERT_SUCCESS(rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
+ getter_AddRefs(handle));
+ NS_ASSERT_SUCCESS(rv);
+
+ return RESULT_OK;
+}
+
+// Extract data from a single result row and create an nsCookie.
+// This is templated since 'T' is different for sync vs async results.
+template<class T> nsCookie*
+nsCookieService::GetCookieFromRow(T &aRow, const OriginAttributes& aOriginAttributes)
+{
+ // Skip reading 'baseDomain' -- up to the caller.
+ nsCString name, value, host, path;
+ DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
+ NS_ASSERT_SUCCESS(rv);
+ rv = aRow->GetUTF8String(IDX_VALUE, value);
+ NS_ASSERT_SUCCESS(rv);
+ rv = aRow->GetUTF8String(IDX_HOST, host);
+ NS_ASSERT_SUCCESS(rv);
+ rv = aRow->GetUTF8String(IDX_PATH, path);
+ NS_ASSERT_SUCCESS(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);
+
+ // Create a new nsCookie and assign the data.
+ return nsCookie::Create(name, value, host, path,
+ expiry,
+ lastAccessed,
+ creationTime,
+ false,
+ isSecure,
+ isHttpOnly,
+ aOriginAttributes);
+}
+
+void
+nsCookieService::AsyncReadComplete()
+{
+ // We may be in the private browsing DB state, with a pending read on the
+ // default DB state. (This would occur if we started up in private browsing
+ // mode.) As long as we do all our operations on the default state, we're OK.
+ NS_ASSERTION(mDefaultDBState, "no default DBState");
+ NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
+ NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
+
+ // Merge the data read on the background thread with the data synchronously
+ // read on the main thread. Note that transactions on the cookie table may
+ // have occurred on the main thread since, making the background data stale.
+ for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
+ const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
+
+ // Tiebreak: if the given base domain has already been read in, ignore
+ // the background data. Note that readSet may contain domains that were
+ // queried but found not to be in the db -- that's harmless.
+ if (mDefaultDBState->readSet.GetEntry(tuple.key))
+ continue;
+
+ AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
+ }
+
+ mDefaultDBState->stmtReadDomain = nullptr;
+ mDefaultDBState->pendingRead = nullptr;
+ mDefaultDBState->readListener = nullptr;
+ mDefaultDBState->syncConn = nullptr;
+ mDefaultDBState->hostArray.Clear();
+ mDefaultDBState->readSet.Clear();
+
+ COOKIE_LOGSTRING(LogLevel::Debug, ("Read(): %ld cookies read",
+ mDefaultDBState->cookieCount));
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
+ }
+}
+
+void
+nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
+{
+ // We may be in the private browsing DB state, with a pending read on the
+ // default DB state. (This would occur if we started up in private browsing
+ // mode.) As long as we do all our operations on the default state, we're OK.
+ NS_ASSERTION(mDefaultDBState, "no default DBState");
+ NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
+ NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
+
+ // Cancel the pending read, kill the read listener, and empty the array
+ // of data already read in on the background thread.
+ mDefaultDBState->readListener->Cancel();
+ DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
+ NS_ASSERT_SUCCESS(rv);
+
+ mDefaultDBState->stmtReadDomain = nullptr;
+ mDefaultDBState->pendingRead = nullptr;
+ mDefaultDBState->readListener = nullptr;
+ mDefaultDBState->hostArray.Clear();
+
+ // Only clear the 'readSet' table if we no longer need to know what set of
+ // data is already accounted for.
+ if (aPurgeReadSet)
+ mDefaultDBState->readSet.Clear();
+}
+
+void
+nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
+{
+ NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
+ "not in default db state");
+
+ // Fast path 1: nothing to read, or we've already finished reading.
+ if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
+ return;
+
+ // Fast path 2: already read in this particular domain.
+ if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
+ return;
+
+ // Read in the data synchronously.
+ // see IDX_NAME, etc. for parameter indexes
+ nsresult rv;
+ if (!mDefaultDBState->stmtReadDomain) {
+ // Cache the statement, since it's likely to be used again.
+ rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly "
+ "FROM moz_cookies "
+ "WHERE baseDomain = :baseDomain "
+ " AND originAttributes = :originAttributes"),
+ getter_AddRefs(mDefaultDBState->stmtReadDomain));
+
+ if (NS_FAILED(rv)) {
+ // Recreate the database.
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("EnsureReadDomain(): corruption detected when creating statement "
+ "with rv 0x%x", rv));
+ HandleCorruptDB(mDefaultDBState);
+ return;
+ }
+ }
+
+ NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
+
+ mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
+
+ rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
+ NS_ASSERT_SUCCESS(rv);
+
+ nsAutoCString suffix;
+ aKey.mOriginAttributes.CreateSuffix(suffix);
+ rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("originAttributes"), suffix);
+ NS_ASSERT_SUCCESS(rv);
+
+ bool hasResult;
+ nsCString name, value, host, path;
+ AutoTArray<RefPtr<nsCookie>, kMaxCookiesPerHost> array;
+ while (true) {
+ rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
+ if (NS_FAILED(rv)) {
+ // Recreate the database.
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("EnsureReadDomain(): corruption detected when reading result "
+ "with rv 0x%x", rv));
+ HandleCorruptDB(mDefaultDBState);
+ return;
+ }
+
+ if (!hasResult)
+ break;
+
+ array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain,
+ aKey.mOriginAttributes));
+ }
+
+ // Add the cookies to the table in a single operation. This makes sure that
+ // either all the cookies get added, or in the case of corruption, none.
+ for (uint32_t i = 0; i < array.Length(); ++i) {
+ AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
+ }
+
+ // Add it to the hashset of read entries, so we don't read it again.
+ mDefaultDBState->readSet.PutEntry(aKey);
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("EnsureReadDomain(): %ld cookies read for base domain %s, "
+ " originAttributes = %s", array.Length(), aKey.mBaseDomain.get(),
+ suffix.get()));
+}
+
+void
+nsCookieService::EnsureReadComplete()
+{
+ NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
+ "not in default db state");
+
+ // Fast path 1: nothing to read, or we've already finished reading.
+ if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
+ return;
+
+ // Cancel the pending read, so we don't get any more results.
+ CancelAsyncRead(false);
+
+ // Read in the data synchronously.
+ // see IDX_NAME, etc. for parameter indexes
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT "
+ "name, "
+ "value, "
+ "host, "
+ "path, "
+ "expiry, "
+ "lastAccessed, "
+ "creationTime, "
+ "isSecure, "
+ "isHttpOnly, "
+ "baseDomain, "
+ "originAttributes "
+ "FROM moz_cookies "
+ "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
+
+ if (NS_FAILED(rv)) {
+ // Recreate the database.
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("EnsureReadComplete(): corruption detected when creating statement "
+ "with rv 0x%x", rv));
+ HandleCorruptDB(mDefaultDBState);
+ return;
+ }
+
+ nsCString baseDomain, name, value, host, path;
+ bool hasResult;
+ nsTArray<CookieDomainTuple> array(kMaxNumberOfCookies);
+ while (true) {
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_FAILED(rv)) {
+ // Recreate the database.
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("EnsureReadComplete(): corruption detected when reading result "
+ "with rv 0x%x", rv));
+ HandleCorruptDB(mDefaultDBState);
+ return;
+ }
+
+ if (!hasResult)
+ break;
+
+ // Make sure we haven't already read the data.
+ stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
+
+ nsAutoCString suffix;
+ NeckoOriginAttributes 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);
+
+ nsCookieKey key(baseDomain, attrs);
+ if (mDefaultDBState->readSet.GetEntry(key))
+ continue;
+
+ CookieDomainTuple* tuple = array.AppendElement();
+ tuple->key = key;
+ tuple->cookie = GetCookieFromRow(stmt, attrs);
+ }
+
+ // Add the cookies to the table in a single operation. This makes sure that
+ // either all the cookies get added, or in the case of corruption, none.
+ for (uint32_t i = 0; i < array.Length(); ++i) {
+ CookieDomainTuple& tuple = array[i];
+ AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
+ false);
+ }
+
+ mDefaultDBState->syncConn = nullptr;
+ mDefaultDBState->readSet.Clear();
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("EnsureReadComplete(): %ld cookies read", array.Length()));
+}
+
+NS_IMETHODIMP
+nsCookieService::ImportCookies(nsIFile *aCookieFile)
+{
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Make sure we're in the default DB state. We don't want people importing
+ // cookies into a private browsing session!
+ if (mDBState != mDefaultDBState) {
+ NS_WARNING("Trying to import cookies in a private browsing session!");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> fileInputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // First, ensure we've read in everything from the database, if we have one.
+ EnsureReadComplete();
+
+ static const char kTrue[] = "TRUE";
+
+ nsAutoCString buffer, baseDomain;
+ bool isMore = true;
+ int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
+ nsASingleFragmentCString::char_iterator iter;
+ int32_t numInts;
+ int64_t expires;
+ bool isDomain, isHttpOnly = false;
+ uint32_t originalCookieCount = mDefaultDBState->cookieCount;
+
+ int64_t currentTimeInUsec = PR_Now();
+ int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
+ // we use lastAccessedCounter to keep cookies in recently-used order,
+ // so we start by initializing to currentTime (somewhat arbitrary)
+ int64_t lastAccessedCounter = currentTimeInUsec;
+
+ /* file format is:
+ *
+ * host \t isDomain \t path \t secure \t expires \t name \t cookie
+ *
+ * if this format isn't respected we move onto the next line in the file.
+ * isDomain is "TRUE" or "FALSE" (default to "FALSE")
+ * isSecure is "TRUE" or "FALSE" (default to "TRUE")
+ * expires is a int64_t integer
+ * note 1: cookie can contain tabs.
+ * note 2: cookies will be stored in order of lastAccessed time:
+ * most-recently used come first; least-recently-used come last.
+ */
+
+ /*
+ * ...but due to bug 178933, we hide HttpOnly cookies from older code
+ * in a comment, so they don't expose HttpOnly cookies to JS.
+ *
+ * The format for HttpOnly cookies is
+ *
+ * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
+ *
+ */
+
+ // We will likely be adding a bunch of cookies to the DB, so we use async
+ // batching with storage to make this super fast.
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
+ mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ }
+
+ while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
+ if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(HTTP_ONLY_PREFIX))) {
+ isHttpOnly = true;
+ hostIndex = sizeof(HTTP_ONLY_PREFIX) - 1;
+ } else if (buffer.IsEmpty() || buffer.First() == '#') {
+ continue;
+ } else {
+ isHttpOnly = false;
+ hostIndex = 0;
+ }
+
+ // this is a cheap, cheesy way of parsing a tab-delimited line into
+ // string indexes, which can be lopped off into substrings. just for
+ // purposes of obfuscation, it also checks that each token was found.
+ // todo: use iterators?
+ if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 ||
+ (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
+ (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 ||
+ (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 ||
+ (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 ||
+ (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) {
+ continue;
+ }
+
+ // check the expirytime first - if it's expired, ignore
+ // nullstomp the trailing tab, to avoid copying the string
+ buffer.BeginWriting(iter);
+ *(iter += nameIndex - 1) = char(0);
+ numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
+ if (numInts != 1 || expires < currentTime) {
+ continue;
+ }
+
+ isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
+ const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
+ // check for bad legacy cookies (domain not starting with a dot, or containing a port),
+ // and discard
+ if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
+ host.Contains(':')) {
+ continue;
+ }
+
+ // compute the baseDomain from the host
+ rv = GetBaseDomainFromHost(host, baseDomain);
+ if (NS_FAILED(rv))
+ continue;
+
+ // pre-existing cookies have appId=0, inIsolatedMozBrowser=false set by default
+ // constructor of NeckoOriginAttributes().
+ nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
+
+ // Create a new nsCookie and assign the data. We don't know the cookie
+ // creation time, so just use the current time to generate a unique one.
+ RefPtr<nsCookie> newCookie =
+ nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
+ Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
+ host,
+ Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
+ expires,
+ lastAccessedCounter,
+ nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
+ false,
+ Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
+ isHttpOnly,
+ key.mOriginAttributes);
+ if (!newCookie) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // trick: preserve the most-recently-used cookie ordering,
+ // by successively decrementing the lastAccessed time
+ lastAccessedCounter--;
+
+ if (originalCookieCount == 0) {
+ AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
+ }
+ else {
+ AddInternal(key, newCookie, currentTimeInUsec,
+ nullptr, nullptr, true);
+ }
+ }
+
+ // If we need to write to disk, do so now.
+ if (paramsArray) {
+ uint32_t length;
+ paramsArray->GetLength(&length);
+ if (length) {
+ rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
+ NS_ASSERT_SUCCESS(rv);
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = mDefaultDBState->stmtInsert->ExecuteAsync(
+ mDefaultDBState->insertListener, getter_AddRefs(handle));
+ NS_ASSERT_SUCCESS(rv);
+ }
+ }
+
+
+ COOKIE_LOGSTRING(LogLevel::Debug, ("ImportCookies(): %ld cookies imported",
+ mDefaultDBState->cookieCount));
+
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsCookieService impl:
+ * private GetCookie/SetCookie helpers
+ ******************************************************************************/
+
+// helper function for GetCookieList
+static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
+
+// Comparator class for sorting cookies before sending to a server.
+class CompareCookiesForSending
+{
+public:
+ bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
+ {
+ return aCookie1->CreationTime() == aCookie2->CreationTime() &&
+ aCookie2->Path().Length() == aCookie1->Path().Length();
+ }
+
+ bool LessThan(const nsCookie* aCookie1, const nsCookie* 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();
+ }
+};
+
+static bool
+DomainMatches(nsCookie* 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
+PathMatches(nsCookie* aCookie, const nsACString& aPath) {
+ // calculate cookie path length, excluding trailing '/'
+ uint32_t cookiePathLen = aCookie->Path().Length();
+ if (cookiePathLen > 0 && aCookie->Path().Last() == '/')
+ --cookiePathLen;
+
+ // if the given path is shorter than the cookie path, it doesn't match
+ // if the given path doesn't start with the cookie path, it doesn't match.
+ if (!StringBeginsWith(aPath, Substring(aCookie->Path(), 0, cookiePathLen)))
+ return false;
+
+ // if the given path is longer than the cookie path, and the first char after
+ // the cookie path is not a path delimiter, it doesn't match.
+ if (aPath.Length() > cookiePathLen &&
+ !ispathdelimiter(aPath.CharAt(cookiePathLen))) {
+ /*
+ * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
+ * '/' is the "standard" case; the '?' test allows a site at host/abc?def
+ * to receive a cookie that has a path attribute of abc. this seems
+ * strange but at least one major site (citibank, bug 156725) depends
+ * on it. The test for # and ; are put in to proactively avoid problems
+ * with other sites - these are the only other chars allowed in the path.
+ */
+ return false;
+ }
+
+ // either the paths match exactly, or the cookie path is a prefix of
+ // the given path.
+ return true;
+}
+
+void
+nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
+ bool aIsForeign,
+ bool aHttpBound,
+ const NeckoOriginAttributes aOriginAttrs,
+ bool aIsPrivate,
+ nsCString &aCookieString)
+{
+ NS_ASSERTION(aHostURI, "null host!");
+
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ return;
+ }
+
+ AutoRestore<DBState*> savePrevDBState(mDBState);
+ mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
+
+ // 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, hostFromURI, pathFromURI;
+ nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
+ if (NS_SUCCEEDED(rv))
+ rv = aHostURI->GetAsciiHost(hostFromURI);
+ if (NS_SUCCEEDED(rv))
+ rv = aHostURI->GetPath(pathFromURI);
+ if (NS_FAILED(rv)) {
+ COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
+ return;
+ }
+
+ // check default prefs
+ CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, nullptr);
+
+ // for GetCookie(), we don't fire rejection notifications.
+ switch (cookieStatus) {
+ case STATUS_REJECTED:
+ case STATUS_REJECTED_WITH_ERROR:
+ return;
+ default:
+ break;
+ }
+
+ // Note: The following permissions logic is mirrored in
+ // toolkit/modules/addons/MatchPattern.jsm: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 isSecure;
+ if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
+ isSecure = false;
+ }
+
+ nsCookie *cookie;
+ AutoTArray<nsCookie*, 8> foundCookieList;
+ int64_t currentTimeInUsec = PR_Now();
+ int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
+ bool stale = false;
+
+ nsCookieKey key(baseDomain, aOriginAttrs);
+ EnsureReadDomain(key);
+
+ // perform the hash lookup
+ nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
+ if (!entry)
+ return;
+
+ // iterate the cookies!
+ const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
+ for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ cookie = cookies[i];
+
+ // check the host, since the base domain lookup is conservative.
+ if (!DomainMatches(cookie, hostFromURI))
+ continue;
+
+ // if the cookie is secure and the host scheme isn't, we can't send it
+ if (cookie->IsSecure() && !isSecure)
+ 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 (!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
+ foundCookieList.AppendElement(cookie);
+ if (cookie->IsStale()) {
+ stale = true;
+ }
+ }
+
+ int32_t count = foundCookieList.Length();
+ if (count == 0)
+ return;
+
+ // 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) {
+ // 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 = mDBState->stmtUpdate;
+ if (mDBState->dbConn) {
+ stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ }
+
+ for (int32_t i = 0; i < count; ++i) {
+ cookie = foundCookieList.ElementAt(i);
+
+ if (cookie->IsStale()) {
+ UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
+ }
+ }
+ // Update the database now if necessary.
+ if (paramsArray) {
+ uint32_t length;
+ paramsArray->GetLength(&length);
+ if (length) {
+ DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
+ NS_ASSERT_SUCCESS(rv);
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmt->ExecuteAsync(mDBState->updateListener,
+ getter_AddRefs(handle));
+ NS_ASSERT_SUCCESS(rv);
+ }
+ }
+ }
+
+ // 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).
+ foundCookieList.Sort(CompareCookiesForSending());
+
+ for (int32_t i = 0; i < count; ++i) {
+ cookie = foundCookieList.ElementAt(i);
+
+ // 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_LITERAL_CSTRING("=") + cookie->Value();
+ } else {
+ // just write value
+ aCookieString += cookie->Value();
+ }
+ }
+ }
+
+ if (!aCookieString.IsEmpty())
+ COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
+}
+
+// processes a single cookie, and returns true if there are more cookies
+// to be processed
+bool
+nsCookieService::SetCookieInternal(nsIURI *aHostURI,
+ const nsCookieKey &aKey,
+ bool aRequireHostMatch,
+ CookieStatus aStatus,
+ nsDependentCString &aCookieHeader,
+ int64_t aServerTime,
+ bool aFromHttp,
+ nsIChannel *aChannel)
+{
+ NS_ASSERTION(aHostURI, "null host!");
+
+ // create a stack-based nsCookieAttributes, to store all the
+ // attributes parsed from the cookie
+ nsCookieAttributes cookieAttributes;
+
+ // init expiryTime such that session cookies won't prematurely expire
+ cookieAttributes.expiryTime = INT64_MAX;
+
+ // aCookieHeader is an in/out param to point to the next cookie, if
+ // there is one. Save the present value for logging purposes
+ nsDependentCString savedCookieHeader(aCookieHeader);
+
+ // newCookie says whether there are multiple cookies in the header;
+ // so we can handle them separately.
+ bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
+
+ // 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 isHTTPS;
+ nsresult rv = aHostURI->SchemeIs("https", &isHTTPS);
+ if (NS_SUCCEEDED(rv)) {
+ Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_SECURITY,
+ ((cookieAttributes.isSecure)? 0x02 : 0x00) |
+ ((isHTTPS)? 0x01 : 0x00));
+ }
+
+ int64_t currentTimeInUsec = PR_Now();
+
+ // calculate expiry time of cookie.
+ cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
+ currentTimeInUsec / PR_USEC_PER_SEC);
+ if (aStatus == STATUS_ACCEPT_SESSION) {
+ // force lifetime to session. note that the expiration time, if set above,
+ // will still apply.
+ cookieAttributes.isSession = true;
+ }
+
+ // reject cookie if it's over the size limit, per RFC2109
+ if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
+ return newCookie;
+ }
+
+ 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 };
+ if (cookieAttributes.name.FindCharInSet(illegalNameCharacters, 0) != -1) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
+ return newCookie;
+ }
+
+ // domain & path checks
+ if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
+ return newCookie;
+ }
+ if (!CheckPath(cookieAttributes, 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(cookieAttributes, isHTTPS)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the prefix tests");
+ return newCookie;
+ }
+
+ // 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 };
+ if (aFromHttp && (cookieAttributes.value.FindCharInSet(illegalCharacters, 0) != -1)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid value character");
+ return newCookie;
+ }
+
+ // create a new nsCookie and copy attributes
+ RefPtr<nsCookie> cookie =
+ nsCookie::Create(cookieAttributes.name,
+ cookieAttributes.value,
+ cookieAttributes.host,
+ cookieAttributes.path,
+ cookieAttributes.expiryTime,
+ currentTimeInUsec,
+ nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
+ cookieAttributes.isSession,
+ cookieAttributes.isSecure,
+ cookieAttributes.isHttpOnly,
+ aKey.mOriginAttributes);
+ if (!cookie)
+ return newCookie;
+
+ // check permissions from site permission list, or ask the user,
+ // to determine if we can set the cookie
+ if (mPermissionService) {
+ bool permission;
+ mPermissionService->CanSetCookie(aHostURI,
+ aChannel,
+ static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
+ &cookieAttributes.isSession,
+ &cookieAttributes.expiryTime,
+ &permission);
+ if (!permission) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
+ NotifyRejected(aHostURI);
+ return newCookie;
+ }
+
+ // update isSession and expiry attributes, in case they changed
+ cookie->SetIsSession(cookieAttributes.isSession);
+ cookie->SetExpiry(cookieAttributes.expiryTime);
+ }
+
+ // add the cookie to the list. AddInternal() takes care of logging.
+ // we get the current time again here, since it may have changed during prompting
+ AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
+ aFromHttp);
+ return newCookie;
+}
+
+// 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
+nsCookieService::AddInternal(const nsCookieKey &aKey,
+ nsCookie *aCookie,
+ int64_t aCurrentTimeInUsec,
+ nsIURI *aHostURI,
+ const char *aCookieHeader,
+ bool aFromHttp)
+{
+ int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
+
+ // if the new cookie is httponly, make sure we're not coming from script
+ if (!aFromHttp && aCookie->IsHttpOnly()) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "cookie is httponly; coming from script");
+ return;
+ }
+
+ bool isSecure = true;
+ if (aHostURI && NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
+ isSecure = false;
+ }
+
+ // 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 (mLeaveSecureAlone && aCookie->IsSecure() && !isSecure) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "non-https cookie can't set secure flag");
+ Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+ BLOCKED_SECURE_SET_FROM_HTTP);
+ return;
+ }
+ nsListIter exactIter;
+ bool foundCookie = false;
+ if (mLeaveSecureAlone) {
+ // 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)
+ foundCookie = FindSecureCookie(aKey, aCookie);
+ if (foundCookie && !aCookie->IsSecure()) {
+ if (!isSecure) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "cookie can't save because older cookie is secure cookie but newer cookie is non-secure cookie");
+ Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+ BLOCKED_DOWNGRADE_SECURE);
+ return;
+ } else {
+ // A secure site is allowed to downgrade a secure cookie
+ // but we want to measure anyway
+ Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+ DOWNGRADE_SECURE_FROM_SECURE);
+ }
+ }
+ }
+
+ foundCookie = FindCookie(aKey, aCookie->Host(),
+ aCookie->Name(), aCookie->Path(), exactIter);
+
+ RefPtr<nsCookie> oldCookie;
+ nsCOMPtr<nsIArray> purgedList;
+ if (foundCookie) {
+ oldCookie = exactIter.Cookie();
+
+ // Check if the old cookie is stale (i.e. has already expired). If so, we
+ // need to be careful about the semantics of removing it and adding the new
+ // cookie: we want the behavior wrt adding the new cookie to be the same as
+ // if it didn't exist, but we still want to fire a removal notification.
+ if (oldCookie->Expiry() <= currentTime) {
+ if (aCookie->Expiry() <= currentTime) {
+ // The new cookie has expired and the old one is stale. Nothing to do.
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "cookie has already expired");
+ return;
+ }
+
+ // Remove the stale cookie. We save notification for later, once all list
+ // modifications are complete.
+ RemoveCookieFromList(exactIter);
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "stale cookie was purged");
+ purgedList = CreatePurgeList(oldCookie);
+
+ // We've done all we need to wrt removing and notifying the stale cookie.
+ // From here on out, we pretend pretend it didn't exist, so that we
+ // preserve expected notification semantics when adding the new cookie.
+ foundCookie = false;
+
+ } else {
+ // If the old cookie is httponly, make sure we're not coming from script.
+ if (!aFromHttp && oldCookie->IsHttpOnly()) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "previously stored cookie is httponly; coming from script");
+ return;
+ }
+
+ // If the new cookie has the same value, expiry date, and isSecure,
+ // isSession, and isHttpOnly 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() &&
+ // 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(mDBState, oldCookie);
+ return;
+ }
+
+ // Remove the old cookie.
+ RemoveCookieFromList(exactIter);
+
+ // If the new cookie has expired -- i.e. the intent was simply to delete
+ // the old cookie -- then we're done.
+ if (aCookie->Expiry() <= currentTime) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "previously stored cookie was deleted");
+ NotifyChanged(oldCookie, u"deleted");
+ return;
+ }
+
+ // Preserve creation time of cookie for ordering purposes.
+ aCookie->SetCreationTime(oldCookie->CreationTime());
+ }
+
+ } else {
+ // check if cookie has already expired
+ if (aCookie->Expiry() <= currentTime) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+ "cookie has already expired");
+ return;
+ }
+
+ // check if we have to delete an old cookie.
+ nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
+ if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
+ nsListIter iter;
+ // Prioritize evicting insecure cookies.
+ // (draft-ietf-httpbis-cookie-alone section 3.3)
+ mozilla::Maybe<bool> optionalSecurity = mLeaveSecureAlone ? Some(false) : Nothing();
+ int64_t oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, optionalSecurity, iter);
+ if (iter.entry == nullptr) {
+ if (aCookie->IsSecure()) {
+ // It's valid to evict a secure cookie for another secure cookie.
+ oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, Some(true), iter);
+ } else {
+ Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+ EVICTING_SECURE_BLOCKED);
+ COOKIE_LOGEVICTED(aCookie,
+ "Too many cookies for this domain and the new cookie is not a secure cookie");
+ return;
+ }
+ }
+
+ MOZ_ASSERT(iter.entry);
+
+ oldCookie = iter.Cookie();
+ if (oldestCookieTime > 0 && mLeaveSecureAlone) {
+ TelemetryForEvictingStaleCookie(oldCookie, oldestCookieTime);
+ }
+
+ // remove the oldest cookie from the domain
+ RemoveCookieFromList(iter);
+ COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
+ purgedList = CreatePurgeList(oldCookie);
+ } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
+ int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
+ 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 cookieOldestTime indicator can be pessimistic - if it's
+ // older than the actual oldest cookie, we'll just purge more eagerly.
+ purgedList = PurgeCookies(aCurrentTimeInUsec);
+ }
+ }
+ }
+
+ // 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(aKey, aCookie, mDBState, nullptr);
+ 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");
+}
+
+/******************************************************************************
+ * nsCookieService impl:
+ * private cookie header parsing functions
+ ******************************************************************************/
+
+// 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. cookie <NAME> is optional, where spec requires it. This is a fairly
+ trivial case, but allows the flexibility of setting only a cookie <VALUE>
+ with a blank <NAME> and is required by some sites (see bug 169091).
+
+ 6. 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"
+
+******************************************************************************/
+
+// helper functions for GetTokenValue
+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
+nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
+ nsASingleFragmentCString::const_char_iterator &aEndIter,
+ nsDependentCSubstring &aTokenString,
+ nsDependentCSubstring &aTokenValue,
+ bool &aEqualsFound)
+{
+ nsASingleFragmentCString::const_char_iterator start, 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 && !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 && !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;
+}
+
+// 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
+nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
+ nsCookieAttributes &aCookieAttributes)
+{
+ 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";
+
+ nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
+ nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
+ aCookieHeader.BeginReading(cookieStart);
+ aCookieHeader.EndReading(cookieEnd);
+
+ aCookieAttributes.isSecure = false;
+ aCookieAttributes.isHttpOnly = false;
+
+ nsDependentCSubstring tokenString(cookieStart, cookieStart);
+ nsDependentCSubstring tokenValue (cookieStart, cookieStart);
+ bool newCookie, 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) {
+ aCookieAttributes.name = tokenString;
+ aCookieAttributes.value = tokenValue;
+ } else {
+ aCookieAttributes.value = tokenString;
+ }
+
+ // extract remaining attributes
+ while (cookieStart != cookieEnd && !newCookie) {
+ newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
+
+ if (!tokenValue.IsEmpty()) {
+ tokenValue.BeginReading(tempBegin);
+ tokenValue.EndReading(tempEnd);
+ }
+
+ // decide which attribute we have, and copy the string
+ if (tokenString.LowerCaseEqualsLiteral(kPath))
+ aCookieAttributes.path = tokenValue;
+
+ else if (tokenString.LowerCaseEqualsLiteral(kDomain))
+ aCookieAttributes.host = tokenValue;
+
+ else if (tokenString.LowerCaseEqualsLiteral(kExpires))
+ aCookieAttributes.expires = tokenValue;
+
+ else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
+ aCookieAttributes.maxage = tokenValue;
+
+ // ignore any tokenValue for isSecure; just set the boolean
+ else if (tokenString.LowerCaseEqualsLiteral(kSecure))
+ aCookieAttributes.isSecure = true;
+
+ // ignore any tokenValue for isHttpOnly (see bug 178993);
+ // just set the boolean
+ else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
+ aCookieAttributes.isHttpOnly = true;
+ }
+
+ // rebind aCookieHeader, in case we need to process another cookie
+ aCookieHeader.Rebind(cookieStart, cookieEnd);
+ return newCookie;
+}
+
+/******************************************************************************
+ * nsCookieService impl:
+ * private domain & permission compliance enforcement functions
+ ******************************************************************************/
+
+// 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
+nsCookieService::GetBaseDomain(nsIURI *aHostURI,
+ nsCString &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 = mTLDService->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 = aHostURI->GetAsciiHost(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()) {
+ bool isFileURI = false;
+ aHostURI->SchemeIs("file", &isFileURI);
+ if (!isFileURI)
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+// Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
+// "bbc.co.uk". This is done differently than GetBaseDomain(): 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
+nsCookieService::GetBaseDomainFromHost(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 = mTLDService->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;
+}
+
+// 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
+nsCookieService::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 nsCString &a, const nsCString &b)
+{
+ if (a == b)
+ return true;
+ if (a.Length() > b.Length())
+ return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
+ return false;
+}
+
+CookieStatus
+nsCookieService::CheckPrefs(nsIURI *aHostURI,
+ bool aIsForeign,
+ const char *aCookieHeader)
+{
+ nsresult rv;
+
+ // don't let ftp sites get/set cookies (could be a security issue)
+ bool ftp;
+ if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
+ COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
+ return STATUS_REJECTED_WITH_ERROR;
+ }
+
+ // check the permission list first; if we find an entry, it overrides
+ // default prefs. see bug 184059.
+ if (mPermissionService) {
+ nsCookieAccess access;
+ // Not passing an nsIChannel here is probably OK; our implementation
+ // doesn't do anything with it anyway.
+ rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
+
+ // if we found an entry, use it
+ if (NS_SUCCEEDED(rv)) {
+ switch (access) {
+ case nsICookiePermission::ACCESS_DENY:
+ COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
+ aCookieHeader, "cookies are blocked for this site");
+ return STATUS_REJECTED;
+
+ case nsICookiePermission::ACCESS_ALLOW:
+ return STATUS_ACCEPTED;
+
+ case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
+ if (aIsForeign) {
+ COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
+ aCookieHeader, "third party cookies are blocked "
+ "for this site");
+ return STATUS_REJECTED;
+
+ }
+ return STATUS_ACCEPTED;
+
+ case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
+ if (!aIsForeign)
+ return STATUS_ACCEPTED;
+ uint32_t priorCookieCount = 0;
+ nsAutoCString hostFromURI;
+ aHostURI->GetHost(hostFromURI);
+ CountCookiesFromHost(hostFromURI, &priorCookieCount);
+ if (priorCookieCount == 0) {
+ COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
+ aCookieHeader, "third party cookies are blocked "
+ "for this site");
+ return STATUS_REJECTED;
+ }
+ return STATUS_ACCEPTED;
+ }
+ }
+ }
+
+ // check default prefs
+ if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT) {
+ COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
+ return STATUS_REJECTED;
+ }
+
+ // check if cookie is foreign
+ if (aIsForeign) {
+ if (mCookieBehavior == nsICookieService::BEHAVIOR_ACCEPT && mThirdPartySession)
+ return STATUS_ACCEPT_SESSION;
+
+ if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN) {
+ COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
+ return STATUS_REJECTED;
+ }
+
+ if (mCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
+ uint32_t priorCookieCount = 0;
+ nsAutoCString hostFromURI;
+ aHostURI->GetHost(hostFromURI);
+ CountCookiesFromHost(hostFromURI, &priorCookieCount);
+ if (priorCookieCount == 0) {
+ COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
+ return STATUS_REJECTED;
+ }
+ if (mThirdPartySession)
+ 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
+nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
+ nsIURI *aHostURI,
+ const nsCString &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;
+ aHostURI->GetAsciiHost(hostFromURI);
+
+ // if a domain is given, check the host has permission
+ if (!aCookieAttributes.host.IsEmpty()) {
+ // Tolerate leading '.' characters, but not if it's otherwise an empty host.
+ if (aCookieAttributes.host.Length() > 1 &&
+ aCookieAttributes.host.First() == '.') {
+ aCookieAttributes.host.Cut(0, 1);
+ }
+
+ // switch to lowercase now, to avoid case-insensitive compares everywhere
+ ToLowerCase(aCookieAttributes.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(aCookieAttributes.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(aCookieAttributes.host, aBaseDomain) &&
+ IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
+ // prepend a dot to indicate a domain cookie
+ aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 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
+ aCookieAttributes.host = hostFromURI;
+ return true;
+}
+
+nsCString
+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->GetPath(path);
+ int32_t slash = path.RFindChar('/');
+ if (slash != kNotFound) {
+ path.Truncate(slash + 1);
+ }
+ }
+ return path;
+}
+
+bool
+nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
+ nsIURI *aHostURI)
+{
+ // if a path is given, check the host has permission
+ if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
+ aCookieAttributes.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->GetPath(pathFromURI)) ||
+ !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
+ return false;
+ }
+#endif
+ }
+
+ if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
+ aCookieAttributes.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 nsCookieAttributes values!
+bool
+nsCookieService::CheckPrefixes(nsCookieAttributes &aCookieAttributes,
+ 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( aCookieAttributes.name.get(), kSecure, kSecureLen ) == 0;
+ bool isHost = strncmp( aCookieAttributes.name.get(), kHost, kHostLen ) == 0;
+
+ if ( !isSecure && !isHost ) {
+ // not one of the magic prefixes: carry on
+ return true;
+ }
+
+ if ( !aSecureRequest || !aCookieAttributes.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 ( aCookieAttributes.host[0] == '.' ||
+ !aCookieAttributes.path.EqualsLiteral( "/" )) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
+ int64_t aServerTime,
+ int64_t aCurrentTime)
+{
+ /* 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 (!aCookieAttributes.maxage.IsEmpty()) {
+ // obtain numeric value of maxageAttribute
+ int64_t maxage;
+ int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%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.
+ aCookieAttributes.expiryTime = aCurrentTime + maxage;
+
+ // check for expires attribute
+ } else if (!aCookieAttributes.expires.IsEmpty()) {
+ PRTime expires;
+
+ // parse expiry time
+ if (PR_ParseTimeString(aCookieAttributes.expires.get(), 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.
+ aCookieAttributes.expiryTime = expires / int64_t(PR_USEC_PER_SEC);
+
+ // default to session cookie if no attributes found
+ } else {
+ return true;
+ }
+
+ return false;
+}
+
+/******************************************************************************
+ * nsCookieService impl:
+ * private cookielist management functions
+ ******************************************************************************/
+
+void
+nsCookieService::RemoveAllFromMemory()
+{
+ // clearing the hashtable will call each nsCookieEntry's dtor,
+ // which releases all their respective children.
+ mDBState->hostTable.Clear();
+ mDBState->cookieCount = 0;
+ mDBState->cookieOldestTime = INT64_MAX;
+}
+
+// comparator class for lastaccessed times of cookies.
+class CompareCookiesByAge {
+public:
+ bool Equals(const nsListIter &a, const nsListIter &b) const
+ {
+ return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
+ a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
+ }
+
+ bool LessThan(const nsListIter &a, const nsListIter &b) const
+ {
+ // 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();
+ }
+};
+
+// comparator class for sorting cookies by entry and index.
+class CompareCookiesByIndex {
+public:
+ bool Equals(const nsListIter &a, const nsListIter &b) const
+ {
+ NS_ASSERTION(a.entry != b.entry || a.index != b.index,
+ "cookie indexes should never be equal");
+ return false;
+ }
+
+ bool LessThan(const nsListIter &a, const nsListIter &b) const
+ {
+ // compare by entryclass pointer, then by index.
+ if (a.entry != b.entry)
+ return a.entry < b.entry;
+
+ return a.index < b.index;
+ }
+};
+
+// purges expired and old cookies in a batch operation.
+already_AddRefed<nsIArray>
+nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
+{
+ NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
+ EnsureReadComplete();
+
+ uint32_t initialCookieCount = mDBState->cookieCount;
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
+ mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
+
+ typedef nsTArray<nsListIter> PurgeList;
+ PurgeList purgeList(kMaxNumberOfCookies);
+
+ nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ // 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.
+ mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ if (mDBState->dbConn) {
+ stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ }
+
+ int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
+ int64_t purgeTime = aCurrentTimeInUsec - mCookiePurgeAge;
+ int64_t oldestTime = INT64_MAX;
+
+ for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
+ nsCookieEntry* entry = iter.Get();
+
+ const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
+ auto length = cookies.Length();
+ for (nsCookieEntry::IndexType i = 0; i < length; ) {
+ nsListIter iter(entry, i);
+ nsCookie* cookie = cookies[i];
+
+ // check if the cookie has expired
+ if (cookie->Expiry() <= currentTime) {
+ removedList->AppendElement(cookie, false);
+ COOKIE_LOGEVICTED(cookie, "Cookie expired");
+
+ // remove from list; do not increment our iterator unless we're the last
+ // in the list already.
+ gCookieService->RemoveCookieFromList(iter, paramsArray);
+ 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 = mDBState->cookieCount;
+
+ // 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 = mDBState->cookieCount > mMaxNumberOfCookies ?
+ mDBState->cookieCount - mMaxNumberOfCookies : 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--; ) {
+ nsCookie *cookie = purgeList[i].Cookie();
+ removedList->AppendElement(cookie, false);
+ COOKIE_LOGEVICTED(cookie, "Cookie too old");
+
+ RemoveCookieFromList(purgeList[i], paramsArray);
+ }
+
+ // Update the database if we have entries to purge.
+ if (paramsArray) {
+ uint32_t length;
+ paramsArray->GetLength(&length);
+ if (length) {
+ DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
+ NS_ASSERT_SUCCESS(rv);
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
+ NS_ASSERT_SUCCESS(rv);
+ }
+ }
+
+ // reset the oldest time indicator
+ mDBState->cookieOldestTime = oldestTime;
+
+ COOKIE_LOGSTRING(LogLevel::Debug,
+ ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
+ initialCookieCount - postExpiryCookieCount,
+ postExpiryCookieCount - mDBState->cookieCount,
+ mDBState->cookieCount,
+ aCurrentTimeInUsec - mDBState->cookieOldestTime));
+
+ return removedList.forget();
+}
+
+// find whether a given cookie has been previously set. this is provided by the
+// nsICookieManager2 interface.
+NS_IMETHODIMP
+nsCookieService::CookieExists(nsICookie2* aCookie,
+ JS::HandleValue aOriginAttributes,
+ JSContext* aCx,
+ uint8_t aArgc,
+ bool* aFoundCookie)
+{
+ NS_ENSURE_ARG_POINTER(aCookie);
+ NS_ENSURE_ARG_POINTER(aCx);
+ NS_ENSURE_ARG_POINTER(aFoundCookie);
+ MOZ_ASSERT(aArgc == 0 || aArgc == 1);
+
+ NeckoOriginAttributes attrs;
+ nsresult rv = InitializeOriginAttributes(&attrs,
+ aOriginAttributes,
+ aCx,
+ aArgc,
+ u"nsICookieManager2.cookieExists()",
+ u"2");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CookieExistsNative(aCookie, &attrs, aFoundCookie);
+}
+
+NS_IMETHODIMP_(nsresult)
+nsCookieService::CookieExistsNative(nsICookie2* aCookie,
+ NeckoOriginAttributes* aOriginAttributes,
+ bool* aFoundCookie)
+{
+ NS_ENSURE_ARG_POINTER(aCookie);
+ NS_ENSURE_ARG_POINTER(aOriginAttributes);
+ NS_ENSURE_ARG_POINTER(aFoundCookie);
+
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString host, name, path;
+ nsresult rv = aCookie->GetHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCookie->GetName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCookie->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString baseDomain;
+ rv = GetBaseDomainFromHost(host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsListIter iter;
+ *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes),
+ host, name, path, iter);
+ return NS_OK;
+}
+
+// For a given base domain, find either an expired cookie or the oldest cookie
+// by lastAccessed time.
+int64_t
+nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
+ int64_t aCurrentTime,
+ nsIURI* aSource,
+ mozilla::Maybe<bool> aIsSecure,
+ nsListIter &aIter)
+{
+ aIter.entry = nullptr;
+ bool requireHostMatch = true;
+ nsAutoCString baseDomain, sourceHost, sourcePath;
+ if (aSource) {
+ GetBaseDomain(aSource, baseDomain, requireHostMatch);
+ aSource->GetAsciiHost(sourceHost);
+ sourcePath = GetPathFromURI(aSource);
+ }
+
+ const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
+
+ int64_t oldestNonMatchingSessionCookieTime = 0;
+ nsListIter oldestNonMatchingSessionCookie;
+ oldestNonMatchingSessionCookie.entry = nullptr;
+
+ int64_t oldestSessionCookieTime = 0;
+ nsListIter oldestSessionCookie;
+ oldestSessionCookie.entry = nullptr;
+
+ int64_t oldestNonMatchingNonSessionCookieTime = 0;
+ nsListIter oldestNonMatchingNonSessionCookie;
+ oldestNonMatchingNonSessionCookie.entry = nullptr;
+
+ int64_t oldestCookieTime = 0;
+ nsListIter oldestCookie;
+ oldestCookie.entry = nullptr;
+
+ int64_t actualOldestCookieTime = cookies.Length() ? cookies[0]->LastAccessed() : 0;
+ for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ nsCookie *cookie = cookies[i];
+
+ // If we found an expired cookie, we're done.
+ if (cookie->Expiry() <= aCurrentTime) {
+ aIter.entry = aEntry;
+ aIter.index = i;
+ return -1;
+ }
+
+ int64_t lastAccessed = cookie->LastAccessed();
+ // Record the age of the oldest cookie that is stored for this host.
+ // oldestCookieTime is the age of the oldest cookie with a matching
+ // secure flag, which may be more recent than an older cookie with
+ // a non-matching secure flag.
+ if (actualOldestCookieTime > lastAccessed) {
+ actualOldestCookieTime = lastAccessed;
+ }
+ if (aIsSecure.isSome() && !aIsSecure.value()) {
+ // We want to look for the oldest non-secure cookie first time through,
+ // then find the oldest secure cookie the second time we are called.
+ if (cookie->IsSecure()) {
+ continue;
+ }
+ }
+
+ // Update our various records of oldest cookies fitting several restrictions:
+ // * session cookies
+ // * non-session cookies
+ // * cookies with paths and domains that don't match the cookie triggering this purge
+
+ // This cookie is a candidate for eviction if we have no information about
+ // the source request, or if it is not a path or domain match against the
+ // source request.
+ bool isPrimaryEvictionCandidate = true;
+ if (aSource) {
+ isPrimaryEvictionCandidate = !PathMatches(cookie, sourcePath) || !DomainMatches(cookie, sourceHost);
+ }
+
+ if (cookie->IsSession()) {
+ if (!oldestSessionCookie.entry || oldestSessionCookieTime > lastAccessed) {
+ oldestSessionCookieTime = lastAccessed;
+ oldestSessionCookie.entry = aEntry;
+ oldestSessionCookie.index = i;
+ }
+
+ if (isPrimaryEvictionCandidate &&
+ (!oldestNonMatchingSessionCookie.entry ||
+ oldestNonMatchingSessionCookieTime > lastAccessed)) {
+ oldestNonMatchingSessionCookieTime = lastAccessed;
+ oldestNonMatchingSessionCookie.entry = aEntry;
+ oldestNonMatchingSessionCookie.index = i;
+ }
+ } else if (isPrimaryEvictionCandidate &&
+ (!oldestNonMatchingNonSessionCookie.entry ||
+ oldestNonMatchingNonSessionCookieTime > lastAccessed)) {
+ oldestNonMatchingNonSessionCookieTime = lastAccessed;
+ oldestNonMatchingNonSessionCookie.entry = aEntry;
+ oldestNonMatchingNonSessionCookie.index = i;
+ }
+
+ // Check if we've found the oldest cookie so far.
+ if (!oldestCookie.entry || oldestCookieTime > lastAccessed) {
+ oldestCookieTime = lastAccessed;
+ oldestCookie.entry = aEntry;
+ oldestCookie.index = i;
+ }
+ }
+
+ // Prefer to evict the oldest session cookies with a non-matching path/domain,
+ // followed by the oldest session cookie with a matching path/domain,
+ // followed by the oldest non-session cookie with a non-matching path/domain,
+ // resorting to the oldest non-session cookie with a matching path/domain.
+ if (oldestNonMatchingSessionCookie.entry) {
+ aIter = oldestNonMatchingSessionCookie;
+ } else if (oldestSessionCookie.entry) {
+ aIter = oldestSessionCookie;
+ } else if (oldestNonMatchingNonSessionCookie.entry) {
+ aIter = oldestNonMatchingNonSessionCookie;
+ } else {
+ aIter = oldestCookie;
+ }
+
+ return actualOldestCookieTime;
+}
+
+void
+nsCookieService::TelemetryForEvictingStaleCookie(nsCookie *aEvicted,
+ int64_t oldestCookieTime)
+{
+ // We need to record the evicting cookie to telemetry.
+ if (!aEvicted->IsSecure()) {
+ if (aEvicted->LastAccessed() > oldestCookieTime) {
+ Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+ EVICTED_NEWER_INSECURE);
+ } else {
+ Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+ EVICTED_OLDEST_COOKIE);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+ EVICTED_PREFERRED_COOKIE);
+ }
+}
+
+// count the number of cookies stored by a particular host. this is provided by the
+// nsICookieManager2 interface.
+NS_IMETHODIMP
+nsCookieService::CountCookiesFromHost(const nsACString &aHost,
+ uint32_t *aCountFromHost)
+{
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ 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);
+
+ nsAutoCString baseDomain;
+ rv = GetBaseDomainFromHost(host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
+ EnsureReadDomain(key);
+
+ // Return a count of all cookies, including expired.
+ nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
+ *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
+ return NS_OK;
+}
+
+// get an enumerator of cookies stored by a particular host. this is provided by the
+// nsICookieManager2 interface.
+NS_IMETHODIMP
+nsCookieService::GetCookiesFromHost(const nsACString &aHost,
+ JS::HandleValue aOriginAttributes,
+ JSContext* aCx,
+ uint8_t aArgc,
+ nsISimpleEnumerator **aEnumerator)
+{
+ MOZ_ASSERT(aArgc == 0 || aArgc == 1);
+
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ 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);
+
+ nsAutoCString baseDomain;
+ rv = GetBaseDomainFromHost(host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NeckoOriginAttributes attrs;
+ rv = InitializeOriginAttributes(&attrs,
+ aOriginAttributes,
+ aCx,
+ aArgc,
+ u"nsICookieManager2.getCookiesFromHost()",
+ u"2");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCookieKey key = nsCookieKey(baseDomain, attrs);
+ EnsureReadDomain(key);
+
+ nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
+ if (!entry)
+ return NS_NewEmptyEnumerator(aEnumerator);
+
+ nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
+ const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
+ for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ cookieList.AppendObject(cookies[i]);
+ }
+
+ return NS_NewArrayEnumerator(aEnumerator, cookieList);
+}
+
+NS_IMETHODIMP
+nsCookieService::GetCookiesWithOriginAttributes(const nsAString& aPattern,
+ const nsACString& aHost,
+ nsISimpleEnumerator **aEnumerator)
+{
+ mozilla::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 = GetBaseDomainFromHost(host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetCookiesWithOriginAttributes(pattern, baseDomain, aEnumerator);
+}
+
+nsresult
+nsCookieService::GetCookiesWithOriginAttributes(
+ const mozilla::OriginAttributesPattern& aPattern,
+ const nsCString& aBaseDomain,
+ nsISimpleEnumerator **aEnumerator)
+{
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already closed?");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aPattern.mAppId.WasPassed() && aPattern.mAppId.Value() == NECKO_UNKNOWN_APP_ID) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMArray<nsICookie> cookies;
+ for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
+ nsCookieEntry* entry = iter.Get();
+
+ if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
+ continue;
+ }
+
+ if (!aPattern.Matches(entry->mOriginAttributes)) {
+ continue;
+ }
+
+ const nsCookieEntry::ArrayType& entryCookies = entry->GetCookies();
+
+ for (nsCookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
+ cookies.AppendObject(entryCookies[i]);
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnumerator, cookies);
+}
+
+NS_IMETHODIMP
+nsCookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
+ const nsACString& aHost)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ mozilla::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 = GetBaseDomainFromHost(host, baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
+}
+
+nsresult
+nsCookieService::RemoveCookiesWithOriginAttributes(
+ const mozilla::OriginAttributesPattern& aPattern,
+ const nsCString& aBaseDomain)
+{
+ if (!mDBState) {
+ NS_WARNING("No DBState! Profile already close?");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Iterate the hash table of nsCookieEntry.
+ for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
+ nsCookieEntry* entry = iter.Get();
+
+ if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
+ continue;
+ }
+
+ if (!aPattern.Matches(entry->mOriginAttributes)) {
+ continue;
+ }
+
+ // Pattern matches. Delete all cookies within this nsCookieEntry.
+ uint32_t cookiesCount = entry->GetCookies().Length();
+
+ for (nsCookieEntry::IndexType i = 0 ; i < cookiesCount; ++i) {
+ // Remove the first cookie from the list.
+ nsListIter iter(entry, 0);
+ RefPtr<nsCookie> cookie = iter.Cookie();
+
+ // Remove the cookie.
+ RemoveCookieFromList(iter);
+
+ if (cookie) {
+ NotifyChanged(cookie, u"deleted");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// find an secure cookie specified by host and name
+bool
+nsCookieService::FindSecureCookie(const nsCookieKey &aKey,
+ nsCookie *aCookie)
+{
+ EnsureReadDomain(aKey);
+
+ nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
+ if (!entry)
+ return false;
+
+ const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
+ for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ nsCookie *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 (DomainMatches(cookie, aCookie->Host()) ||
+ 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 (PathMatches(cookie, aCookie->Path())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// find an exact cookie specified by host, name, and path that hasn't expired.
+bool
+nsCookieService::FindCookie(const nsCookieKey &aKey,
+ const nsAFlatCString &aHost,
+ const nsAFlatCString &aName,
+ const nsAFlatCString &aPath,
+ nsListIter &aIter)
+{
+ EnsureReadDomain(aKey);
+
+ nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
+ if (!entry)
+ return false;
+
+ const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
+ for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+ nsCookie *cookie = cookies[i];
+
+ if (aHost.Equals(cookie->Host()) &&
+ aPath.Equals(cookie->Path()) &&
+ aName.Equals(cookie->Name())) {
+ aIter = nsListIter(entry, i);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// remove a cookie from the hashtable, and update the iterator state.
+void
+nsCookieService::RemoveCookieFromList(const nsListIter &aIter,
+ mozIStorageBindingParamsArray *aParamsArray)
+{
+ // if it's a non-session cookie, remove it from the db
+ if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
+ // Use the asynchronous binding methods to ensure that we do not acquire
+ // the database lock.
+ mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
+ if (!paramsArray) {
+ stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ }
+
+ nsCOMPtr<mozIStorageBindingParams> params;
+ paramsArray->NewBindingParams(getter_AddRefs(params));
+
+ DebugOnly<nsresult> rv =
+ params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
+ aIter.Cookie()->Name());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
+ aIter.Cookie()->Host());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
+ aIter.Cookie()->Path());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = paramsArray->AddParams(params);
+ NS_ASSERT_SUCCESS(rv);
+
+ // If we weren't given a params array, we'll need to remove it ourselves.
+ if (!aParamsArray) {
+ rv = stmt->BindParameters(paramsArray);
+ NS_ASSERT_SUCCESS(rv);
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
+ NS_ASSERT_SUCCESS(rv);
+ }
+ }
+
+ 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!
+ mDBState->hostTable.RawRemoveEntry(aIter.entry);
+
+ } else {
+ // just remove the element from the list
+ aIter.entry->GetCookies().RemoveElementAt(aIter.index);
+ }
+
+ --mDBState->cookieCount;
+}
+
+void
+bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
+ const nsCookieKey &aKey,
+ const nsCookie *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));
+ NS_ASSERT_SUCCESS(rv);
+
+ // Bind our values to params
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
+ aKey.mBaseDomain);
+ NS_ASSERT_SUCCESS(rv);
+
+ nsAutoCString suffix;
+ aKey.mOriginAttributes.CreateSuffix(suffix);
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
+ suffix);
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
+ aCookie->Name());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
+ aCookie->Value());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
+ aCookie->Host());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
+ aCookie->Path());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
+ aCookie->Expiry());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
+ aCookie->LastAccessed());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
+ aCookie->CreationTime());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
+ aCookie->IsSecure());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
+ aCookie->IsHttpOnly());
+ NS_ASSERT_SUCCESS(rv);
+
+ // Bind the params to the array.
+ rv = aParamsArray->AddParams(params);
+ NS_ASSERT_SUCCESS(rv);
+}
+
+void
+nsCookieService::UpdateCookieOldestTime(DBState* aDBState,
+ nsCookie* aCookie)
+{
+ if (aCookie->LastAccessed() < aDBState->cookieOldestTime) {
+ aDBState->cookieOldestTime = aCookie->LastAccessed();
+ }
+}
+
+void
+nsCookieService::AddCookieToList(const nsCookieKey &aKey,
+ nsCookie *aCookie,
+ DBState *aDBState,
+ mozIStorageBindingParamsArray *aParamsArray,
+ bool aWriteToDB)
+{
+ NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
+ "Not writing to the DB but have a params array?");
+ NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
+ "Do not have a DB connection but have a params array?");
+
+ nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
+ NS_ASSERTION(entry, "can't insert element into a null entry!");
+
+ entry->GetCookies().AppendElement(aCookie);
+ ++aDBState->cookieCount;
+
+ // keep track of the oldest cookie, for when it comes time to purge
+ UpdateCookieOldestTime(aDBState, aCookie);
+
+ // if it's a non-session cookie and hasn't just been read from the db, write it out.
+ if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
+ mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
+ if (!paramsArray) {
+ stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ }
+ bindCookieParameters(paramsArray, aKey, aCookie);
+
+ // If we were supplied an array to store parameters, we shouldn't call
+ // executeAsync - someone up the stack will do this for us.
+ if (!aParamsArray) {
+ DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
+ NS_ASSERT_SUCCESS(rv);
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
+ NS_ASSERT_SUCCESS(rv);
+ }
+ }
+}
+
+void
+nsCookieService::UpdateCookieInList(nsCookie *aCookie,
+ int64_t aLastAccessed,
+ mozIStorageBindingParamsArray *aParamsArray)
+{
+ NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
+
+ // 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(NS_LITERAL_CSTRING("lastAccessed"),
+ aLastAccessed);
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
+ aCookie->Name());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
+ aCookie->Host());
+ NS_ASSERT_SUCCESS(rv);
+
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
+ aCookie->Path());
+ NS_ASSERT_SUCCESS(rv);
+
+ // Add our bound parameters to the array.
+ rv = aParamsArray->AddParams(params);
+ NS_ASSERT_SUCCESS(rv);
+ }
+}
+
+size_t
+nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ if (mDefaultDBState) {
+ n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mPrivateDBState) {
+ n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
+
+NS_IMETHODIMP
+nsCookieService::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;
+}
diff --git a/netwerk/cookie/nsCookieService.h b/netwerk/cookie/nsCookieService.h
new file mode 100644
index 0000000000..e3b2d3e8ad
--- /dev/null
+++ b/netwerk/cookie/nsCookieService.h
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCookieService_h__
+#define nsCookieService_h__
+
+#include "nsICookieService.h"
+#include "nsICookieManager.h"
+#include "nsICookieManager2.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+#include "nsCookie.h"
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "nsHashKeys.h"
+#include "nsIMemoryReporter.h"
+#include "nsTHashtable.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageAsyncStatement.h"
+#include "mozIStoragePendingStatement.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageRow.h"
+#include "mozIStorageCompletionCallback.h"
+#include "mozIStorageStatementCallback.h"
+#include "mozIStorageFunction.h"
+#include "nsIVariant.h"
+#include "nsIFile.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Maybe.h"
+
+using mozilla::NeckoOriginAttributes;
+using mozilla::OriginAttributes;
+
+class nsICookiePermission;
+class nsIEffectiveTLDService;
+class nsIIDNService;
+class nsIPrefBranch;
+class nsIObserverService;
+class nsIURI;
+class nsIChannel;
+class nsIArray;
+class mozIStorageService;
+class mozIThirdPartyUtil;
+class ReadCookieDBListener;
+
+struct nsCookieAttributes;
+struct nsListIter;
+
+namespace mozilla {
+namespace net {
+class CookieServiceParent;
+} // namespace net
+} // namespace mozilla
+
+// hash key class
+class nsCookieKey : public PLDHashEntryHdr
+{
+public:
+ typedef const nsCookieKey& KeyType;
+ typedef const nsCookieKey* KeyTypePointer;
+
+ nsCookieKey()
+ {}
+
+ nsCookieKey(const nsCString &baseDomain, const NeckoOriginAttributes &attrs)
+ : mBaseDomain(baseDomain)
+ , mOriginAttributes(attrs)
+ {}
+
+ explicit nsCookieKey(KeyTypePointer other)
+ : mBaseDomain(other->mBaseDomain)
+ , mOriginAttributes(other->mOriginAttributes)
+ {}
+
+ nsCookieKey(KeyType other)
+ : mBaseDomain(other.mBaseDomain)
+ , mOriginAttributes(other.mOriginAttributes)
+ {}
+
+ ~nsCookieKey()
+ {}
+
+ bool KeyEquals(KeyTypePointer other) const
+ {
+ return mBaseDomain == other->mBaseDomain &&
+ mOriginAttributes == other->mOriginAttributes;
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey)
+ {
+ return &aKey;
+ }
+
+ static PLDHashNumber HashKey(KeyTypePointer aKey)
+ {
+ // TODO: more efficient way to generate hash?
+ nsAutoCString temp(aKey->mBaseDomain);
+ temp.Append('#');
+ nsAutoCString suffix;
+ aKey->mOriginAttributes.CreateSuffix(suffix);
+ temp.Append(suffix);
+ return mozilla::HashString(temp);
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ enum { ALLOW_MEMMOVE = true };
+
+ nsCString mBaseDomain;
+ NeckoOriginAttributes mOriginAttributes;
+};
+
+// Inherit from nsCookieKey so this can be stored in nsTHashTable
+// TODO: why aren't we using nsClassHashTable<nsCookieKey, ArrayType>?
+class nsCookieEntry : public nsCookieKey
+{
+ public:
+ // Hash methods
+ typedef nsTArray< RefPtr<nsCookie> > ArrayType;
+ typedef ArrayType::index_type IndexType;
+
+ explicit nsCookieEntry(KeyTypePointer aKey)
+ : nsCookieKey(aKey)
+ {}
+
+ nsCookieEntry(const nsCookieEntry& toCopy)
+ {
+ // if we end up here, things will break. nsTHashtable shouldn't
+ // allow this, since we set ALLOW_MEMMOVE to true.
+ NS_NOTREACHED("nsCookieEntry copy constructor is forbidden!");
+ }
+
+ ~nsCookieEntry()
+ {}
+
+ inline ArrayType& GetCookies() { return mCookies; }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ ArrayType mCookies;
+};
+
+// encapsulates a (key, nsCookie) tuple for temporary storage purposes.
+struct CookieDomainTuple
+{
+ nsCookieKey key;
+ RefPtr<nsCookie> cookie;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+};
+
+// encapsulates in-memory and on-disk DB states, so we can
+// conveniently switch state when entering or exiting private browsing.
+struct DBState final
+{
+ DBState() : cookieCount(0), cookieOldestTime(INT64_MAX), corruptFlag(OK)
+ {
+ }
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~DBState()
+ {
+ }
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(DBState)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // State of the database connection.
+ enum CorruptFlag {
+ OK, // normal
+ CLOSING_FOR_REBUILD, // corruption detected, connection closing
+ REBUILDING // close complete, rebuilding database from memory
+ };
+
+ nsTHashtable<nsCookieEntry> hostTable;
+ uint32_t cookieCount;
+ int64_t cookieOldestTime;
+ nsCOMPtr<nsIFile> cookieFile;
+ nsCOMPtr<mozIStorageConnection> dbConn;
+ nsCOMPtr<mozIStorageAsyncStatement> stmtInsert;
+ nsCOMPtr<mozIStorageAsyncStatement> stmtDelete;
+ nsCOMPtr<mozIStorageAsyncStatement> stmtUpdate;
+ CorruptFlag corruptFlag;
+
+ // Various parts representing asynchronous read state. These are useful
+ // while the background read is taking place.
+ nsCOMPtr<mozIStorageConnection> syncConn;
+ nsCOMPtr<mozIStorageStatement> stmtReadDomain;
+ nsCOMPtr<mozIStoragePendingStatement> pendingRead;
+ // The asynchronous read listener. This is a weak ref (storage has ownership)
+ // since it may need to outlive the DBState's database connection.
+ ReadCookieDBListener* readListener;
+ // An array of (baseDomain, cookie) tuples representing data read in
+ // asynchronously. This is merged into hostTable once read is complete.
+ nsTArray<CookieDomainTuple> hostArray;
+ // A hashset of baseDomains read in synchronously, while the async read is
+ // in flight. This is used to keep track of which data in hostArray is stale
+ // when the time comes to merge.
+ nsTHashtable<nsCookieKey> readSet;
+
+ // DB completion handlers.
+ nsCOMPtr<mozIStorageStatementCallback> insertListener;
+ nsCOMPtr<mozIStorageStatementCallback> updateListener;
+ nsCOMPtr<mozIStorageStatementCallback> removeListener;
+ nsCOMPtr<mozIStorageCompletionCallback> closeListener;
+};
+
+// 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
+};
+
+// Result codes for TryInitDB() and Read().
+enum OpenDBResult
+{
+ RESULT_OK,
+ RESULT_RETRY,
+ RESULT_FAILURE
+};
+
+/******************************************************************************
+ * nsCookieService:
+ * class declaration
+ ******************************************************************************/
+
+class nsCookieService final : public nsICookieService
+ , public nsICookieManager2
+ , public nsIObserver
+ , public nsSupportsWeakReference
+ , public nsIMemoryReporter
+{
+ private:
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSICOOKIESERVICE
+ NS_DECL_NSICOOKIEMANAGER
+ NS_DECL_NSICOOKIEMANAGER2
+ NS_DECL_NSIMEMORYREPORTER
+
+ nsCookieService();
+ static 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 void AppClearDataObserverInit();
+
+ protected:
+ virtual ~nsCookieService();
+
+ void PrefChanged(nsIPrefBranch *aPrefBranch);
+ void InitDBStates();
+ OpenDBResult TryInitDB(bool aDeleteExistingDB);
+ nsresult CreateTable();
+ nsresult CreateTableForSchemaVersion6();
+ nsresult CreateTableForSchemaVersion5();
+ void CloseDBStates();
+ void CleanupCachedStatements();
+ void CleanupDefaultDBConnection();
+ void HandleDBClosed(DBState* aDBState);
+ void HandleCorruptDB(DBState* aDBState);
+ void RebuildCorruptDB(DBState* aDBState);
+ OpenDBResult Read();
+ template<class T> nsCookie* GetCookieFromRow(T &aRow, const OriginAttributes& aOriginAttributes);
+ void AsyncReadComplete();
+ void CancelAsyncRead(bool aPurgeReadSet);
+ void EnsureReadDomain(const nsCookieKey &aKey);
+ void EnsureReadComplete();
+ nsresult NormalizeHost(nsCString &aHost);
+ nsresult GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, bool &aRequireHostMatch);
+ nsresult GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain);
+ nsresult GetCookieStringCommon(nsIURI *aHostURI, nsIChannel *aChannel, bool aHttpBound, char** aCookie);
+ void GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, const NeckoOriginAttributes aOriginAttrs, bool aIsPrivate, nsCString &aCookie);
+ nsresult SetCookieStringCommon(nsIURI *aHostURI, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, bool aFromHttp);
+ void SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, const NeckoOriginAttributes &aOriginAttrs, bool aIsPrivate, nsIChannel* aChannel);
+ bool SetCookieInternal(nsIURI *aHostURI, const nsCookieKey& aKey, bool aRequireHostMatch, CookieStatus aStatus, nsDependentCString &aCookieHeader, int64_t aServerTime, bool aFromHttp, nsIChannel* aChannel);
+ void AddInternal(const nsCookieKey& aKey, nsCookie *aCookie, int64_t aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, bool aFromHttp);
+ void RemoveCookieFromList(const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray = nullptr);
+ void AddCookieToList(const nsCookieKey& aKey, nsCookie *aCookie, DBState *aDBState, mozIStorageBindingParamsArray *aParamsArray, bool aWriteToDB = true);
+ void UpdateCookieInList(nsCookie *aCookie, int64_t aLastAccessed, mozIStorageBindingParamsArray *aParamsArray);
+ static bool GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, bool &aEqualsFound);
+ static bool ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie);
+ bool RequireThirdPartyCheck();
+ CookieStatus CheckPrefs(nsIURI *aHostURI, bool aIsForeign, const char *aCookieHeader);
+ bool CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, bool aRequireHostMatch);
+ static bool CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
+ static bool CheckPrefixes(nsCookieAttributes &aCookie, bool aSecureRequest);
+ static bool GetExpiry(nsCookieAttributes &aCookie, int64_t aServerTime, int64_t aCurrentTime);
+ void RemoveAllFromMemory();
+ already_AddRefed<nsIArray> PurgeCookies(int64_t aCurrentTimeInUsec);
+ bool FindCookie(const nsCookieKey& aKey, const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter);
+ bool FindSecureCookie(const nsCookieKey& aKey, nsCookie* aCookie);
+ int64_t FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsIURI* aSource, mozilla::Maybe<bool> aIsSecure, nsListIter &aIter);
+ void TelemetryForEvictingStaleCookie(nsCookie* aEvicted, int64_t oldestCookieTime);
+ void NotifyRejected(nsIURI *aHostURI);
+ void NotifyThirdParty(nsIURI *aHostURI, bool aAccepted, nsIChannel *aChannel);
+ void NotifyChanged(nsISupports *aSubject, const char16_t *aData);
+ void NotifyPurged(nsICookie2* aCookie);
+ already_AddRefed<nsIArray> CreatePurgeList(nsICookie2* aCookie);
+ void UpdateCookieOldestTime(DBState* aDBState, nsCookie* aCookie);
+
+ nsresult GetCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern& aPattern, const nsCString& aBaseDomain, nsISimpleEnumerator **aEnumerator);
+ nsresult RemoveCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern& aPattern, const nsCString& aBaseDomain);
+
+ /**
+ * This method is a helper that allows calling nsICookieManager::Remove()
+ * with NeckoOriginAttributes parameter.
+ * NOTE: this could be added to a public interface if we happen to need it.
+ */
+ nsresult Remove(const nsACString& aHost, const NeckoOriginAttributes& aAttrs,
+ const nsACString& aName, const nsACString& aPath,
+ bool aBlocked);
+
+ protected:
+ // cached members.
+ nsCOMPtr<nsICookiePermission> mPermissionService;
+ nsCOMPtr<mozIThirdPartyUtil> mThirdPartyUtil;
+ nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+ nsCOMPtr<nsIIDNService> mIDNService;
+ nsCOMPtr<mozIStorageService> mStorageService;
+
+ // we have two separate DB states: one for normal browsing and one for
+ // private browsing, switching between them on a per-cookie-request basis.
+ // this state encapsulates both the in-memory table and the on-disk DB.
+ // note that the private states' dbConn should always be null - we never
+ // want to be dealing with the on-disk DB when in private browsing.
+ DBState *mDBState;
+ RefPtr<DBState> mDefaultDBState;
+ RefPtr<DBState> mPrivateDBState;
+
+ // cached prefs
+ uint8_t mCookieBehavior; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT, LIMITFOREIGN}
+ bool mThirdPartySession;
+ bool mLeaveSecureAlone;
+ uint16_t mMaxNumberOfCookies;
+ uint16_t mMaxCookiesPerHost;
+ int64_t mCookiePurgeAge;
+
+ // friends!
+ friend class DBListenerErrorHandler;
+ friend class ReadCookieDBListener;
+ friend class CloseCookieDBListener;
+
+ static nsCookieService* GetSingleton();
+ friend class mozilla::net::CookieServiceParent;
+};
+
+#endif // nsCookieService_h__
diff --git a/netwerk/cookie/nsICookie.idl b/netwerk/cookie/nsICookie.idl
new file mode 100644
index 0000000000..e8dadb9491
--- /dev/null
+++ b/netwerk/cookie/nsICookie.idl
@@ -0,0 +1,85 @@
+/* -*- 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"
+
+/**
+ * An optional interface for accessing the HTTP or
+ * javascript cookie object
+ */
+
+typedef long nsCookieStatus;
+typedef long nsCookiePolicy;
+
+[scriptable, uuid(adf0db5e-211e-45a3-be14-4486ac430a58)]
+interface nsICookie : nsISupports {
+
+ /**
+ * 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 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 nsICookie2.expiry and nsICookie2.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;
+
+ /**
+ * @DEPRECATED status implementation will return STATUS_UNKNOWN in all cases.
+ */
+ const nsCookieStatus STATUS_UNKNOWN=0;
+ const nsCookieStatus STATUS_ACCEPTED=1;
+ const nsCookieStatus STATUS_DOWNGRADED=2;
+ const nsCookieStatus STATUS_FLAGGED=3;
+ const nsCookieStatus STATUS_REJECTED=4;
+ readonly attribute nsCookieStatus status;
+
+ /**
+ * @DEPRECATED policy implementation will return POLICY_UNKNOWN in all cases.
+ */
+ const nsCookiePolicy POLICY_UNKNOWN=0;
+ const nsCookiePolicy POLICY_NONE=1;
+ const nsCookiePolicy POLICY_NO_CONSENT=2;
+ const nsCookiePolicy POLICY_IMPLICIT_CONSENT=3;
+ const nsCookiePolicy POLICY_EXPLICIT_CONSENT=4;
+ const nsCookiePolicy POLICY_NO_II=5;
+ readonly attribute nsCookiePolicy policy;
+
+ /**
+ * The origin attributes for this cookie
+ */
+ [implicit_jscontext]
+ readonly attribute jsval originAttributes;
+};
diff --git a/netwerk/cookie/nsICookie2.idl b/netwerk/cookie/nsICookie2.idl
new file mode 100644
index 0000000000..62a97f5bbb
--- /dev/null
+++ b/netwerk/cookie/nsICookie2.idl
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICookie.idl"
+
+/**
+ * Main cookie object interface for use by consumers:
+ * extends nsICookie, a frozen interface for external
+ * access of cookie objects
+ */
+
+[scriptable, uuid(05c420e5-03d0-4c7b-a605-df7ebe5ca326)]
+
+interface nsICookie2 : nsICookie
+{
+
+ /**
+ * the host (possibly fully qualified) of the cookie,
+ * without a leading dot to represent if it is a
+ * domain cookie.
+ */
+ readonly attribute AUTF8String rawHost;
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+};
diff --git a/netwerk/cookie/nsICookieManager.idl b/netwerk/cookie/nsICookieManager.idl
new file mode 100644
index 0000000000..daea98a4e0
--- /dev/null
+++ b/netwerk/cookie/nsICookieManager.idl
@@ -0,0 +1,88 @@
+/* -*- 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 NeckoOriginAttributes;
+} // mozilla namespace
+%}
+
+[ptr] native NeckoOriginAttributesPtr(mozilla::NeckoOriginAttributes);
+
+interface nsISimpleEnumerator;
+
+[scriptable, function, uuid(20709db8-8dad-4e45-b33e-6e7c761dfc5d)]
+interface nsIPrivateModeCallback : nsISupports
+{
+ void callback();
+};
+
+/**
+ * An optional interface for accessing or removing the cookies
+ * that are in the cookie list
+ */
+
+[scriptable, uuid(AAAB6710-0F2C-11d5-A53B-0010A401EB10)]
+interface nsICookieManager : nsISupports
+{
+
+ /**
+ * Called to remove all cookies from the cookie list
+ */
+ void removeAll();
+
+ /**
+ * Called to enumerate through each cookie in the cookie list.
+ * The objects enumerated over are of type nsICookie
+ */
+ readonly attribute nsISimpleEnumerator enumerator;
+
+ /**
+ * 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
+ * nsICookieManager2::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. This
+ * attribute is optional to avoid breaking add-ons.
+ * In 1 or 2 releases it will be mandatory: bug 1260399.
+ * @param aBlocked Indicates if cookies from this host should be permanently
+ * blocked.
+ *
+ */
+ [implicit_jscontext, optional_argc]
+ void remove(in AUTF8String aHost,
+ in ACString aName,
+ in AUTF8String aPath,
+ in boolean aBlocked,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult removeNative(in AUTF8String aHost,
+ in ACString aName,
+ in AUTF8String aPath,
+ in boolean aBlocked,
+ in NeckoOriginAttributesPtr aOriginAttributes);
+
+ /**
+ * Set the cookie manager to work on private or non-private cookies for the
+ * duration of the callback.
+ *
+ * @param aIsPrivate True to work on private cookies, false to work on
+ * non-private cookies.
+ * @param aCallback Methods on the cookie manager interface will work on
+ * private or non-private cookies for the duration of this
+ * callback.
+ */
+ void usePrivateMode(in boolean aIsPrivate, in nsIPrivateModeCallback aCallback);
+};
diff --git a/netwerk/cookie/nsICookieManager2.idl b/netwerk/cookie/nsICookieManager2.idl
new file mode 100644
index 0000000000..f20618bfae
--- /dev/null
+++ b/netwerk/cookie/nsICookieManager2.idl
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICookieManager.idl"
+
+interface nsICookie2;
+interface nsIFile;
+
+/**
+ * Additions to the frozen nsICookieManager
+ */
+
+[scriptable, uuid(daf0caa7-b431-4b4d-ba51-08c179bb9dfe)]
+interface nsICookieManager2 : nsICookieManager
+{
+ /**
+ * 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. This
+ * attribute is optional to avoid breaking add-ons.
+ */
+ [implicit_jscontext, optional_argc]
+ void add(in AUTF8String aHost,
+ in AUTF8String aPath,
+ in ACString aName,
+ in ACString aValue,
+ in boolean aIsSecure,
+ in boolean aIsHttpOnly,
+ in boolean aIsSession,
+ in int64_t aExpiry,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult addNative(in AUTF8String aHost,
+ in AUTF8String aPath,
+ in ACString aName,
+ in ACString aValue,
+ in boolean aIsSecure,
+ in boolean aIsHttpOnly,
+ in boolean aIsSession,
+ in int64_t aExpiry,
+ in NeckoOriginAttributesPtr aOriginAttributes);
+
+ /**
+ * Find whether a given cookie already exists.
+ *
+ * @param aCookie
+ * the cookie to look for
+ * @param aOriginAttributes
+ * nsICookie2 contains an originAttributes but if nsICookie2 is
+ * implemented in JS, we can't retrieve its originAttributes because
+ * the getter is marked [implicit_jscontext]. This optional parameter
+ * is a workaround.
+ *
+ * @return true if a cookie was found which matches the host, path, and name
+ * fields of aCookie
+ */
+ [implicit_jscontext, optional_argc]
+ boolean cookieExists(in nsICookie2 aCookie,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult cookieExistsNative(in nsICookie2 aCookie,
+ in NeckoOriginAttributesPtr 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 enumerator 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. This attribute is optional to avoid
+ * breaking add-ons.
+ *
+ * @return an nsISimpleEnumerator of nsICookie2 objects.
+ *
+ * @see countCookiesFromHost
+ */
+ [implicit_jscontext, optional_argc]
+ nsISimpleEnumerator getCookiesFromHost(in AUTF8String aHost,
+ [optional] in jsval aOriginAttributes);
+
+ /**
+ * Import an old-style cookie file. Imported cookies will be added to the
+ * existing database. If the database contains any cookies the same as those
+ * being imported (i.e. domain, name, and path match), they will be replaced.
+ *
+ * @param aCookieFile the file to import, usually cookies.txt
+ */
+ void importCookies(in nsIFile aCookieFile);
+
+ /**
+ * Returns an enumerator 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.
+ */
+ nsISimpleEnumerator getCookiesWithOriginAttributes(in DOMString 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 DOMString aPattern,
+ [optional] in AUTF8String aHost);
+};
diff --git a/netwerk/cookie/nsICookiePermission.idl b/netwerk/cookie/nsICookiePermission.idl
new file mode 100644
index 0000000000..fd4a879f9b
--- /dev/null
+++ b/netwerk/cookie/nsICookiePermission.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsICookie2;
+interface nsIURI;
+interface nsIChannel;
+
+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;
+ const nsCookieAccess ACCESS_ALLOW_FIRST_PARTY_ONLY = 9;
+ const nsCookieAccess ACCESS_LIMIT_THIRD_PARTY = 10;
+
+ /**
+ * setAccess
+ *
+ * this method is called to block cookie access for the given URI. this
+ * may result in other URIs being blocked as well (e.g., URIs which share
+ * the same host name).
+ *
+ * @param aURI
+ * the URI to block
+ * @param aAccess
+ * the new cookie access for the URI.
+ */
+ void setAccess(in nsIURI aURI,
+ in nsCookieAccess aAccess);
+
+ /**
+ * canAccess
+ *
+ * this method is called to test whether or not the given URI/channel may
+ * access the cookie database, either to set or get cookies.
+ *
+ * @param aURI
+ * the URI trying to access cookies
+ * @param aChannel
+ * the channel corresponding to aURI
+ *
+ * @return one of the following nsCookieAccess values:
+ * ACCESS_DEFAULT, ACCESS_ALLOW, ACCESS_DENY, or
+ * ACCESS_ALLOW_FIRST_PARTY_ONLY
+ */
+ nsCookieAccess canAccess(in nsIURI aURI,
+ in nsIChannel aChannel);
+
+ /**
+ * canSetCookie
+ *
+ * this method is called to test whether or not the given URI/channel may
+ * set a specific cookie. this method is always preceded by a call to
+ * canAccess. it may modify the isSession and expiry attributes of the
+ * cookie via the aIsSession and aExpiry parameters, in order to limit
+ * or extend the lifetime of the cookie. this is useful, for instance, to
+ * downgrade a cookie to session-only if it fails to meet certain criteria.
+ *
+ * @param aURI
+ * the URI trying to set the cookie
+ * @param aChannel
+ * the channel corresponding to aURI
+ * @param aCookie
+ * the cookie being added to the cookie database
+ * @param aIsSession
+ * when canSetCookie is invoked, this is the current isSession attribute
+ * of the cookie. canSetCookie may leave this value unchanged to
+ * preserve this attribute of the cookie.
+ * @param aExpiry
+ * when canSetCookie is invoked, this is the current expiry time of
+ * the cookie. canSetCookie may leave this value unchanged to
+ * preserve this attribute of the cookie.
+ *
+ * @return true if the cookie can be set.
+ */
+ boolean canSetCookie(in nsIURI aURI,
+ in nsIChannel aChannel,
+ in nsICookie2 aCookie,
+ inout boolean aIsSession,
+ inout int64_t aExpiry);
+};
+
+%{ C++
+/**
+ * The nsICookiePermission implementation is an XPCOM service registered
+ * under the ContractID:
+ */
+#define NS_COOKIEPERMISSION_CONTRACTID "@mozilla.org/cookie/permission;1"
+%}
diff --git a/netwerk/cookie/nsICookieService.idl b/netwerk/cookie/nsICookieService.idl
new file mode 100644
index 0000000000..f876c61b42
--- /dev/null
+++ b/netwerk/cookie/nsICookieService.idl
@@ -0,0 +1,193 @@
+/* -*- 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 nsIPrompt;
+interface nsIChannel;
+
+/**
+ * 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 nsICookie2 representing
+ * the deleted cookie.
+ * "added"
+ * a cookie was added. the subject is an nsICookie2 representing
+ * the added cookie.
+ * "changed"
+ * a cookie was changed. the subject is an nsICookie2 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 nsICookie2'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.
+ *
+ * topic : "third-party-cookie-accepted"
+ * broadcast whenever a third party cookie was accepted
+ * subject: an nsIURI interface pointer representing the URI that attempted
+ * to set the cookie.
+ * data : the referrer, or "?" if unknown
+ *
+ * topic : "third-party-cookie-rejected"
+ * broadcast whenever a third party cookie was rejected
+ * subject: an nsIURI interface pointer representing the URI that attempted
+ * to set the cookie.
+ * data : the referrer, or "?" if unknown
+ */
+[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
+
+ /*
+ * 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
+ const uint32_t ACCEPT_FOR_N_DAYS = 3; // limit lifetime to N days
+
+ /*
+ * Get the complete cookie string associated with the URI.
+ *
+ * @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. this parameter should not
+ * be null, otherwise the cookies will not be returned if third-party
+ * cookies have been disabled by the user. (the channel is used
+ * to determine the originating URI of the document; if it is not
+ * provided, the cookies will be assumed third-party.)
+ *
+ * @return the resulting cookie string
+ */
+ string getCookieString(in nsIURI aURI, in nsIChannel aChannel);
+
+ /*
+ * 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 aFirstURI
+ * the URI that the user originally typed in or clicked on to initiate
+ * the load of the document referenced by aURI.
+ * @param aChannel
+ * the channel used to load the document. this parameter should not
+ * be null, otherwise the cookies will not be returned if third-party
+ * cookies have been disabled by the user. (the channel is used
+ * to determine the originating URI of the document; if it is not
+ * provided, the cookies will be assumed third-party.)
+ *
+ * @return the resulting cookie string
+ */
+ string getCookieStringFromHttp(in nsIURI aURI, in nsIURI aFirstURI, in nsIChannel aChannel);
+
+ /*
+ * Set the cookie string associated with the URI.
+ *
+ * @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 aPrompt
+ * the prompt to use for all user-level cookie notifications. This is
+ * presently ignored and can be null. (Prompt information is determined
+ * from the channel if necessary.)
+ * @param aCookie
+ * the cookie string to set.
+ * @param aChannel
+ * the channel used to load the document. this parameter should not
+ * be null, otherwise the cookies will not be set if third-party
+ * cookies have been disabled by the user. (the channel is used
+ * to determine the originating URI of the document; if it is not
+ * provided, the cookies will be assumed third-party.)
+ */
+ void setCookieString(in nsIURI aURI, in nsIPrompt aPrompt, in string aCookie, in nsIChannel aChannel);
+
+ /*
+ * 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 aFirstURI
+ * the URI that the user originally typed in or clicked on to initiate
+ * the load of the document referenced by aURI.
+ * @param aPrompt
+ * the prompt to use for all user-level cookie notifications. This is
+ * presently ignored and can be null. (Prompt information is determined
+ * from the channel if necessary.)
+ * @param aCookie
+ * the cookie string to set.
+ * @param aServerTime
+ * the current time reported by the server, if available. This should
+ * be the string from the Date header in an HTTP response. If the
+ * string is empty or null, server time is assumed to be the current
+ * local time. If provided, it will be used to calculate the expiry
+ * time of the cookie relative to the server's local time.
+ * @param aChannel
+ * the channel used to load the document. this parameter should not
+ * be null, otherwise the cookies will not be set if third-party
+ * cookies have been disabled by the user. (the channel is used
+ * to determine the originating URI of the document; if it is not
+ * provided, the cookies will be assumed third-party.)
+ */
+ void setCookieStringFromHttp(in nsIURI aURI, in nsIURI aFirstURI, in nsIPrompt aPrompt, in string aCookie, in string aServerTime, in nsIChannel aChannel);
+};
diff --git a/netwerk/cookie/test/browser/browser.ini b/netwerk/cookie/test/browser/browser.ini
new file mode 100644
index 0000000000..342e145789
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ file_empty.html
+
+[browser_originattributes.js]
diff --git a/netwerk/cookie/test/browser/browser_originattributes.js b/netwerk/cookie/test/browser/browser_originattributes.js
new file mode 100644
index 0000000000..617d52e359
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser_originattributes.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let { classes: Cc, interfaces: Ci } = Components;
+
+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.nsICookieManager2);
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+function* openTabInUserContext(uri, userContextId) {
+ // open the tab in the correct userContextId
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerDocument.defaultView.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ // wait for tab load
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ return {tab, browser};
+}
+
+add_task(function* setup() {
+ // make sure userContext is enabled.
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]}, resolve);
+ });
+});
+
+add_task(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} = yield* openTabInUserContext(TEST_URL, userContextId);
+
+ yield ContentTask.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);
+ yield checkCookies(expectedValues, "before removal");
+
+ // remove cookies that belongs to user context id #1
+ cm.removeCookiesWithOriginAttributes(JSON.stringify({userContextId: 1}));
+
+ expectedValues[1] = undefined;
+ yield checkCookies(expectedValues, "after removal");
+});
+
+function *checkCookies(expectedValues, time) {
+ for (let userContextId of Object.keys(expectedValues)) {
+ let cookiesFromTitle = yield* 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 enumerator = cm.getCookiesWithOriginAttributes(JSON.stringify({userContextId}));
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+ cookies[cookie.name] = cookie.value;
+ }
+ return cookies;
+}
+
+function* getCookiesFromJS(userContextId) {
+ let {tab, browser} = yield* openTabInUserContext(TEST_URL, userContextId);
+
+ // get the cookies
+ let cookieString = yield ContentTask.spawn(browser, null, 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/file_empty.html b/netwerk/cookie/test/browser/file_empty.html
new file mode 100644
index 0000000000..5a08c42053
--- /dev/null
+++ b/netwerk/cookie/test/browser/file_empty.html
@@ -0,0 +1,3 @@
+<html><body>
+</body></html>
+
diff --git a/netwerk/cookie/test/unit/test_bug1155169.js b/netwerk/cookie/test/unit/test_bug1155169.js
new file mode 100644
index 0000000000..6806ffe6d5
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug1155169.js
@@ -0,0 +1,73 @@
+var {utils: Cu, interfaces: Ci, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const URI = Services.io.newURI("http://example.org/", null, null);
+
+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(value, expected = null) {
+ function observer(subject, topic, data) {
+ if (!expected) {
+ do_throw("no notification expected");
+ return;
+ }
+
+ // Check we saw the right notification.
+ do_check_eq(data, expected.type);
+
+ // Check cookie details.
+ let cookie = subject.QueryInterface(Ci.nsICookie2);
+ do_check_eq(cookie.isSession, expected.isSession);
+ do_check_eq(cookie.isSecure, expected.isSecure);
+ do_check_eq(cookie.isHttpOnly, expected.isHttpOnly);
+ }
+
+ Services.obs.addObserver(observer, "cookie-changed", false);
+ cs.setCookieStringFromHttp(URI, null, null, value, null, null);
+ 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_bug1267910.js b/netwerk/cookie/test/unit/test_bug1267910.js
new file mode 100644
index 0000000000..93ea5e1328
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug1267910.js
@@ -0,0 +1,196 @@
+/*
+ * Bug 1267910 - Add test cases for the backward compatiability and originAttributes
+ * of nsICookieManager2.
+ */
+
+var {utils: Cu, interfaces: Ci, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const BASE_URL = "http://example.org/";
+
+const COOKIE = {
+ host: BASE_URL,
+ path: "/",
+ name: "test1",
+ value: "yes",
+ isSecure: false,
+ isHttpOnly: false,
+ isSession: true,
+ expiry: 2145934800,
+};
+
+const COOKIE_OA_DEFAULT = {
+ host: BASE_URL,
+ path: "/",
+ name: "test0",
+ value: "yes0",
+ isSecure: false,
+ isHttpOnly: false,
+ isSession: true,
+ expiry: 2145934800,
+ originAttributes: {},
+};
+
+const COOKIE_OA_1 = {
+ host: BASE_URL,
+ path: "/",
+ name: "test1",
+ value: "yes1",
+ isSecure: false,
+ isHttpOnly: false,
+ isSession: true,
+ expiry: 2145934800,
+ originAttributes: {userContextId: 1},
+};
+
+function checkCookie(cookie, cookieObj) {
+ for (let prop of Object.keys(cookieObj)) {
+ if (prop === "originAttributes") {
+ ok(ChromeUtils.isOriginAttributesEqual(cookie[prop], cookieObj[prop]),
+ "Check cookie: " + prop);
+ } else {
+ equal(cookie[prop], cookieObj[prop], "Check cookie: " + prop);
+ }
+ }
+}
+
+function countCookies(enumerator) {
+ let cnt = 0;
+
+ while (enumerator.hasMoreElements()) {
+ cnt++;
+ enumerator.getNext();
+ }
+
+ return cnt;
+}
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // Enable user context id
+ Services.prefs.setBoolPref("privacy.userContext.enabled", true);
+
+ add_test(test_backward_compatiability);
+ add_test(test_originAttributes);
+
+
+ run_next_test();
+}
+
+/*
+ * Test for backward compatiablility that APIs works correctly without
+ * originAttributes.
+ */
+function test_backward_compatiability() {
+ // Clear cookies.
+ Services.cookies.removeAll();
+
+ // Call Add() to add a cookie without originAttributes
+ Services.cookies.add(COOKIE.host,
+ COOKIE.path,
+ COOKIE.name,
+ COOKIE.value,
+ COOKIE.isSecure,
+ COOKIE.isHttpOnly,
+ COOKIE.isSession,
+ COOKIE.expiry);
+
+ // Call getCookiesFromHost() to get cookies without originAttributes
+ let enumerator = Services.cookies.getCookiesFromHost(BASE_URL);
+
+ ok(enumerator.hasMoreElements(), "Cookies available");
+ let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+
+ checkCookie(foundCookie, COOKIE);
+
+ ok(!enumerator.hasMoreElements(), "We should get only one cookie");
+
+ run_next_test();
+}
+
+/*
+ * Test for originAttributes.
+ */
+function test_originAttributes() {
+ // Clear cookies.
+ Services.cookies.removeAll();
+
+ // Add a cookie for default originAttributes.
+ Services.cookies.add(COOKIE_OA_DEFAULT.host,
+ COOKIE_OA_DEFAULT.path,
+ COOKIE_OA_DEFAULT.name,
+ COOKIE_OA_DEFAULT.value,
+ COOKIE_OA_DEFAULT.isSecure,
+ COOKIE_OA_DEFAULT.isHttpOnly,
+ COOKIE_OA_DEFAULT.isSession,
+ COOKIE_OA_DEFAULT.expiry,
+ COOKIE_OA_DEFAULT.originAttributes);
+
+ // Get cookies for default originAttributes.
+ let enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_DEFAULT.originAttributes);
+
+ // Check that do we get cookie correctly.
+ ok(enumerator.hasMoreElements(), "Cookies available");
+ let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ checkCookie(foundCookie, COOKIE_OA_DEFAULT);
+
+ // We should only get one cookie.
+ ok(!enumerator.hasMoreElements(), "We should get only one cookie");
+
+ // Get cookies for originAttributes with user context id 1.
+ enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_1.originAttributes);
+
+ // Check that we will not get cookies if the originAttributes is different.
+ ok(!enumerator.hasMoreElements(), "No cookie should be here");
+
+ // Add a cookie for originAttributes with user context id 1.
+ Services.cookies.add(COOKIE_OA_1.host,
+ COOKIE_OA_1.path,
+ COOKIE_OA_1.name,
+ COOKIE_OA_1.value,
+ COOKIE_OA_1.isSecure,
+ COOKIE_OA_1.isHttpOnly,
+ COOKIE_OA_1.isSession,
+ COOKIE_OA_1.expiry,
+ COOKIE_OA_1.originAttributes);
+
+ // Get cookies for originAttributes with user context id 1.
+ enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_1.originAttributes);
+
+ // Check that do we get cookie correctly.
+ ok(enumerator.hasMoreElements(), "Cookies available");
+ foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ checkCookie(foundCookie, COOKIE_OA_1);
+
+ // We should only get one cookie.
+ ok(!enumerator.hasMoreElements(), "We should get only one cookie");
+
+ // Check that add a cookie will not affect cookies in different originAttributes.
+ enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_DEFAULT.originAttributes);
+ equal(countCookies(enumerator), 1, "We should get only one cookie for default originAttributes");
+
+ // Remove a cookie for originAttributes with user context id 1.
+ Services.cookies.remove(COOKIE_OA_1.host, COOKIE_OA_1.name, COOKIE_OA_1.path,
+ false, COOKIE_OA_1.originAttributes);
+
+ // Check that remove will not affect cookies in default originAttributes.
+ enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_DEFAULT.originAttributes);
+ equal(countCookies(enumerator), 1, "Get one cookie for default originAttributes.");
+
+ // Check that should be no cookie for originAttributes with user context id 1.
+ enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_1.originAttributes);
+ equal(countCookies(enumerator), 0, "No cookie shold be here");
+
+ // Remove a cookie for default originAttributes.
+ Services.cookies.remove(COOKIE_OA_DEFAULT.host, COOKIE_OA_DEFAULT.name, COOKIE_OA_DEFAULT.path,
+ false, COOKIE_OA_DEFAULT.originAttributes);
+
+ // Check remove() works correctly for default originAttributes.
+ enumerator = Services.cookies.getCookiesFromHost(BASE_URL, COOKIE_OA_DEFAULT.originAttributes);
+ equal(countCookies(enumerator), 0, "No cookie shold be here");
+
+ run_next_test();
+}
diff --git a/netwerk/cookie/test/unit/test_bug643051.js b/netwerk/cookie/test/unit/test_bug643051.js
new file mode 100644
index 0000000000..d6695054ea
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug643051.js
@@ -0,0 +1,29 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ let cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ let uri = NetUtil.newURI("http://example.org/");
+
+ let set = "foo=bar\nbaz=foo";
+ let expected = "foo=bar; baz=foo";
+ cs.setCookieStringFromHttp(uri, null, null, set, null, null);
+
+ let actual = cs.getCookieStringFromHttp(uri, null, null);
+ do_check_eq(actual, expected);
+
+ uri = NetUtil.newURI("http://example.com/");
+ cs.setCookieString(uri, null, set, null);
+
+ expected = "foo=bar";
+ actual = cs.getCookieString(uri, null, null);
+ do_check_eq(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..7f693ee94b
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_eviction.js
@@ -0,0 +1,296 @@
+var {utils: Cu, interfaces: Ci, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const BASE_HOSTNAMES = ["example.org", "example.co.uk"];
+const SUBDOMAINS = ["", "pub.", "www.", "other."];
+
+const cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+const cm = cs.QueryInterface(Ci.nsICookieManager2);
+
+function run_test() {
+ var tests = [];
+ Services.prefs.setIntPref("network.cookie.staleThreshold", 0);
+ for (var host of BASE_HOSTNAMES) {
+ var base = SUBDOMAINS[0] + host;
+ var sub = SUBDOMAINS[1] + host;
+ var other = SUBDOMAINS[2] + host;
+ var another = SUBDOMAINS[3] + host;
+ tests.push([host, test_basic_eviction.bind(this, base, sub, other, another)]);
+ add_task(function* a() {
+ var t = tests.splice(0, 1)[0];
+ do_print('testing with host ' + t[0]);
+ yield t[1]();
+ cm.removeAll();
+ });
+ tests.push([host, test_domain_or_path_matches_not_both.bind(this, base, sub, other, another)]);
+ add_task(function*() {
+ var t = tests.splice(0, 1)[0];
+ do_print('testing with host ' + t[0]);
+ yield t[1]();
+ cm.removeAll();
+ });
+ }
+ add_task(function*() {
+ yield test_localdomain();
+ cm.removeAll();
+ });
+
+ add_task(function*() {
+ yield test_path_prefix();
+ });
+
+ run_next_test();
+}
+
+// Verify that cookies that share a path prefix with the URI path are still considered
+// candidates for eviction, since the paths do not actually match.
+function* test_path_prefix() {
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 2);
+
+ const BASE_URI = Services.io.newURI("http://example.org/", null, null);
+ const BASE_BAR = Services.io.newURI("http://example.org/bar/", null, null);
+ const BASE_BARBAR = Services.io.newURI("http://example.org/barbar/", null, null);
+
+ yield setCookie("session_first", null, null, null, BASE_URI);
+ yield setCookie("session_second", null, "/bar", null, BASE_BAR);
+ verifyCookies(['session_first', 'session_second'], BASE_URI);
+
+ yield setCookie("session_third", null, "/barbar", null, BASE_BARBAR);
+ verifyCookies(['session_first', 'session_third'], BASE_URI);
+}
+
+// Verify that subdomains of localhost are treated as separate hosts and aren't considered
+// candidates for eviction.
+function* test_localdomain() {
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 2);
+
+ const BASE_URI = Services.io.newURI("http://localhost", null, null);
+ const BASE_BAR = Services.io.newURI("http://localhost/bar", null, null);
+ const OTHER_URI = Services.io.newURI("http://other.localhost", null, null);
+ const OTHER_BAR = Services.io.newURI("http://other.localhost/bar", null, null);
+
+ yield setCookie("session_no_path", null, null, null, BASE_URI);
+ yield setCookie("session_bar_path", null, "/bar", null, BASE_BAR);
+
+ yield setCookie("session_no_path", null, null, null, OTHER_URI);
+ yield setCookie("session_bar_path", null, "/bar", null, OTHER_BAR);
+
+ verifyCookies(['session_no_path',
+ 'session_bar_path'], BASE_URI);
+ verifyCookies(['session_no_path',
+ 'session_bar_path'], OTHER_URI);
+
+ yield setCookie("session_another_no_path", null, null, null, BASE_URI);
+ verifyCookies(['session_no_path',
+ 'session_another_no_path'], BASE_URI);
+
+ yield setCookie("session_another_no_path", null, null, null, OTHER_URI);
+ verifyCookies(['session_no_path',
+ 'session_another_no_path'], OTHER_URI);
+}
+
+// Ensure that cookies are still considered candidates for eviction if either the domain
+// or path matches, but not both.
+function* test_domain_or_path_matches_not_both(base_host,
+ subdomain_host,
+ other_subdomain_host,
+ another_subdomain_host) {
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 2);
+
+ const BASE_URI = Services.io.newURI("http://" + base_host, null, null);
+ const PUB_FOO_PATH = Services.io.newURI("http://" + subdomain_host + "/foo/", null, null);
+ const WWW_BAR_PATH = Services.io.newURI("http://" + other_subdomain_host + "/bar/", null, null);
+ const OTHER_BAR_PATH = Services.io.newURI("http://" + another_subdomain_host + "/bar/", null, null);
+ const PUB_BAR_PATH = Services.io.newURI("http://" + subdomain_host + "/bar/", null, null);
+ const WWW_FOO_PATH = Services.io.newURI("http://" + other_subdomain_host + "/foo/", null, null);
+
+ yield setCookie("session_pub_with_foo_path", subdomain_host, "/foo", null, PUB_FOO_PATH);
+ yield setCookie("session_www_with_bar_path", other_subdomain_host, "/bar", null, WWW_BAR_PATH);
+ verifyCookies(['session_pub_with_foo_path',
+ 'session_www_with_bar_path'], BASE_URI);
+
+ yield setCookie("session_pub_with_bar_path", subdomain_host, "/bar", null, PUB_BAR_PATH);
+ verifyCookies(['session_www_with_bar_path',
+ 'session_pub_with_bar_path'], BASE_URI);
+
+ yield setCookie("session_other_with_bar_path", another_subdomain_host, "/bar", null, OTHER_BAR_PATH);
+ verifyCookies(['session_pub_with_bar_path',
+ 'session_other_with_bar_path'], BASE_URI);
+}
+
+function* test_basic_eviction(base_host, subdomain_host, other_subdomain_host) {
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 5);
+
+ const BASE_URI = Services.io.newURI("http://" + base_host, null, null);
+ const SUBDOMAIN_URI = Services.io.newURI("http://" + subdomain_host, null, null);
+ const OTHER_SUBDOMAIN_URI = Services.io.newURI("http://" + other_subdomain_host, null, null);
+ const FOO_PATH = Services.io.newURI("http://" + base_host + "/foo/", null, null);
+ const BAR_PATH = Services.io.newURI("http://" + base_host + "/bar/", null, null);
+ const ALL_SUBDOMAINS = '.' + base_host;
+ const OTHER_SUBDOMAIN = other_subdomain_host;
+
+ // Initialize the set of cookies with a mix of non-session cookies with no path,
+ // and session cookies with explicit paths. Any subsequent cookies added will cause
+ // existing cookies to be evicted.
+ yield setCookie("non_session_non_path_non_domain", null, null, 100000, BASE_URI);
+ yield setCookie("non_session_non_path_subdomain", ALL_SUBDOMAINS, null, 100000, SUBDOMAIN_URI);
+ yield setCookie("session_non_path_pub_domain", OTHER_SUBDOMAIN, null, null, OTHER_SUBDOMAIN_URI);
+ yield setCookie("session_foo_path", null, "/foo", null, FOO_PATH);
+ yield setCookie("session_bar_path", null, "/bar", null, BAR_PATH);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'non_session_non_path_subdomain',
+ 'session_non_path_pub_domain',
+ 'session_foo_path',
+ 'session_bar_path'], BASE_URI);
+
+ // Ensure that cookies set for the / path appear more recent.
+ cs.getCookieString(OTHER_SUBDOMAIN_URI, null)
+ verifyCookies(['non_session_non_path_non_domain',
+ 'session_foo_path',
+ 'session_bar_path',
+ 'non_session_non_path_subdomain',
+ 'session_non_path_pub_domain'], BASE_URI);
+
+ // Evict oldest session cookie that does not match example.org/foo (session_bar_path)
+ yield setCookie("session_foo_path_2", null, "/foo", null, FOO_PATH);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'session_foo_path',
+ 'non_session_non_path_subdomain',
+ 'session_non_path_pub_domain',
+ 'session_foo_path_2'], BASE_URI);
+
+ // Evict oldest session cookie that does not match example.org/bar (session_foo_path)
+ yield setCookie("session_bar_path_2", null, "/bar", null, BAR_PATH);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'non_session_non_path_subdomain',
+ 'session_non_path_pub_domain',
+ 'session_foo_path_2',
+ 'session_bar_path_2'], BASE_URI);
+
+ // Evict oldest session cookie that does not match example.org/ (session_non_path_pub_domain)
+ yield setCookie("non_session_non_path_non_domain_2", null, null, 100000, BASE_URI);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'non_session_non_path_subdomain',
+ 'session_foo_path_2',
+ 'session_bar_path_2',
+ 'non_session_non_path_non_domain_2'], BASE_URI);
+
+ // Evict oldest session cookie that does not match example.org/ (session_foo_path_2)
+ yield setCookie("session_non_path_non_domain_3", null, null, null, BASE_URI);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'non_session_non_path_subdomain',
+ 'session_bar_path_2',
+ 'non_session_non_path_non_domain_2',
+ 'session_non_path_non_domain_3'], BASE_URI);
+
+ // Evict oldest session cookie; all such cookies match example.org/bar (session_bar_path_2)
+ // note: this new cookie doesn't have an explicit path, but empty paths inherit the
+ // request's path
+ yield setCookie("non_session_bar_path_non_domain", null, null, 100000, BAR_PATH);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'non_session_non_path_subdomain',
+ 'non_session_non_path_non_domain_2',
+ 'session_non_path_non_domain_3',
+ 'non_session_bar_path_non_domain'], BASE_URI);
+
+ // Evict oldest session cookie, even though it matches pub.example.org (session_non_path_non_domain_3)
+ yield setCookie("non_session_non_path_pub_domain", null, null, 100000, OTHER_SUBDOMAIN_URI);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'non_session_non_path_subdomain',
+ 'non_session_non_path_non_domain_2',
+ 'non_session_bar_path_non_domain',
+ 'non_session_non_path_pub_domain'], BASE_URI);
+
+ // All session cookies have been evicted.
+ // Evict oldest non-session non-domain-matching cookie (non_session_non_path_pub_domain)
+ yield setCookie("non_session_bar_path_non_domain_2", null, '/bar', 100000, BAR_PATH);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'non_session_non_path_subdomain',
+ 'non_session_non_path_non_domain_2',
+ 'non_session_bar_path_non_domain',
+ 'non_session_bar_path_non_domain_2'], BASE_URI);
+
+ // Evict oldest non-session non-path-matching cookie (non_session_bar_path_non_domain)
+ yield setCookie("non_session_non_path_non_domain_4", null, null, 100000, BASE_URI);
+ verifyCookies(['non_session_non_path_non_domain',
+ 'non_session_non_path_subdomain',
+ 'non_session_non_path_non_domain_2',
+ 'non_session_bar_path_non_domain_2',
+ 'non_session_non_path_non_domain_4'], BASE_URI);
+
+ // At this point all remaining cookies are non-session cookies, have a path of /,
+ // and either don't have a domain or have one that matches subdomains.
+ // They will therefore be evicted from oldest to newest if all new cookies added share
+ // similar characteristics.
+}
+
+// Verify that the given cookie names exist, and are ordered from least to most recently accessed
+function verifyCookies(names, uri) {
+ do_check_eq(cm.countCookiesFromHost(uri.host), names.length);
+ let cookies = cm.getCookiesFromHost(uri.host, {});
+ let actual_cookies = [];
+ while (cookies.hasMoreElements()) {
+ let cookie = cookies.getNext().QueryInterface(Ci.nsICookie2);
+ 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) {
+ do_print("unexpected cookies: " + left);
+ }
+ if (right.length) {
+ do_print("expected cookies: " + right);
+ }
+ }
+ do_check_eq(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++) {
+ do_check_eq(names[i], actual_cookies[i].name);
+ do_check_eq(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;
+ do_print(s);
+ cs.setCookieStringFromHttp(url, null, null, value, null, null);
+ 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_parser_0001.js b/netwerk/cookie/test/unit/test_parser_0001.js
new file mode 100644
index 0000000000..f885972a0a
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_parser_0001.js
@@ -0,0 +1,29 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function inChildProcess() {
+ return Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .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);
+
+ let cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ let uri = NetUtil.newURI("http://example.org/");
+
+ let set = "foo=bar";
+ cs.setCookieStringFromHttp(uri, null, null, set, null, null);
+
+ let expected = "foo=bar";
+ let actual = cs.getCookieStringFromHttp(uri, null, null);
+ do_check_eq(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..7811b01fe6
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_parser_0019.js
@@ -0,0 +1,29 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function inChildProcess() {
+ return Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .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);
+
+ let cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ let uri = NetUtil.newURI("http://example.org/");
+
+ let set = "foo=b;max-age=3600, c=d;path=/";
+ cs.setCookieStringFromHttp(uri, null, null, set, null, null);
+
+ let expected = "foo=b";
+ let actual = cs.getCookieStringFromHttp(uri, null, null);
+ do_check_eq(actual, expected);
+}
+
diff --git a/netwerk/cookie/test/unit/xpcshell.ini b/netwerk/cookie/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..f9c4093cf8
--- /dev/null
+++ b/netwerk/cookie/test/unit/xpcshell.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+head =
+tail =
+
+[test_bug643051.js]
+[test_bug1155169.js]
+[test_bug1267910.js]
+[test_parser_0001.js]
+[test_parser_0019.js]
+[test_eviction.js] \ No newline at end of file
diff --git a/netwerk/cookie/test/unit_ipc/test_ipc_parser_0001.js b/netwerk/cookie/test/unit_ipc/test_ipc_parser_0001.js
new file mode 100644
index 0000000000..988c8d1961
--- /dev/null
+++ b/netwerk/cookie/test/unit_ipc/test_ipc_parser_0001.js
@@ -0,0 +1,9 @@
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_parser_0001.js");
+}
diff --git a/netwerk/cookie/test/unit_ipc/test_ipc_parser_0019.js b/netwerk/cookie/test/unit_ipc/test_ipc_parser_0019.js
new file mode 100644
index 0000000000..535ac6e343
--- /dev/null
+++ b/netwerk/cookie/test/unit_ipc/test_ipc_parser_0019.js
@@ -0,0 +1,9 @@
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_parser_0019.js");
+}
diff --git a/netwerk/cookie/test/unit_ipc/xpcshell.ini b/netwerk/cookie/test/unit_ipc/xpcshell.ini
new file mode 100644
index 0000000000..9224434901
--- /dev/null
+++ b/netwerk/cookie/test/unit_ipc/xpcshell.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+head =
+tail =
+skip-if = toolkit == 'android'
+support-files =
+ !/netwerk/cookie/test/unit/test_parser_0001.js
+ !/netwerk/cookie/test/unit/test_parser_0019.js
+
+[test_ipc_parser_0001.js]
+[test_ipc_parser_0019.js]
diff --git a/netwerk/dns/ChildDNSService.cpp b/netwerk/dns/ChildDNSService.cpp
new file mode 100644
index 0000000000..a3a1f3347b
--- /dev/null
+++ b/netwerk/dns/ChildDNSService.cpp
@@ -0,0 +1,330 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIDNSListener.h"
+#include "nsIIOService.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsIXPConnect.h"
+#include "nsIPrefService.h"
+#include "nsIProtocolProxyService.h"
+#include "nsNetCID.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/DNSListenerProxy.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// ChildDNSService
+//-----------------------------------------------------------------------------
+
+static ChildDNSService *gChildDNSService;
+static const char kPrefNameDisablePrefetch[] = "network.dns.disablePrefetch";
+
+ChildDNSService* ChildDNSService::GetSingleton()
+{
+ MOZ_ASSERT(IsNeckoChild());
+
+ if (!gChildDNSService) {
+ gChildDNSService = new ChildDNSService();
+ }
+
+ NS_ADDREF(gChildDNSService);
+ return gChildDNSService;
+}
+
+NS_IMPL_ISUPPORTS(ChildDNSService,
+ nsIDNSService,
+ nsPIDNSService,
+ nsIObserver)
+
+ChildDNSService::ChildDNSService()
+ : mFirstTime(true)
+ , mDisablePrefetch(false)
+ , mPendingRequestsLock("DNSPendingRequestsLock")
+{
+ MOZ_ASSERT(IsNeckoChild());
+}
+
+ChildDNSService::~ChildDNSService()
+{
+
+}
+
+void
+ChildDNSService::GetDNSRecordHashKey(const nsACString &aHost,
+ uint32_t aFlags,
+ const nsACString &aNetworkInterface,
+ nsIDNSListener* aListener,
+ nsACString &aHashKey)
+{
+ aHashKey.Assign(aHost);
+ aHashKey.AppendInt(aFlags);
+ if (!aNetworkInterface.IsEmpty()) {
+ aHashKey.Append(aNetworkInterface);
+ }
+ aHashKey.AppendPrintf("%p", aListener);
+}
+
+//-----------------------------------------------------------------------------
+// ChildDNSService::nsIDNSService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolve(const nsACString &hostname,
+ uint32_t flags,
+ nsIDNSListener *listener,
+ nsIEventTarget *target_,
+ nsICancelable **result)
+{
+ return AsyncResolveExtended(hostname, flags, EmptyCString(), listener,
+ target_, result);
+}
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolveExtended(const nsACString &hostname,
+ uint32_t flags,
+ const nsACString &aNetworkInterface,
+ nsIDNSListener *listener,
+ nsIEventTarget *target_,
+ nsICancelable **result)
+{
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+
+ if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) {
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+ }
+
+ // We need original flags for the pending requests hash.
+ uint32_t originalFlags = flags;
+
+ // Support apps being 'offline' even if parent is not: avoids DNS traffic by
+ // apps that have been told they are offline.
+ if (GetOffline()) {
+ flags |= RESOLVE_OFFLINE;
+ }
+
+ // We need original listener for the pending requests hash.
+ nsIDNSListener *originalListener = listener;
+
+ // make sure JS callers get notification on the main thread
+ nsCOMPtr<nsIEventTarget> target = target_;
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
+ if (wrappedListener && !target) {
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ target = do_QueryInterface(mainThread);
+ }
+ 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<DNSRequestChild> childReq =
+ new DNSRequestChild(nsCString(hostname), flags,
+ nsCString(aNetworkInterface),
+ listener, target);
+
+ {
+ MutexAutoLock lock(mPendingRequestsLock);
+ nsCString key;
+ GetDNSRecordHashKey(hostname, originalFlags, aNetworkInterface,
+ originalListener, key);
+ nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
+ if (mPendingRequests.Get(key, &hashEntry)) {
+ hashEntry->AppendElement(childReq);
+ } else {
+ hashEntry = new nsTArray<RefPtr<DNSRequestChild>>();
+ hashEntry->AppendElement(childReq);
+ mPendingRequests.Put(key, hashEntry);
+ }
+ }
+
+ childReq->StartRequest();
+
+ childReq.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::CancelAsyncResolve(const nsACString &aHostname,
+ uint32_t aFlags,
+ nsIDNSListener *aListener,
+ nsresult aReason)
+{
+ return CancelAsyncResolveExtended(aHostname, aFlags, EmptyCString(),
+ aListener, aReason);
+}
+
+NS_IMETHODIMP
+ChildDNSService::CancelAsyncResolveExtended(const nsACString &aHostname,
+ uint32_t aFlags,
+ const nsACString &aNetworkInterface,
+ nsIDNSListener *aListener,
+ nsresult aReason)
+{
+ if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) {
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+ }
+
+ MutexAutoLock lock(mPendingRequestsLock);
+ nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
+ nsCString key;
+ GetDNSRecordHashKey(aHostname, aFlags, aNetworkInterface, aListener, key);
+ if (mPendingRequests.Get(key, &hashEntry)) {
+ // We cancel just one.
+ hashEntry->ElementAt(0)->Cancel(aReason);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::Resolve(const nsACString &hostname,
+ uint32_t flags,
+ 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::GetMyHostName(nsACString &result)
+{
+ // TODO: get value from parent during PNecko construction?
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+void
+ChildDNSService::NotifyRequestDone(DNSRequestChild *aDnsRequest)
+{
+ // We need the original flags and listener for the pending requests hash.
+ uint32_t originalFlags = aDnsRequest->mFlags & ~RESOLVE_OFFLINE;
+ nsCOMPtr<nsIDNSListener> originalListener = aDnsRequest->mListener;
+ nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(originalListener);
+ if (wrapper) {
+ wrapper->GetOriginalListener(getter_AddRefs(originalListener));
+ if (NS_WARN_IF(!originalListener)) {
+ MOZ_ASSERT(originalListener);
+ return;
+ }
+ }
+
+ MutexAutoLock lock(mPendingRequestsLock);
+
+ nsCString key;
+ GetDNSRecordHashKey(aDnsRequest->mHost, originalFlags,
+ aDnsRequest->mNetworkInterface, originalListener, key);
+
+ nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
+
+ if (mPendingRequests.Get(key, &hashEntry)) {
+ int idx;
+ if ((idx = hashEntry->IndexOf(aDnsRequest))) {
+ 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;
+ int proxyType = nsIProtocolProxyService::PROXYCONFIG_DIRECT;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->GetIntPref("network.proxy.type", &proxyType);
+ 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 ||
+ (proxyType == 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;
+}
+
+bool
+ChildDNSService::GetOffline() const
+{
+ bool offline = false;
+ nsCOMPtr<nsIIOService> io = do_GetService(NS_IOSERVICE_CONTRACTID);
+ if (io) {
+ io->GetOffline(&offline);
+ }
+ return offline;
+}
+
+//-----------------------------------------------------------------------------
+// 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..1662960bbe
--- /dev/null
+++ b/netwerk/dns/ChildDNSService.h
@@ -0,0 +1,60 @@
+/* -*- 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 "nsHashKeys.h"
+#include "nsClassHashtable.h"
+
+namespace mozilla {
+namespace net {
+
+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 ChildDNSService* GetSingleton();
+
+ void NotifyRequestDone(DNSRequestChild *aDnsRequest);
+
+ bool GetOffline() const;
+private:
+ virtual ~ChildDNSService();
+
+ void MOZ_ALWAYS_INLINE GetDNSRecordHashKey(const nsACString &aHost,
+ uint32_t aFlags,
+ const nsACString &aNetworkInterface,
+ nsIDNSListener* aListener,
+ nsACString &aHashKey);
+
+ bool mFirstTime;
+ bool mDisablePrefetch;
+
+ // We need to remember pending dns requests to be able to cancel them.
+ nsClassHashtable<nsCStringHashKey, nsTArray<RefPtr<DNSRequestChild>>> mPendingRequests;
+ Mutex mPendingRequestsLock;
+};
+
+} // 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..643296af00
--- /dev/null
+++ b/netwerk/dns/DNS.cpp
@@ -0,0 +1,368 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/Assertions.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/ArrayUtils.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 NetAddrToString(const NetAddr *addr, char *buf, uint32_t bufSize)
+{
+ 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);
+ }
+ else 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)
+ else 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 IsLoopBackAddress(const NetAddr *addr)
+{
+ if (addr->raw.family == AF_INET) {
+ return (addr->inet.ip == htonl(INADDR_LOOPBACK));
+ }
+ else if (addr->raw.family == AF_INET6) {
+ if (IPv6ADDR_IS_LOOPBACK(&addr->inet6.ip)) {
+ return true;
+ } else if (IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip) &&
+ IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip) == htonl(INADDR_LOOPBACK)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool IsIPAddrAny(const NetAddr *addr)
+{
+ if (addr->raw.family == AF_INET) {
+ if (addr->inet.ip == htonl(INADDR_ANY)) {
+ return true;
+ }
+ }
+ else if (addr->raw.family == AF_INET6) {
+ if (IPv6ADDR_IS_UNSPECIFIED(&addr->inet6.ip)) {
+ return true;
+ } else if (IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip) &&
+ IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip) == htonl(INADDR_ANY)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool IsIPAddrV4Mapped(const NetAddr *addr)
+{
+ if (addr->raw.family == AF_INET6) {
+ return IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip);
+ }
+ return false;
+}
+
+bool IsIPAddrLocal(const NetAddr *addr)
+{
+ MOZ_ASSERT(addr);
+
+ // IPv4 RFC1918 and Link Local Addresses.
+ if (addr->raw.family == AF_INET) {
+ uint32_t addr32 = ntohl(addr->inet.ip);
+ 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;
+ }
+ }
+ // IPv6 Unique and Link Local 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;
+ }
+ }
+ // Not an IPv4/6 local address.
+ return false;
+}
+
+nsresult
+GetPort(const NetAddr *aAddr, uint16_t *aResult)
+{
+ uint16_t port;
+ if (aAddr->raw.family == PR_AF_INET) {
+ port = aAddr->inet.port;
+ } else if (aAddr->raw.family == PR_AF_INET6) {
+ port = aAddr->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;
+ } else if (this->raw.family == AF_INET) {
+ return (this->inet.port == other.inet.port) &&
+ (this->inet.ip == other.inet.ip);
+ } else 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)
+ } else 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;
+ } else if (this->raw.family == AF_INET) {
+ if (this->inet.ip == other.inet.ip) {
+ return this->inet.port < other.inet.port;
+ } else {
+ return this->inet.ip < other.inet.ip;
+ }
+ } else if (this->raw.family == AF_INET6) {
+ int cmpResult = memcmp(&this->inet6.ip, &other.inet6.ip,
+ sizeof(this->inet6.ip));
+ if (cmpResult) {
+ return cmpResult < 0;
+ } else if (this->inet6.port != other.inet6.port) {
+ return this->inet6.port < other.inet6.port;
+ } else {
+ return this->inet6.flowinfo < other.inet6.flowinfo;
+ }
+ }
+ return false;
+}
+
+NetAddrElement::NetAddrElement(const PRNetAddr *prNetAddr)
+{
+ PRNetAddrToNetAddr(prNetAddr, &mAddress);
+}
+
+NetAddrElement::NetAddrElement(const NetAddrElement& netAddr)
+{
+ mAddress = netAddr.mAddress;
+}
+
+NetAddrElement::~NetAddrElement() = default;
+
+AddrInfo::AddrInfo(const char *host, const PRAddrInfo *prAddrInfo,
+ bool disableIPv4, bool filterNameCollision, const char *cname)
+ : mHostName(nullptr)
+ , mCanonicalName(nullptr)
+ , ttl(NO_TTL_DATA)
+{
+ MOZ_ASSERT(prAddrInfo, "Cannot construct AddrInfo with a null prAddrInfo pointer!");
+ const uint32_t nameCollisionAddr = htonl(0x7f003535); // 127.0.53.53
+
+ Init(host, cname);
+ 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) {
+ auto *addrElement = new NetAddrElement(&tmpAddr);
+ mAddresses.insertBack(addrElement);
+ }
+ } while (iter);
+}
+
+AddrInfo::AddrInfo(const char *host, const char *cname)
+ : mHostName(nullptr)
+ , mCanonicalName(nullptr)
+ , ttl(NO_TTL_DATA)
+{
+ Init(host, cname);
+}
+
+AddrInfo::~AddrInfo()
+{
+ NetAddrElement *addrElement;
+ while ((addrElement = mAddresses.popLast())) {
+ delete addrElement;
+ }
+ free(mHostName);
+ free(mCanonicalName);
+}
+
+void
+AddrInfo::Init(const char *host, const char *cname)
+{
+ MOZ_ASSERT(host, "Cannot initialize AddrInfo with a null host pointer!");
+
+ ttl = NO_TTL_DATA;
+ size_t hostlen = strlen(host);
+ mHostName = static_cast<char*>(moz_xmalloc(hostlen + 1));
+ memcpy(mHostName, host, hostlen + 1);
+ if (cname) {
+ size_t cnameLen = strlen(cname);
+ mCanonicalName = static_cast<char*>(moz_xmalloc(cnameLen + 1));
+ memcpy(mCanonicalName, cname, cnameLen + 1);
+ }
+ else {
+ mCanonicalName = nullptr;
+ }
+}
+
+void
+AddrInfo::AddAddress(NetAddrElement *address)
+{
+ MOZ_ASSERT(address, "Cannot add the address to an uninitialized list");
+
+ mAddresses.insertBack(address);
+}
+
+size_t
+AddrInfo::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
+{
+ size_t n = mallocSizeOf(this);
+ n += mallocSizeOf(mHostName);
+ n += mallocSizeOf(mCanonicalName);
+ n += mAddresses.sizeOfExcludingThis(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..22e1f31f12
--- /dev/null
+++ b/netwerk/dns/DNS.h
@@ -0,0 +1,183 @@
+/* -*- 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 "prio.h"
+#include "prnetdb.h"
+#include "plstr.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.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 {
+
+// 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;
+};
+
+// This class wraps a NetAddr union to provide C++ linked list
+// capabilities and other methods. It is created from a PRNetAddr,
+// which is converted to a mozilla::dns::NetAddr.
+class NetAddrElement : public LinkedListElement<NetAddrElement> {
+public:
+ explicit NetAddrElement(const PRNetAddr *prNetAddr);
+ NetAddrElement(const NetAddrElement& netAddr);
+ ~NetAddrElement();
+
+ NetAddr mAddress;
+};
+
+class AddrInfo {
+public:
+ // Creates an AddrInfo object. It calls the AddrInfo(const char*, const char*)
+ // to initialize the host and the cname.
+ AddrInfo(const char *host, const PRAddrInfo *prAddrInfo, bool disableIPv4,
+ bool filterNameCollision, const char *cname);
+
+ // Creates a basic AddrInfo object (initialize only the host and the cname).
+ AddrInfo(const char *host, const char *cname);
+ ~AddrInfo();
+
+ void AddAddress(NetAddrElement *address);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ char *mHostName;
+ char *mCanonicalName;
+ uint16_t ttl;
+ static const uint16_t NO_TTL_DATA = (uint16_t) -1;
+
+ LinkedList<NetAddrElement> mAddresses;
+
+private:
+ void Init(const char *host, const char *cname);
+};
+
+// 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 NetAddrToString(const NetAddr *addr, char *buf, uint32_t bufSize);
+
+bool IsLoopBackAddress(const NetAddr *addr);
+
+bool IsIPAddrAny(const NetAddr *addr);
+
+bool IsIPAddrV4Mapped(const NetAddr *addr);
+
+bool IsIPAddrLocal(const NetAddr *addr);
+
+nsresult GetPort(const NetAddr *aAddr, uint16_t *aResult);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DNS_h_
diff --git a/netwerk/dns/DNSListenerProxy.cpp b/netwerk/dns/DNSListenerProxy.cpp
new file mode 100644
index 0000000000..333f0946d3
--- /dev/null
+++ b/netwerk/dns/DNSListenerProxy.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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"
+#include "nsIEventTarget.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(DNSListenerProxy,
+ nsIDNSListener,
+ nsIDNSListenerProxy)
+
+NS_IMETHODIMP
+DNSListenerProxy::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord,
+ nsresult aStatus)
+{
+ RefPtr<OnLookupCompleteRunnable> r =
+ new OnLookupCompleteRunnable(mListener, aRequest, aRecord, aStatus);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+DNSListenerProxy::OnLookupCompleteRunnable::Run()
+{
+ mListener->OnLookupComplete(mRequest, mRecord, mStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DNSListenerProxy::GetOriginalListener(nsIDNSListener **aOriginalListener)
+{
+ NS_IF_ADDREF(*aOriginalListener = mListener);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSListenerProxy.h b/netwerk/dns/DNSListenerProxy.h
new file mode 100644
index 0000000000..307dde0f71
--- /dev/null
+++ b/netwerk/dns/DNSListenerProxy.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 {
+
+class DNSListenerProxy final
+ : public nsIDNSListener
+ , public nsIDNSListenerProxy
+{
+public:
+ DNSListenerProxy(nsIDNSListener* aListener, nsIEventTarget* aTargetThread)
+ // Sometimes aListener is a main-thread only object like XPCWrappedJS, and
+ // sometimes it's a threadsafe object like nsSOCKSSocketInfo. Use a main-
+ // thread pointer holder, but disable strict enforcement of thread invariants.
+ // The AddRef implementation of XPCWrappedJS will assert if we go wrong here.
+ : mListener(new nsMainThreadPtrHolder<nsIDNSListener>(aListener, false))
+ , mTargetThread(aTargetThread)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSIDNSLISTENERPROXY
+
+ class OnLookupCompleteRunnable : public Runnable
+ {
+ public:
+ OnLookupCompleteRunnable(const nsMainThreadPtrHandle<nsIDNSListener>& aListener,
+ nsICancelable* aRequest,
+ nsIDNSRecord* aRecord,
+ nsresult aStatus)
+ : mListener(aListener)
+ , mRequest(aRequest)
+ , mRecord(aRecord)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIDNSListener> mListener;
+ nsCOMPtr<nsICancelable> mRequest;
+ nsCOMPtr<nsIDNSRecord> mRecord;
+ nsresult mStatus;
+ };
+
+private:
+ ~DNSListenerProxy() {}
+
+ nsMainThreadPtrHandle<nsIDNSListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+};
+
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DNSListenerProxy_h__
diff --git a/netwerk/dns/DNSRequestChild.cpp b/netwerk/dns/DNSRequestChild.cpp
new file mode 100644
index 0000000000..84086202b5
--- /dev/null
+++ b/netwerk/dns/DNSRequestChild.cpp
@@ -0,0 +1,313 @@
+/* -*- 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/ChildDNSService.h"
+#include "mozilla/net/DNSRequestChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/Unused.h"
+#include "nsIDNSRecord.h"
+#include "nsHostResolver.h"
+#include "nsTArray.h"
+#include "nsNetAddr.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// ChildDNSRecord:
+// A simple class to provide nsIDNSRecord on the child
+//-----------------------------------------------------------------------------
+
+class ChildDNSRecord : public nsIDNSRecord
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRECORD
+
+ ChildDNSRecord(const DNSRecord& reply, uint16_t flags);
+
+private:
+ virtual ~ChildDNSRecord();
+
+ nsCString mCanonicalName;
+ nsTArray<NetAddr> mAddresses;
+ uint32_t mCurrent; // addr iterator
+ uint32_t mLength; // number of addrs
+ uint16_t mFlags;
+};
+
+NS_IMPL_ISUPPORTS(ChildDNSRecord, nsIDNSRecord)
+
+ChildDNSRecord::ChildDNSRecord(const DNSRecord& reply, uint16_t flags)
+ : mCurrent(0)
+ , mFlags(flags)
+{
+ mCanonicalName = reply.canonicalName();
+
+ // A shame IPDL gives us no way to grab ownership of array: so copy it.
+ const nsTArray<NetAddr>& addrs = reply.addrs();
+ uint32_t i = 0;
+ mLength = addrs.Length();
+ for (; i < mLength; i++) {
+ mAddresses.AppendElement(addrs[i]);
+ }
+}
+
+ChildDNSRecord::~ChildDNSRecord()
+{
+}
+
+//-----------------------------------------------------------------------------
+// ChildDNSRecord::nsIDNSRecord
+//-----------------------------------------------------------------------------
+
+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::GetNextAddr(uint16_t port, NetAddr *addr)
+{
+ if (mCurrent >= mLength) {
+ 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;
+ 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;
+
+ NS_ADDREF(*result = new nsNetAddr(&addr));
+
+ 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 (NetAddrToString(&addr, 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 < mLength;
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// CancelDNSRequestEvent
+//-----------------------------------------------------------------------------
+
+class CancelDNSRequestEvent : public Runnable
+{
+public:
+ CancelDNSRequestEvent(DNSRequestChild* aDnsReq, nsresult aReason)
+ : mDnsRequest(aDnsReq)
+ , mReasonForCancel(aReason)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ if (mDnsRequest->mIPCOpen) {
+ // Send request to Parent process.
+ mDnsRequest->SendCancelDNSRequest(mDnsRequest->mHost, mDnsRequest->mFlags,
+ mDnsRequest->mNetworkInterface,
+ mReasonForCancel);
+ }
+ return NS_OK;
+ }
+private:
+ RefPtr<DNSRequestChild> mDnsRequest;
+ nsresult mReasonForCancel;
+};
+
+//-----------------------------------------------------------------------------
+// DNSRequestChild
+//-----------------------------------------------------------------------------
+
+DNSRequestChild::DNSRequestChild(const nsCString& aHost,
+ const uint32_t& aFlags,
+ const nsCString& aNetworkInterface,
+ nsIDNSListener *aListener,
+ nsIEventTarget *target)
+ : mListener(aListener)
+ , mTarget(target)
+ , mResultStatus(NS_OK)
+ , mHost(aHost)
+ , mFlags(aFlags)
+ , mNetworkInterface(aNetworkInterface)
+ , mIPCOpen(false)
+{
+}
+
+void
+DNSRequestChild::StartRequest()
+{
+ // we can only do IPDL on the main thread
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod(this, &DNSRequestChild::StartRequest));
+ return;
+ }
+
+ // Send request to Parent process.
+ gNeckoChild->SendPDNSRequestConstructor(this, mHost, mFlags,
+ mNetworkInterface);
+ mIPCOpen = true;
+
+ // IPDL holds a reference until IPDL channel gets destroyed
+ AddIPDLReference();
+}
+
+void
+DNSRequestChild::CallOnLookupComplete()
+{
+ MOZ_ASSERT(mListener);
+ mListener->OnLookupComplete(this, mResultRecord, mResultStatus);
+}
+
+bool
+DNSRequestChild::RecvLookupCompleted(const DNSRequestResponse& reply)
+{
+ mIPCOpen = false;
+ 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;
+ }
+ default:
+ NS_NOTREACHED("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(this, &DNSRequestChild::CallOnLookupComplete);
+ mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+void
+DNSRequestChild::ReleaseIPDLReference()
+{
+ // Request is done or destroyed. Remove it from the hash table.
+ RefPtr<ChildDNSService> dnsServiceChild =
+ dont_AddRef(ChildDNSService::GetSingleton());
+ dnsServiceChild->NotifyRequestDone(this);
+
+ Release();
+}
+
+void
+DNSRequestChild::ActorDestroy(ActorDestroyReason why)
+{
+ mIPCOpen = false;
+}
+
+//-----------------------------------------------------------------------------
+// DNSRequestChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(DNSRequestChild,
+ nsICancelable)
+
+//-----------------------------------------------------------------------------
+// DNSRequestChild::nsICancelable
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+DNSRequestChild::Cancel(nsresult reason)
+{
+ if(mIPCOpen) {
+ // We can only do IPDL on the main thread
+ NS_DispatchToMainThread(
+ new CancelDNSRequestEvent(this, reason));
+ }
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSRequestChild.h b/netwerk/dns/DNSRequestChild.h
new file mode 100644
index 0000000000..26d4ff98d7
--- /dev/null
+++ b/netwerk/dns/DNSRequestChild.h
@@ -0,0 +1,61 @@
+/* -*- 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/PDNSRequestChild.h"
+#include "nsICancelable.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSListener.h"
+#include "nsIEventTarget.h"
+
+namespace mozilla {
+namespace net {
+
+class DNSRequestChild final
+ : public PDNSRequestChild
+ , public nsICancelable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ DNSRequestChild(const nsCString& aHost, const uint32_t& aFlags,
+ const nsCString& aNetworkInterface,
+ nsIDNSListener *aListener, nsIEventTarget *target);
+
+ void AddIPDLReference() {
+ AddRef();
+ }
+ void ReleaseIPDLReference();
+
+ // Sends IPDL request to parent
+ void StartRequest();
+ void CallOnLookupComplete();
+
+protected:
+ friend class CancelDNSRequestEvent;
+ friend class ChildDNSService;
+ virtual ~DNSRequestChild() {}
+
+ virtual bool RecvLookupCompleted(const DNSRequestResponse& reply) override;
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ nsCOMPtr<nsIDNSListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ nsCOMPtr<nsIDNSRecord> mResultRecord;
+ nsresult mResultStatus;
+ nsCString mHost;
+ uint16_t mFlags;
+ nsCString mNetworkInterface;
+ bool mIPCOpen;
+};
+
+} // 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..a5c95a8124
--- /dev/null
+++ b/netwerk/dns/DNSRequestParent.cpp
@@ -0,0 +1,132 @@
+/* -*- 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 "nsIDNSService.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "nsIServiceManager.h"
+#include "nsICancelable.h"
+#include "nsIDNSRecord.h"
+#include "nsHostResolver.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+DNSRequestParent::DNSRequestParent()
+ : mFlags(0)
+ , mIPCClosed(false)
+{
+
+}
+
+DNSRequestParent::~DNSRequestParent()
+{
+
+}
+
+void
+DNSRequestParent::DoAsyncResolve(const nsACString &hostname, uint32_t flags,
+ const nsACString &networkInterface)
+{
+ nsresult rv;
+ mFlags = flags;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ nsCOMPtr<nsICancelable> unused;
+ rv = dns->AsyncResolveExtended(hostname, flags, networkInterface, this,
+ mainThread, getter_AddRefs(unused));
+ }
+
+ if (NS_FAILED(rv) && !mIPCClosed) {
+ mIPCClosed = true;
+ Unused << SendLookupCompleted(DNSRequestResponse(rv));
+ }
+}
+
+bool
+DNSRequestParent::RecvCancelDNSRequest(const nsCString& hostName,
+ const uint32_t& flags,
+ const nsCString& networkInterface,
+ const nsresult& reason)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = dns->CancelAsyncResolveExtended(hostName, flags, networkInterface,
+ this, reason);
+ }
+ return true;
+}
+
+bool
+DNSRequestParent::Recv__delete__()
+{
+ mIPCClosed = true;
+ return true;
+}
+
+void
+DNSRequestParent::ActorDestroy(ActorDestroyReason why)
+{
+ // We may still have refcount>0 if DNS hasn't called our OnLookupComplete
+ // yet, but child process has crashed. We must not send any more msgs
+ // to child, or IPDL will kill chrome process, too.
+ mIPCClosed = true;
+}
+//-----------------------------------------------------------------------------
+// DNSRequestParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(DNSRequestParent,
+ nsIDNSListener)
+
+//-----------------------------------------------------------------------------
+// nsIDNSListener functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+DNSRequestParent::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ if (mIPCClosed) {
+ // nothing to do: child probably crashed
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ 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)
+ NetAddrArray array;
+ NetAddr addr;
+ while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) {
+ array.AppendElement(addr);
+ }
+
+ Unused << SendLookupCompleted(DNSRequestResponse(DNSRecord(cname, array)));
+ } else {
+ Unused << SendLookupCompleted(DNSRequestResponse(status));
+ }
+
+ mIPCClosed = true;
+ return NS_OK;
+}
+
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSRequestParent.h b/netwerk/dns/DNSRequestParent.h
new file mode 100644
index 0000000000..d217dbd4fe
--- /dev/null
+++ b/netwerk/dns/DNSRequestParent.h
@@ -0,0 +1,50 @@
+/* -*- 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/PDNSRequestParent.h"
+#include "nsIDNSService.h"
+#include "nsIDNSListener.h"
+
+namespace mozilla {
+namespace net {
+
+class DNSRequestParent
+ : public PDNSRequestParent
+ , public nsIDNSListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ DNSRequestParent();
+
+ void DoAsyncResolve(const nsACString &hostname, uint32_t flags,
+ const nsACString &networkInterface);
+
+ // Pass args here rather than storing them in the parent; they are only
+ // needed if the request is to be canceled.
+ bool RecvCancelDNSRequest(const nsCString& hostName,
+ const uint32_t& flags,
+ const nsCString& networkInterface,
+ const nsresult& reason) override;
+ bool Recv__delete__() override;
+
+protected:
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+private:
+ virtual ~DNSRequestParent();
+
+ uint32_t mFlags;
+ bool mIPCClosed; // true if IPDL channel has been closed (child crash)
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DNSRequestParent_h
diff --git a/netwerk/dns/GetAddrInfo.cpp b/netwerk/dns/GetAddrInfo.cpp
new file mode 100644
index 0000000000..3c177ec536
--- /dev/null
+++ b/netwerk/dns/GetAddrInfo.cpp
@@ -0,0 +1,369 @@
+/* -*- 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"
+#include "mozilla/net/DNS.h"
+#include "prnetdb.h"
+#include "nsHostResolver.h"
+#include "nsError.h"
+#include "mozilla/Mutex.h"
+#include "nsAutoPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "MainThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/net/DNS.h"
+#include <algorithm>
+#include "prerror.h"
+
+#include "mozilla/Logging.h"
+
+#if 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
+
+namespace mozilla {
+namespace net {
+
+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__))
+
+#if DNSQUERY_AVAILABLE
+////////////////////////////
+// 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_*");
+
+// We intentionally leak this mutex. This is because we can run into a
+// situation where the worker threads are still running until the process
+// is actually fully shut down, and at any time one of those worker
+// threads can access gDnsapiInfoLock.
+static OffTheBooksMutex* gDnsapiInfoLock = nullptr;
+
+struct DnsapiInfo
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DnsapiInfo);
+
+ HMODULE mLibrary;
+ decltype(&DnsQuery_A) mDnsQueryFunc;
+ decltype(&DnsFree) mDnsFreeFunc;
+
+private:
+ // This will either be called during shutdown of the GetAddrInfo module, or
+ // when a worker thread is done doing a lookup (ie: within
+ // _GetAddrInfo_Windows). Note that the lock must be held when this is
+ // called.
+ ~DnsapiInfo()
+ {
+ if (gDnsapiInfoLock) {
+ gDnsapiInfoLock->AssertCurrentThreadOwns();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("No mutex available during GetAddrInfo "
+ "shutdown.");
+ return;
+ }
+
+ LOG("Freeing Dnsapi.dll");
+ MOZ_ASSERT(mLibrary);
+ DebugOnly<BOOL> rv = FreeLibrary(mLibrary);
+ NS_WARNING_ASSERTION(rv, "Failed to free Dnsapi.dll.");
+ }
+};
+
+static StaticRefPtr<DnsapiInfo> gDnsapiInfo;
+
+static MOZ_ALWAYS_INLINE nsresult
+_GetAddrInfoInit_Windows()
+{
+ // This is necessary to ensure strict thread safety because if two threads
+ // run this function at the same time they can potentially create two
+ // mutexes.
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Do not initialize GetAddrInfo off main thread!");
+
+ if (!gDnsapiInfoLock) {
+ gDnsapiInfoLock = new OffTheBooksMutex("GetAddrInfo.cpp::gDnsapiInfoLock");
+ }
+ OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
+
+ if (gDnsapiInfo) {
+ MOZ_ASSERT_UNREACHABLE("GetAddrInfo is being initialized multiple times!");
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ HMODULE library = LoadLibraryA("Dnsapi.dll");
+ if (NS_WARN_IF(!library)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ FARPROC dnsQueryFunc = GetProcAddress(library, "DnsQuery_A");
+ FARPROC dnsFreeFunc = GetProcAddress(library, "DnsFree");
+ if (NS_WARN_IF(!dnsQueryFunc) || NS_WARN_IF(!dnsFreeFunc)) {
+ DebugOnly<BOOL> rv = FreeLibrary(library);
+ NS_WARNING_ASSERTION(rv, "Failed to free Dnsapi.dll.");
+ return NS_ERROR_FAILURE;
+ }
+
+ DnsapiInfo* info = new DnsapiInfo;
+ info->mLibrary = library;
+ info->mDnsQueryFunc = (decltype(info->mDnsQueryFunc)) dnsQueryFunc;
+ info->mDnsFreeFunc = (decltype(info->mDnsFreeFunc)) dnsFreeFunc;
+ gDnsapiInfo = info;
+
+ return NS_OK;
+}
+
+static MOZ_ALWAYS_INLINE nsresult
+_GetAddrInfoShutdown_Windows()
+{
+ OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
+
+ if (NS_WARN_IF(!gDnsapiInfo) || NS_WARN_IF(!gDnsapiInfoLock)) {
+ MOZ_ASSERT_UNREACHABLE("GetAddrInfo not initialized!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ gDnsapiInfo = nullptr;
+
+ return NS_OK;
+}
+
+// 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 dnsapi->mDnsQueryFunc and iterating through the returned
+// records to find the one with the smallest TTL value.
+static MOZ_ALWAYS_INLINE nsresult
+_GetMinTTLForRequestType_Windows(DnsapiInfo * dnsapi, const char* aHost,
+ uint16_t aRequestType, unsigned int* aResult)
+{
+ MOZ_ASSERT(dnsapi);
+ MOZ_ASSERT(aHost);
+ MOZ_ASSERT(aResult);
+
+ PDNS_RECORDA dnsData = nullptr;
+ DNS_STATUS status = dnsapi->mDnsQueryFunc(
+ aHost,
+ aRequestType,
+ (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),
+ 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. aRequestType = %X\n",
+ aHost, status, aRequestType);
+ 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 == aRequestType) {
+ *aResult = std::min<unsigned int>(*aResult, curRecord->dwTtl);
+ } else {
+ LOG("Received unexpected record type %u in response for %s.\n",
+ curRecord->wType, aHost);
+ }
+ }
+
+ dnsapi->mDnsFreeFunc(dnsData, DNS_FREE_TYPE::DnsFreeRecordList);
+ return NS_OK;
+}
+
+static MOZ_ALWAYS_INLINE nsresult
+_GetTTLData_Windows(const char* aHost, uint16_t* aResult, uint16_t aAddressFamily)
+{
+ MOZ_ASSERT(aHost);
+ MOZ_ASSERT(aResult);
+ if (aAddressFamily != PR_AF_UNSPEC &&
+ aAddressFamily != PR_AF_INET &&
+ aAddressFamily != PR_AF_INET6) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<DnsapiInfo> dnsapi = nullptr;
+ {
+ OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
+ dnsapi = gDnsapiInfo;
+ }
+
+ if (!dnsapi) {
+ LOG_WARNING("GetAddrInfo has been shutdown or has not been initialized.");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // 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.
+ unsigned int ttl = -1;
+ if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET) {
+ _GetMinTTLForRequestType_Windows(dnsapi, aHost, DNS_TYPE_A, &ttl);
+ }
+ if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET6) {
+ _GetMinTTLForRequestType_Windows(dnsapi, aHost, DNS_TYPE_AAAA, &ttl);
+ }
+
+ {
+ // dnsapi's destructor is not thread-safe, so we release explicitly here
+ OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
+ dnsapi = nullptr;
+ }
+
+ if (ttl == -1) {
+ LOG("No useable TTL found.");
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = ttl;
+ return NS_OK;
+}
+#endif
+
+////////////////////////////////////
+// PORTABLE RUNTIME IMPLEMENTATION//
+////////////////////////////////////
+
+static MOZ_ALWAYS_INLINE nsresult
+_GetAddrInfo_Portable(const char* aCanonHost, uint16_t aAddressFamily,
+ uint16_t aFlags, const char* aNetworkInterface,
+ AddrInfo** aAddrInfo)
+{
+ MOZ_ASSERT(aCanonHost);
+ 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;
+ }
+
+ PRAddrInfo* prai = PR_GetAddrInfoByName(aCanonHost, aAddressFamily, prFlags);
+
+ if (!prai) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ const char* canonName = nullptr;
+ if (aFlags & nsHostResolver::RES_CANON_NAME) {
+ canonName = PR_GetCanonNameFromAddrInfo(prai);
+ }
+
+ bool filterNameCollision = !(aFlags & nsHostResolver::RES_ALLOW_NAME_COLLISION);
+ nsAutoPtr<AddrInfo> ai(new AddrInfo(aCanonHost, prai, disableIPv4,
+ filterNameCollision, canonName));
+ PR_FreeAddrInfo(prai);
+ if (ai->mAddresses.isEmpty()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ *aAddrInfo = ai.forget();
+
+ return NS_OK;
+}
+
+//////////////////////////////////////
+// COMMON/PLATFORM INDEPENDENT CODE //
+//////////////////////////////////////
+nsresult
+GetAddrInfoInit() {
+ LOG("Initializing GetAddrInfo.\n");
+
+#if DNSQUERY_AVAILABLE
+ return _GetAddrInfoInit_Windows();
+#else
+ return NS_OK;
+#endif
+}
+
+nsresult
+GetAddrInfoShutdown() {
+ LOG("Shutting down GetAddrInfo.\n");
+
+#if DNSQUERY_AVAILABLE
+ return _GetAddrInfoShutdown_Windows();
+#else
+ return NS_OK;
+#endif
+}
+
+nsresult
+GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags,
+ const char* aNetworkInterface, AddrInfo** aAddrInfo, bool aGetTtl)
+{
+ if (NS_WARN_IF(!aHost) || NS_WARN_IF(!aAddrInfo)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+#if DNSQUERY_AVAILABLE
+ // The GetTTLData needs the canonical name to function properly
+ if (aGetTtl) {
+ aFlags |= nsHostResolver::RES_CANON_NAME;
+ }
+#endif
+
+ *aAddrInfo = nullptr;
+ nsresult rv = _GetAddrInfo_Portable(aHost, aAddressFamily, aFlags,
+ aNetworkInterface, aAddrInfo);
+
+#if DNSQUERY_AVAILABLE
+ if (aGetTtl && NS_SUCCEEDED(rv)) {
+ // Figure out the canonical name, or if that fails, just use the host name
+ // we have.
+ const char *name = nullptr;
+ if (*aAddrInfo != nullptr && (*aAddrInfo)->mCanonicalName) {
+ name = (*aAddrInfo)->mCanonicalName;
+ } else {
+ name = aHost;
+ }
+
+ LOG("Getting TTL for %s (cname = %s).", aHost, name);
+ uint16_t ttl = 0;
+ nsresult ttlRv = _GetTTLData_Windows(name, &ttl, aAddressFamily);
+ if (NS_SUCCEEDED(ttlRv)) {
+ (*aAddrInfo)->ttl = ttl;
+ LOG("Got TTL %u for %s (name = %s).", ttl, aHost, name);
+ } else {
+ LOG_WARNING("Could not get TTL for %s (cname = %s).", aHost, name);
+ }
+ }
+#endif
+
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/GetAddrInfo.h b/netwerk/dns/GetAddrInfo.h
new file mode 100644
index 0000000000..57c008dd50
--- /dev/null
+++ b/netwerk/dns/GetAddrInfo.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef netwerk_dns_GetAddrInfo_h
+#define netwerk_dns_GetAddrInfo_h
+
+#include "nsError.h"
+#include "nscore.h"
+
+#if defined(XP_WIN)
+#define DNSQUERY_AVAILABLE 1
+#define TTL_AVAILABLE 1
+#else
+#define DNSQUERY_AVAILABLE 0
+#define TTL_AVAILABLE 0
+#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, and TTL_AVAILABLE is truthy, the TTL will be
+ * retrieved if DNS provides the answers..
+ */
+nsresult
+GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags,
+ const char* aNetworkInterface, 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();
+
+} // namespace net
+} // namespace mozilla
+
+#endif // netwerk_dns_GetAddrInfo_h
diff --git a/netwerk/dns/PDNSParams.h b/netwerk/dns/PDNSParams.h
new file mode 100644
index 0000000000..e5e668164c
--- /dev/null
+++ b/netwerk/dns/PDNSParams.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 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/. */
+
+#ifndef PDNSParams_h
+#define PDNSParams_h
+
+#include "DNS.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+// Need to define typedef in .h file--can't seem to in ipdl.h file?
+typedef nsTArray<NetAddr> NetAddrArray;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // PDNSParams_h
diff --git a/netwerk/dns/PDNSRequest.ipdl b/netwerk/dns/PDNSRequest.ipdl
new file mode 100644
index 0000000000..2288ab09e5
--- /dev/null
+++ b/netwerk/dns/PDNSRequest.ipdl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 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 PDNSRequestParams;
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+namespace mozilla {
+namespace net {
+
+async protocol PDNSRequest
+{
+ manager PNecko;
+
+parent:
+ // 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, uint32_t flags,
+ nsCString networkInterface, nsresult reason);
+ async __delete__();
+
+child:
+ 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..d06f27cfa2
--- /dev/null
+++ b/netwerk/dns/PDNSRequestParams.ipdlh
@@ -0,0 +1,31 @@
+/* -*- 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 NetAddrArray from "mozilla/net/PDNSParams.h";
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// DNS IPDL structs
+//-----------------------------------------------------------------------------
+
+struct DNSRecord
+{
+ nsCString canonicalName;
+ NetAddrArray addrs;
+};
+
+union DNSRequestResponse
+{
+ DNSRecord;
+ nsresult; // if error
+};
+
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/netwerk/dns/effective_tld_names.dat b/netwerk/dns/effective_tld_names.dat
new file mode 100644
index 0000000000..24165a090c
--- /dev/null
+++ b/netwerk/dns/effective_tld_names.dat
@@ -0,0 +1,10834 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// ===BEGIN ICANN DOMAINS===
+
+// ac : http://en.wikipedia.org/wiki/.ac
+ac
+com.ac
+edu.ac
+gov.ac
+net.ac
+mil.ac
+org.ac
+
+// ad : http://en.wikipedia.org/wiki/.ad
+ad
+nom.ad
+
+// ae : http://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 http://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
+freight.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
+marketplace.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
+taxi.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 : http://en.wikipedia.org/wiki/.am
+am
+
+// an : http://www.una.an/an_domreg/default.asp
+an
+com.an
+net.an
+org.an
+edu.an
+
+// ao : http://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 : http://en.wikipedia.org/wiki/.aq
+aq
+
+// ar : https://nic.ar/normativa-vigente.xhtml
+ar
+com.ar
+edu.ar
+gob.ar
+gov.ar
+int.ar
+mil.ar
+net.ar
+org.ar
+tur.ar
+
+// arpa : http://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 : http://en.wikipedia.org/wiki/.as
+as
+gov.as
+
+// asia : http://en.wikipedia.org/wiki/.asia
+asia
+
+// at : http://en.wikipedia.org/wiki/.at
+// Confirmed by registry <it@nic.at> 2008-06-17
+at
+ac.at
+co.at
+gv.at
+or.at
+
+// au : http://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
+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
+
+// aw : http://en.wikipedia.org/wiki/.aw
+aw
+com.aw
+
+// ax : http://en.wikipedia.org/wiki/.ax
+ax
+
+// az : http://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://en.wikipedia.org/wiki/.ba
+ba
+org.ba
+net.ba
+edu.ba
+gov.ba
+mil.ba
+unsa.ba
+unbi.ba
+co.ba
+com.ba
+rs.ba
+
+// bb : http://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 : http://en.wikipedia.org/wiki/.bd
+*.bd
+
+// be : http://en.wikipedia.org/wiki/.be
+// Confirmed by registry <tech@dns.be> 2008-06-08
+be
+ac.be
+
+// bf : http://en.wikipedia.org/wiki/.bf
+bf
+gov.bf
+
+// bg : http://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 : http://en.wikipedia.org/wiki/.bh
+bh
+com.bh
+edu.bh
+net.bh
+org.bh
+gov.bh
+
+// bi : http://en.wikipedia.org/wiki/.bi
+// http://whois.nic.bi/
+bi
+co.bi
+com.bi
+edu.bi
+or.bi
+org.bi
+
+// biz : http://en.wikipedia.org/wiki/.biz
+biz
+
+// bj : http://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://en.wikipedia.org/wiki/.bn
+*.bn
+
+// bo : http://www.nic.bo/
+bo
+com.bo
+edu.bo
+gov.bo
+gob.bo
+int.bo
+org.bo
+net.bo
+mil.bo
+tv.bo
+
+// br : http://registro.br/dominio/categoria.html
+// Submitted by registry <fneves@registro.br> 2014-08-11
+br
+adm.br
+adv.br
+agr.br
+am.br
+arq.br
+art.br
+ato.br
+b.br
+bio.br
+blog.br
+bmd.br
+cim.br
+cng.br
+cnt.br
+com.br
+coop.br
+ecn.br
+eco.br
+edu.br
+emp.br
+eng.br
+esp.br
+etc.br
+eti.br
+far.br
+flog.br
+fm.br
+fnd.br
+fot.br
+fst.br
+g12.br
+ggf.br
+gov.br
+imb.br
+ind.br
+inf.br
+jor.br
+jus.br
+leg.br
+lel.br
+mat.br
+med.br
+mil.br
+mp.br
+mus.br
+net.br
+*.nom.br
+not.br
+ntr.br
+odo.br
+org.br
+ppg.br
+pro.br
+psc.br
+psi.br
+qsl.br
+radio.br
+rec.br
+slg.br
+srv.br
+taxi.br
+teo.br
+tmp.br
+trd.br
+tur.br
+tv.br
+vet.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 : http://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> 2006-06-16
+bv
+
+// bw : http://en.wikipedia.org/wiki/.bw
+// http://www.gobin.info/domainname/bw.doc
+// list of other 2nd level tlds ?
+bw
+co.bw
+org.bw
+
+// by : http://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 : http://en.wikipedia.org/wiki/.bz
+// http://www.belizenic.bz/
+bz
+com.bz
+net.bz
+org.bz
+edu.bz
+gov.bz
+
+// ca : http://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: http://en.wikipedia.org/wiki/.gc.ca
+// see also: http://registry.gc.ca/en/SubdomainFAQ
+gc.ca
+
+// cat : http://en.wikipedia.org/wiki/.cat
+cat
+
+// cc : http://en.wikipedia.org/wiki/.cc
+cc
+
+// cd : http://en.wikipedia.org/wiki/.cd
+// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1
+cd
+gov.cd
+
+// cf : http://en.wikipedia.org/wiki/.cf
+cf
+
+// cg : http://en.wikipedia.org/wiki/.cg
+cg
+
+// ch : http://en.wikipedia.org/wiki/.ch
+ch
+
+// ci : http://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 : http://en.wikipedia.org/wiki/.ck
+*.ck
+!www.ck
+
+// cl : http://en.wikipedia.org/wiki/.cl
+cl
+gov.cl
+gob.cl
+co.cl
+mil.cl
+
+// cm : http://en.wikipedia.org/wiki/.cm plus bug 981927
+cm
+co.cm
+com.cm
+gov.cm
+net.cm
+
+// cn : http://en.wikipedia.org/wiki/.cn
+// Submitted by registry <tanyaling@cnnic.cn> 2008-06-11
+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 : http://en.wikipedia.org/wiki/.co
+// Submitted by registry <tecnico@uniandes.edu.co> 2008-06-11
+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 : http://en.wikipedia.org/wiki/.com
+com
+
+// coop : http://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 : http://en.wikipedia.org/wiki/.cu
+cu
+com.cu
+edu.cu
+org.cu
+net.cu
+gov.cu
+inf.cu
+
+// cv : http://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 : http://en.wikipedia.org/wiki/.cx
+// list of other 2nd level tlds ?
+cx
+gov.cx
+
+// cy : http://en.wikipedia.org/wiki/.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 : http://en.wikipedia.org/wiki/.cz
+cz
+
+// de : http://en.wikipedia.org/wiki/.de
+// Confirmed by registry <ops@denic.de> (with technical
+// reservations) 2008-07-01
+de
+
+// dj : http://en.wikipedia.org/wiki/.dj
+dj
+
+// dk : http://en.wikipedia.org/wiki/.dk
+// Confirmed by registry <robert@dk-hostmaster.dk> 2008-06-17
+dk
+
+// dm : http://en.wikipedia.org/wiki/.dm
+dm
+com.dm
+net.dm
+org.dm
+edu.dm
+gov.dm
+
+// do : http://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://en.wikipedia.org/wiki/.dz
+dz
+com.dz
+org.dz
+net.dz
+gov.dz
+edu.dz
+asso.dz
+pol.dz
+art.dz
+
+// ec : http://www.nic.ec/reg/paso1.asp
+// Submitted by registry <vabboud@nic.ec> 2008-07-04
+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 : http://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 : http://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 : http://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 : http://en.wikipedia.org/wiki/.et
+et
+com.et
+gov.et
+org.et
+edu.et
+biz.et
+name.et
+info.et
+net.et
+
+// eu : http://en.wikipedia.org/wiki/.eu
+eu
+
+// fi : http://en.wikipedia.org/wiki/.fi
+fi
+// aland.fi : http://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://en.wikipedia.org/wiki/.fj
+*.fj
+
+// fk : http://en.wikipedia.org/wiki/.fk
+*.fk
+
+// fm : http://en.wikipedia.org/wiki/.fm
+fm
+
+// fo : http://en.wikipedia.org/wiki/.fo
+fo
+
+// fr : http://www.afnic.fr/
+// domaines descriptifs : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-descriptifs
+fr
+com.fr
+asso.fr
+nom.fr
+prd.fr
+presse.fr
+tm.fr
+// domaines sectoriels : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-sectoriels
+aeroport.fr
+assedic.fr
+avocat.fr
+avoues.fr
+cci.fr
+chambagri.fr
+chirurgiens-dentistes.fr
+experts-comptables.fr
+geometre-expert.fr
+gouv.fr
+greta.fr
+huissier-justice.fr
+medecin.fr
+notaires.fr
+pharmacien.fr
+port.fr
+veterinaire.fr
+
+// ga : http://en.wikipedia.org/wiki/.ga
+ga
+
+// gb : This registry is effectively dormant
+// Submitted by registry <Damien.Shaw@ja.net> 2008-06-12
+gb
+
+// gd : http://en.wikipedia.org/wiki/.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 : http://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 : http://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 : http://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> 2008-06-17
+gn
+ac.gn
+com.gn
+edu.gn
+gov.gn
+org.gn
+net.gn
+
+// gov : http://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 : http://en.wikipedia.org/wiki/.gq
+gq
+
+// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html
+// Submitted by registry <segred@ics.forth.gr> 2008-06-09
+gr
+com.gr
+edu.gr
+net.gr
+org.gr
+gov.gr
+
+// gs : http://en.wikipedia.org/wiki/.gs
+gs
+
+// gt : http://www.gt/politicas_de_registro.html
+gt
+com.gt
+edu.gt
+gob.gt
+ind.gt
+mil.gt
+net.gt
+org.gt
+
+// gu : http://gadao.gov.gu/registration.txt
+*.gu
+
+// gw : http://en.wikipedia.org/wiki/.gw
+gw
+
+// gy : http://en.wikipedia.org/wiki/.gy
+// http://registry.gy/
+gy
+co.gy
+com.gy
+net.gy
+
+// hk : https://www.hkdnr.hk
+// Submitted by registry <hk.tech@hkirc.hk> 2008-06-11
+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 : http://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://register.pandi.or.id/
+id
+ac.id
+biz.id
+co.id
+desa.id
+go.id
+mil.id
+my.id
+net.id
+or.id
+sch.id
+web.id
+
+// ie : http://en.wikipedia.org/wiki/.ie
+ie
+gov.ie
+
+// il : http://en.wikipedia.org/wiki/.il
+*.il
+
+// im : https://www.nic.im/
+// Submitted by registry <info@nic.im> 2013-11-15
+im
+ac.im
+co.im
+com.im
+ltd.co.im
+net.im
+org.im
+plc.co.im
+tt.im
+tv.im
+
+// in : http://en.wikipedia.org/wiki/.in
+// see also: https://registry.in/Policies
+// Please note, that nic.in is not an offical 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 : http://en.wikipedia.org/wiki/.info
+info
+
+// int : http://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 : http://en.wikipedia.org/wiki/.it
+it
+gov.it
+edu.it
+// Reserved geo-names:
+// http://www.nic.it/documenti/regolamenti-e-linee-guida/regolamento-assegnazione-versione-6.0.pdf
+// There is also a list of reserved geo-names corresponding to Italian municipalities
+// http://www.nic.it/documenti/appendice-c.pdf, but it is not included here.
+// 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
+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-sudtirol.it
+trentino-sued-tirol.it
+trentino-suedtirol.it
+trentinoa-adige.it
+trentinoaadige.it
+trentinoalto-adige.it
+trentinoaltoadige.it
+trentinos-tirol.it
+trentinostirol.it
+trentinosud-tirol.it
+trentinosudtirol.it
+trentinosued-tirol.it
+trentinosuedtirol.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
+valleeaoste.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.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.it
+bozen.it
+br.it
+brescia.it
+brindisi.it
+bs.it
+bt.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
+cesenaforli.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
+forlicesena.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
+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
+trentino.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 : http://en.wikipedia.org/wiki/.jobs
+jobs
+
+// jp : http://en.wikipedia.org/wiki/.jp
+// http://jprs.co.jp/en/jpdomain.html
+// Submitted by registry <info@jprs.jp> 2014-10-30
+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
+hitoyoshi.kumamoto.jp
+kamiamakusa.kumamoto.jp
+kashima.kumamoto.jp
+kikuchi.kumamoto.jp
+kosa.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
+kesennuma.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?option=com_content&task=view&id=117&Itemid=145
+*.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 : http://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
+// http://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 : http://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 : http://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 : http://en.wikipedia.org/wiki/.kw
+*.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 : http://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 : http://en.wikipedia.org/wiki/.la
+// Submitted by registry <gavin.brown@nic.la> 2008-06-10
+la
+int.la
+net.la
+info.la
+edu.la
+gov.la
+per.la
+com.la
+org.la
+
+// lb : http://en.wikipedia.org/wiki/.lb
+// Submitted by registry <randy@psg.com> 2008-06-17
+lb
+com.lb
+edu.lb
+gov.lb
+net.lb
+org.lb
+
+// lc : http://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 : http://en.wikipedia.org/wiki/.li
+li
+
+// lk : http://www.nic.lk/seclevpr.html
+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> 2008-06-17
+lr
+com.lr
+edu.lr
+gov.lr
+org.lr
+net.lr
+
+// ls : http://en.wikipedia.org/wiki/.ls
+ls
+co.ls
+org.ls
+
+// lt : http://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 : http://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 : http://en.wikipedia.org/wiki/.md
+md
+
+// me : http://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 : http://en.wikipedia.org/wiki/.mh
+mh
+
+// mil : http://en.wikipedia.org/wiki/.mil
+mil
+
+// mk : http://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: http://en.wikipedia.org/wiki/.ml
+ml
+com.ml
+edu.ml
+gouv.ml
+gov.ml
+net.ml
+org.ml
+presse.ml
+
+// mm : http://en.wikipedia.org/wiki/.mm
+*.mm
+
+// mn : http://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 : http://en.wikipedia.org/wiki/.mobi
+mobi
+
+// mp : http://www.dot.mp/
+// Confirmed by registry <dcamacho@saipan.com> 2008-06-17
+mp
+
+// mq : http://en.wikipedia.org/wiki/.mq
+mq
+
+// mr : http://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> 2013-11-19
+mt
+com.mt
+edu.mt
+net.mt
+org.mt
+
+// mu : http://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
+nuernberg.museum
+nuremberg.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 : http://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> 2008-06-19
+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.gobin.info/domainname/mz-template.doc
+*.mz
+!teledata.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
+
+// ne : http://en.wikipedia.org/wiki/.ne
+ne
+
+// net : http://en.wikipedia.org/wiki/.net
+net
+
+// nf : http://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://psg.com/dns/ng/
+ng
+com.ng
+edu.ng
+name.ng
+net.ng
+org.ng
+sch.ng
+gov.ng
+mil.ng
+mobi.ng
+
+// ni : http://www.nic.ni/dominios.htm
+*.ni
+
+// nl : http://en.wikipedia.org/wiki/.nl
+// https://www.sidn.nl/
+// ccTLD for the Netherlands
+nl
+
+// BV.nl will be a registry for dutch BV's (besloten vennootschap)
+bv.nl
+
+// no : http://www.norid.no/regelverk/index.en.html
+// The Norwegian registry has declined to notify us of updates. The web pages
+// referenced below are the official source of the data. There is also an
+// announce mailing list:
+// https://postlister.uninett.no/sympa/info/norid-diskusjon
+no
+// Norid generic domains : http://www.norid.no/regelverk/vedlegg-c.en.html
+fhs.no
+vgs.no
+fylkesbibl.no
+folkebibl.no
+museum.no
+idrett.no
+priv.no
+// Non-Norid generic domains : http://www.norid.no/regelverk/vedlegg-d.en.html
+mil.no
+stat.no
+dep.no
+kommune.no
+herad.no
+// no geographical names : http://www.norid.no/regelverk/vedlegg-b.en.html
+// 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
+// Confirmed by registry <technician@cenpac.net.nr> 2008-06-17
+nr
+biz.nr
+info.nr
+gov.nr
+edu.nr
+org.nr
+net.nr
+com.nr
+
+// nu : http://en.wikipedia.org/wiki/.nu
+nu
+
+// nz : http://en.wikipedia.org/wiki/.nz
+// Confirmed by registry <jay@nzrs.net.nz> 2014-05-19
+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 : http://en.wikipedia.org/wiki/.om
+om
+co.om
+com.om
+edu.om
+gov.om
+med.om
+museum.om
+net.om
+org.om
+pro.om
+
+// org : http://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 : http://en.wikipedia.org/wiki/.pg
+*.pg
+
+// ph : http://www.domains.ph/FAQ2.asp
+// Submitted by registry <jed@email.com.ph> 2008-06-13
+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
+// updated by .PL registry on 2015-04-28
+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 : http://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 http://en.wikipedia.org/wiki/.pr
+est.pr
+prof.pr
+ac.pr
+
+// pro : http://www.nic.pro/support_faq.htm
+pro
+aca.pro
+bar.pro
+cpa.pro
+jur.pro
+law.pro
+med.pro
+eng.pro
+
+// ps : http://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 : http://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
+// Confirmed by registry 2012-10-03
+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
+com.re
+asso.re
+nom.re
+
+// ro : http://www.rotld.ro/
+ro
+com.ro
+org.ro
+tm.ro
+nt.ro
+nom.ro
+info.ro
+rec.ro
+arts.ro
+firm.ro
+store.ro
+www.ro
+
+// rs : http://en.wikipedia.org/wiki/.rs
+rs
+co.rs
+org.rs
+edu.rs
+ac.rs
+gov.rs
+in.rs
+
+// ru : http://www.cctld.ru/ru/docs/aktiv_8.php
+// Industry domains
+ru
+ac.ru
+com.ru
+edu.ru
+int.ru
+net.ru
+org.ru
+pp.ru
+// Geographical domains
+adygeya.ru
+altai.ru
+amur.ru
+arkhangelsk.ru
+astrakhan.ru
+bashkiria.ru
+belgorod.ru
+bir.ru
+bryansk.ru
+buryatia.ru
+cbg.ru
+chel.ru
+chelyabinsk.ru
+chita.ru
+chukotka.ru
+chuvashia.ru
+dagestan.ru
+dudinka.ru
+e-burg.ru
+grozny.ru
+irkutsk.ru
+ivanovo.ru
+izhevsk.ru
+jar.ru
+joshkar-ola.ru
+kalmykia.ru
+kaluga.ru
+kamchatka.ru
+karelia.ru
+kazan.ru
+kchr.ru
+kemerovo.ru
+khabarovsk.ru
+khakassia.ru
+khv.ru
+kirov.ru
+koenig.ru
+komi.ru
+kostroma.ru
+krasnoyarsk.ru
+kuban.ru
+kurgan.ru
+kursk.ru
+lipetsk.ru
+magadan.ru
+mari.ru
+mari-el.ru
+marine.ru
+mordovia.ru
+// mosreg.ru Bug 1090800 - removed at request of Aleksey Konstantinov <konstantinovav@mosreg.ru>
+msk.ru
+murmansk.ru
+nalchik.ru
+nnov.ru
+nov.ru
+novosibirsk.ru
+nsk.ru
+omsk.ru
+orenburg.ru
+oryol.ru
+palana.ru
+penza.ru
+perm.ru
+ptz.ru
+rnd.ru
+ryazan.ru
+sakhalin.ru
+samara.ru
+saratov.ru
+simbirsk.ru
+smolensk.ru
+spb.ru
+stavropol.ru
+stv.ru
+surgut.ru
+tambov.ru
+tatarstan.ru
+tom.ru
+tomsk.ru
+tsaritsyn.ru
+tsk.ru
+tula.ru
+tuva.ru
+tver.ru
+tyumen.ru
+udm.ru
+udmurtia.ru
+ulan-ude.ru
+vladikavkaz.ru
+vladimir.ru
+vladivostok.ru
+volgograd.ru
+vologda.ru
+voronezh.ru
+vrn.ru
+vyatka.ru
+yakutia.ru
+yamal.ru
+yaroslavl.ru
+yekaterinburg.ru
+yuzhno-sakhalinsk.ru
+// More geographical domains
+amursk.ru
+baikal.ru
+cmw.ru
+fareast.ru
+jamal.ru
+kms.ru
+k-uralsk.ru
+kustanai.ru
+kuzbass.ru
+magnitka.ru
+mytis.ru
+nakhodka.ru
+nkz.ru
+norilsk.ru
+oskol.ru
+pyatigorsk.ru
+rubtsovsk.ru
+snz.ru
+syzran.ru
+vdonsk.ru
+zgrad.ru
+// State domains
+gov.ru
+mil.ru
+// Technical domains
+test.ru
+
+// rw : http://www.nic.rw/cgi-bin/policy.pl
+rw
+gov.rw
+net.rw
+edu.rw
+ac.rw
+com.rw
+co.rw
+int.rw
+mil.rw
+gouv.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> 2008-06-08
+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> 2008-06-17
+sd
+com.sd
+net.sd
+org.sd
+edu.sd
+med.sd
+tv.sd
+gov.sd
+info.sd
+
+// se : http://en.wikipedia.org/wiki/.se
+// Submitted by registry <patrik.wallstrom@iis.se> 2014-03-18
+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 : http://en.wikipedia.org/wiki/.si
+si
+
+// sj : No registrations at this time.
+// Submitted by registry <jarle@uninett.no> 2008-06-16
+sj
+
+// sk : http://en.wikipedia.org/wiki/.sk
+// list of 2nd level domains ?
+sk
+
+// sl : http://www.nic.sl
+// Submitted by registry <adam@neoip.com> 2008-06-12
+sl
+com.sl
+net.sl
+edu.sl
+gov.sl
+org.sl
+
+// sm : http://en.wikipedia.org/wiki/.sm
+sm
+
+// sn : http://en.wikipedia.org/wiki/.sn
+sn
+art.sn
+com.sn
+edu.sn
+gouv.sn
+org.sn
+perso.sn
+univ.sn
+
+// so : http://www.soregistry.com/
+so
+com.so
+net.so
+org.so
+
+// sr : http://en.wikipedia.org/wiki/.sr
+sr
+
+// 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 : http://en.wikipedia.org/wiki/.su
+su
+adygeya.su
+arkhangelsk.su
+balashov.su
+bashkiria.su
+bryansk.su
+dagestan.su
+grozny.su
+ivanovo.su
+kalmykia.su
+kaluga.su
+karelia.su
+khakassia.su
+krasnodar.su
+kurgan.su
+lenug.su
+mordovia.su
+msk.su
+murmansk.su
+nalchik.su
+nov.su
+obninsk.su
+penza.su
+pokrovsk.su
+sochi.su
+spb.su
+togliatti.su
+troitsk.su
+tula.su
+tuva.su
+vladikavkaz.su
+vladimir.su
+vologda.su
+
+// sv : http://www.svnet.org.sv/niveldos.pdf
+sv
+com.sv
+edu.sv
+gob.sv
+org.sv
+red.sv
+
+// sx : http://en.wikipedia.org/wiki/.sx
+// Confirmed by registry <jcvignes@openregistry.com> 2012-05-31
+sx
+gov.sx
+
+// sy : http://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 : http://en.wikipedia.org/wiki/.sz
+// http://www.sispa.org.sz/
+sz
+co.sz
+ac.sz
+org.sz
+
+// tc : http://en.wikipedia.org/wiki/.tc
+tc
+
+// td : http://en.wikipedia.org/wiki/.td
+td
+
+// tel: http://en.wikipedia.org/wiki/.tel
+// http://www.telnic.org/
+tel
+
+// tf : http://en.wikipedia.org/wiki/.tf
+tf
+
+// tg : http://en.wikipedia.org/wiki/.tg
+// http://www.nic.tg/
+tg
+
+// th : http://en.wikipedia.org/wiki/.th
+// Submitted by registry <krit@thains.co.th> 2008-06-17
+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 : http://en.wikipedia.org/wiki/.tk
+tk
+
+// tl : http://en.wikipedia.org/wiki/.tl
+tl
+gov.tl
+
+// tm : http://www.nic.tm/local.html
+tm
+com.tm
+co.tm
+org.tm
+net.tm
+nom.tm
+gov.tm
+mil.tm
+edu.tm
+
+// tn : http://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 : http://en.wikipedia.org/wiki/.to
+// Submitted by registry <egullich@colo.to> 2008-06-17
+to
+com.to
+gov.to
+net.to
+org.to
+edu.to
+mil.to
+
+// tp : No registrations at this time.
+// Submitted by Ryan Sleevi <ryan.sleevi@gmail.com> 2014-01-03
+tp
+
+// subTLDs: https://www.nic.tr/forms/eng/policies.pdf
+// and: https://www.nic.tr/forms/politikalar.pdf
+// Submitted by <mehmetgurevin@gmail.com> 2014-07-19
+tr
+com.tr
+info.tr
+biz.tr
+net.tr
+org.tr
+web.tr
+gen.tr
+tv.tr
+av.tr
+dr.tr
+bbs.tr
+name.tr
+tel.tr
+gov.tr
+bel.tr
+pol.tr
+mil.tr
+k12.tr
+edu.tr
+kep.tr
+
+// Used by Northern Cyprus
+nc.tr
+
+// Used by government agencies of Northern Cyprus
+gov.nc.tr
+
+// travel : http://en.wikipedia.org/wiki/.travel
+travel
+
+// 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 : http://en.wikipedia.org/wiki/.tv
+// Not listing any 2LDs as reserved since none seem to exist in practice,
+// Wikipedia notwithstanding.
+tv
+
+// tw : http://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
+// Confirmed by registry <manager@tznic.or.tz> 2013-01-22
+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> 2012-04-27
+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
+dominic.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
+
+// Private registries in .ua
+co.ua
+pp.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 : http://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 : http://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
+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
+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 indepedently except for private (PVT), charter (CHTR) and
+// parochial (PAROCH) schools. Those are delegated dorectly to the
+// 5LD operators. <k12-ma-hostmaster _ at _ rsuc.gweep.net>
+pvt.k12.ma.us
+chtr.k12.ma.us
+paroch.k12.ma.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 : http://en.wikipedia.org/wiki/.va
+va
+
+// vc : http://en.wikipedia.org/wiki/.vc
+// Submitted by registry <kshah@ca.afilias.info> 2008-06-13
+vc
+com.vc
+net.vc
+org.vc
+gov.vc
+mil.vc
+edu.vc
+
+// ve : https://registro.nic.ve/
+// Confirmed by registry 2012-10-04
+// Updated 2014-05-20 - Bug 940478
+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 : http://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 : http://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 : http://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--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--node ("ge", Georgian Mkhedruli) : GE
+გე
+
+// xn--qxam ("el", Greek) : GR
+// Hellenic Ministry of Infrastructure, Transport, and Networks
+ελ
+
+// xn--j6w193g ("Hong Kong", Chinese) : HK
+// https://www2.hkirc.hk/register/rules.jsp
+香港
+
+// xn--h2brj9c ("Bharat", Devanagari) : 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--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK
+// http://nic.lk
+ලංකා
+
+// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK
+// http://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
+// http://www.rnids.rs/en/the-.срб-domain
+срб
+пр.срб
+орг.срб
+обр.срб
+од.срб
+упр.срб
+ак.срб
+
+// xn--p1ai ("rf", Russian-Cyrillic) : RU
+// http://www.cctld.ru/en/docs/rulesrf.php
+рф
+
+// 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
+
+// za : http://www.zadna.org.za/content/page/domain-information
+ac.za
+agrica.za
+alt.za
+co.za
+edu.za
+gov.za
+grondar.za
+law.za
+mil.za
+net.za
+ngo.za
+nis.za
+nom.za
+org.za
+school.za
+tm.za
+web.za
+
+// zm : http://en.wikipedia.org/wiki/.zm
+*.zm
+
+// zw : http://en.wikipedia.org/wiki/.zw
+*.zw
+
+
+// List of new gTLDs imported from https://newgtlds.icann.org/newgtlds.csv on 2015-07-27T22:08:32Z
+
+// aaa : 2015-02-26 American Automobile Association, Inc.
+aaa
+
+// aarp : 2015-05-21 AARP
+aarp
+
+// abb : 2014-10-24 ABB Ltd
+abb
+
+// abbott : 2014-07-24 Abbott Laboratories, Inc.
+abbott
+
+// able : 2015-06-25 Able Inc.
+able
+
+// abogado : 2014-04-24 Top Level Domain Holdings Limited
+abogado
+
+// academy : 2013-11-07 Half Oaks, LLC
+academy
+
+// accenture : 2014-08-15 Accenture plc
+accenture
+
+// accountant : 2014-11-20 dot Accountant Limited
+accountant
+
+// accountants : 2014-03-20 Knob Town, LLC
+accountants
+
+// aco : 2015-01-08 ACO Severin Ahlmann GmbH & Co. KG
+aco
+
+// active : 2014-05-01 The Active Network, Inc
+active
+
+// actor : 2013-12-12 United TLD Holdco Ltd.
+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
+
+// africamagic : 2015-03-05 Electronic Media Network (Pty) Ltd
+africamagic
+
+// agakhan : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation)
+agakhan
+
+// agency : 2013-11-14 Steel Falls, LLC
+agency
+
+// aig : 2014-12-18 American International Group, Inc.
+aig
+
+// airforce : 2014-03-06 United TLD Holdco Ltd.
+airforce
+
+// airtel : 2014-10-24 Bharti Airtel Limited
+airtel
+
+// akdn : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation)
+akdn
+
+// 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
+
+// ally : 2015-06-18 Ally Financial Inc.
+ally
+
+// alsace : 2014-07-02 REGION D ALSACE
+alsace
+
+// americanfamily : 2015-07-23 AmFam, Inc.
+americanfamily
+
+// 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 QIHOO 360 TECHNOLOGY CO. LTD.
+anquan
+
+// apartments : 2014-12-11 June Maple, 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
+
+// aramco : 2014-11-20 Aramco Services Company
+aramco
+
+// archi : 2014-02-06 STARTING DOT LIMITED
+archi
+
+// army : 2014-03-06 United TLD Holdco Ltd.
+army
+
+// arte : 2014-12-11 Association Relative à la Télévision Européenne G.E.I.E.
+arte
+
+// associates : 2014-03-06 Baxter Hill, LLC
+associates
+
+// attorney : 2014-03-20
+attorney
+
+// auction : 2014-03-20
+auction
+
+// audi : 2015-05-21 AUDI Aktiengesellschaft
+audi
+
+// audible : 2015-06-25 Amazon EU S.à r.l.
+audible
+
+// audio : 2014-03-20 Uniregistry, Corp.
+audio
+
+// author : 2014-12-18 Amazon EU S.à r.l.
+author
+
+// auto : 2014-11-13
+auto
+
+// autos : 2014-01-09 DERAutos, LLC
+autos
+
+// avianca : 2015-01-08 Aerovias del Continente Americano S.A. Avianca
+avianca
+
+// aws : 2015-06-25 Amazon EU S.à r.l.
+aws
+
+// axa : 2013-12-19 AXA SA
+axa
+
+// azure : 2014-12-18 Microsoft Corporation
+azure
+
+// baby : 2015-04-09 Johnson & Johnson Services, Inc.
+baby
+
+// baidu : 2015-01-08 Baidu, Inc.
+baidu
+
+// band : 2014-06-12
+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 Half Hallow, LLC
+bargains
+
+// 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
+
+// beer : 2014-01-09 Top Level Domain Holdings 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
+
+// bet : 2015-05-07 Afilias plc
+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 Grand Hollow, LLC
+bike
+
+// bing : 2014-12-18 Microsoft Corporation
+bing
+
+// bingo : 2014-12-04 Sand Cedar, LLC
+bingo
+
+// bio : 2014-03-06 STARTING DOT LIMITED
+bio
+
+// black : 2014-01-16 Afilias Limited
+black
+
+// blackfriday : 2014-01-16 Uniregistry, Corp.
+blackfriday
+
+// blanco : 2015-07-16 BLANCO GmbH + Co KG
+blanco
+
+// blog : 2015-05-14 PRIMER NIVEL S.A.
+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
+
+// bnl : 2014-07-24 Banca Nazionale del Lavoro
+bnl
+
+// bnpparibas : 2014-05-29 BNP Paribas
+bnpparibas
+
+// boats : 2014-12-04 DERBoats, LLC
+boats
+
+// boehringer : 2015-07-09 Boehringer Ingelheim International GmbH
+boehringer
+
+// bom : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+bom
+
+// bond : 2014-06-05 Bond University Limited
+bond
+
+// boo : 2014-01-30 Charleston Road Registry Inc.
+boo
+
+// booking : 2015-07-16 Booking.com B.V.
+booking
+
+// boots : 2015-01-08 THE BOOTS COMPANY PLC
+boots
+
+// bosch : 2015-06-18 Robert Bosch GMBH
+bosch
+
+// bostik : 2015-05-28 Bostik SA
+bostik
+
+// bot : 2014-12-18 Amazon EU S.à r.l.
+bot
+
+// boutique : 2013-11-14 Over Galley, LLC
+boutique
+
+// 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 IG Group Holdings PLC
+broker
+
+// brother : 2015-01-29 Brother Industries, Ltd.
+brother
+
+// brussels : 2014-02-06 DNS.be vzw
+brussels
+
+// budapest : 2013-11-21 Top Level Domain Holdings Limited
+budapest
+
+// bugatti : 2015-07-23 Bugatti International SA
+bugatti
+
+// build : 2013-11-07 Plan Bee LLC
+build
+
+// builders : 2013-11-07 Atomic Madison, LLC
+builders
+
+// business : 2013-11-07 Spring Cross, LLC
+business
+
+// buy : 2014-12-18 Amazon EU S.à r.l.
+buy
+
+// buzz : 2013-10-02 DOTSTRATEGY CO.
+buzz
+
+// bzh : 2014-02-27 Association www.bzh
+bzh
+
+// cab : 2013-10-24 Half Sunset, LLC
+cab
+
+// cafe : 2015-02-11 Pioneer Canyon, LLC
+cafe
+
+// cal : 2014-07-24 Charleston Road Registry Inc.
+cal
+
+// call : 2014-12-18 Amazon EU S.à r.l.
+call
+
+// camera : 2013-08-27 Atomic Maple, LLC
+camera
+
+// camp : 2013-11-07 Delta Dynamite, 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 Delta Mill, LLC
+capital
+
+// car : 2015-01-22
+car
+
+// caravan : 2013-12-12 Caravan International, Inc.
+caravan
+
+// cards : 2013-12-05 Foggy Hollow, LLC
+cards
+
+// care : 2014-03-06 Goose Cross
+care
+
+// career : 2013-10-09 dotCareer LLC
+career
+
+// careers : 2013-10-02 Wild Corner, LLC
+careers
+
+// cars : 2014-11-13
+cars
+
+// cartier : 2014-06-23 Richemont DNS Inc.
+cartier
+
+// casa : 2013-11-21 Top Level Domain Holdings Limited
+casa
+
+// cash : 2014-03-06 Delta Lake, LLC
+cash
+
+// casino : 2014-12-18 Binky Sky, LLC
+casino
+
+// catering : 2013-12-05 New Falls. LLC
+catering
+
+// 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
+
+// ceb : 2015-04-09 The Corporate Executive Board Company
+ceb
+
+// center : 2013-11-07 Tin Mill, 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 IG Group Holdings PLC
+cfd
+
+// chanel : 2015-04-09 Chanel International B.V.
+chanel
+
+// channel : 2014-05-08 Charleston Road Registry Inc.
+channel
+
+// chase : 2015-04-30 JPMorgan Chase & Co.
+chase
+
+// chat : 2014-12-04 Sand Fields, LLC
+chat
+
+// cheap : 2013-11-14 Sand Cover, LLC
+cheap
+
+// chintai : 2015-06-11 CHINTAI Corporation
+chintai
+
+// chloe : 2014-10-16 Richemont DNS Inc.
+chloe
+
+// christmas : 2013-11-21 Uniregistry, Corp.
+christmas
+
+// chrome : 2014-07-24 Charleston Road Registry Inc.
+chrome
+
+// church : 2014-02-06 Holly Fields, LLC
+church
+
+// cipriani : 2015-02-19 Hotel Cipriani Srl
+cipriani
+
+// circle : 2014-12-18 Amazon EU S.à r.l.
+circle
+
+// cisco : 2014-12-22 Cisco Technology, Inc.
+cisco
+
+// citadel : 2015-07-23 Citadel Domain LLC
+citadel
+
+// citic : 2014-01-09 CITIC Group Corporation
+citic
+
+// city : 2014-05-29 Snow Sky, LLC
+city
+
+// cityeats : 2014-12-11 Lifestyle Domain Holdings, Inc.
+cityeats
+
+// claims : 2014-03-20 Black Corner, LLC
+claims
+
+// cleaning : 2013-12-05 Fox Shadow, LLC
+cleaning
+
+// click : 2014-06-05 Uniregistry, Corp.
+click
+
+// clinic : 2014-03-20 Goose Park, LLC
+clinic
+
+// clothing : 2013-08-27 Steel Lake, LLC
+clothing
+
+// cloud : 2015-04-16 ARUBA 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 Koko Island, LLC
+coach
+
+// codes : 2013-10-31 Puff Willow, LLC
+codes
+
+// coffee : 2013-10-17 Trixy Cover, LLC
+coffee
+
+// college : 2014-01-16 XYZ.COM LLC
+college
+
+// cologne : 2014-02-05 NetCologne Gesellschaft für Telekommunikation mbH
+cologne
+
+// comcast : 2015-07-23 Comcast IP Holdings I, LLC
+comcast
+
+// commbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA
+commbank
+
+// community : 2013-12-05 Fox Orchard, LLC
+community
+
+// company : 2013-11-07 Silver Avenue, LLC
+company
+
+// computer : 2013-10-24 Pine Mill, LLC
+computer
+
+// comsec : 2015-01-08 VeriSign, Inc.
+comsec
+
+// condos : 2013-12-05 Pine House, LLC
+condos
+
+// construction : 2013-09-16 Fox Dynamite, LLC
+construction
+
+// consulting : 2013-12-05
+consulting
+
+// contact : 2015-01-08 Top Level Spectrum, Inc.
+contact
+
+// contractors : 2013-09-10 Magic Woods, LLC
+contractors
+
+// cooking : 2013-11-21 Top Level Domain Holdings Limited
+cooking
+
+// cookingchannel : 2015-07-02 Lifestyle Domain Holdings, Inc.
+cookingchannel
+
+// cool : 2013-11-14 Koko Lake, LLC
+cool
+
+// corsica : 2014-09-25 Collectivité Territoriale de Corse
+corsica
+
+// country : 2013-12-19 Top Level Domain Holdings Limited
+country
+
+// coupon : 2015-02-26 Amazon EU S.à r.l.
+coupon
+
+// coupons : 2015-03-26 Black Island, LLC
+coupons
+
+// courses : 2014-12-04 OPEN UNIVERSITIES AUSTRALIA PTY LTD
+courses
+
+// credit : 2014-03-20 Snow Shadow, LLC
+credit
+
+// creditcard : 2014-03-20 Binky Frostbite, LLC
+creditcard
+
+// creditunion : 2015-01-22 CUNA Performance Resources, 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
+
+// cruises : 2013-12-05 Spring Way, LLC
+cruises
+
+// csc : 2014-09-25 Alliance-One Services, Inc.
+csc
+
+// cuisinella : 2014-04-03 SALM S.A.S.
+cuisinella
+
+// cymru : 2014-05-08 Nominet UK
+cymru
+
+// cyou : 2015-01-22 Beijing Gamease Age Digital Technology Co., Ltd.
+cyou
+
+// dabur : 2014-02-06 Dabur India Limited
+dabur
+
+// dad : 2014-01-23 Charleston Road Registry Inc.
+dad
+
+// dance : 2013-10-24 United TLD Holdco Ltd.
+dance
+
+// date : 2014-11-20 dot Date Limited
+date
+
+// dating : 2013-12-05 Pine Fest, 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 Top Level Domain Holdings Limited
+dds
+
+// deal : 2015-06-25 Amazon EU S.à r.l.
+deal
+
+// dealer : 2014-12-22 Dealer Dot Com, Inc.
+dealer
+
+// deals : 2014-05-22 Sand Sunset, LLC
+deals
+
+// degree : 2014-03-06
+degree
+
+// delivery : 2014-09-11 Steel Station, LLC
+delivery
+
+// dell : 2014-10-24 Dell Inc.
+dell
+
+// delta : 2015-02-19 Delta Air Lines, Inc.
+delta
+
+// democrat : 2013-10-24 United TLD Holdco Ltd.
+democrat
+
+// dental : 2014-03-20 Tin Birch, LLC
+dental
+
+// dentist : 2014-03-20
+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 John Edge, LLC
+diamonds
+
+// diet : 2014-06-26 Uniregistry, Corp.
+diet
+
+// digital : 2014-03-06 Dash Park, LLC
+digital
+
+// direct : 2014-04-10 Half Trail, LLC
+direct
+
+// directory : 2013-09-20 Extra Madison, LLC
+directory
+
+// discount : 2014-03-06 Holly Hill, LLC
+discount
+
+// discover : 2015-07-23 Discover Financial Services
+discover
+
+// dnp : 2013-12-13 Dai Nippon Printing Co., Ltd.
+dnp
+
+// docs : 2014-10-16 Charleston Road Registry Inc.
+docs
+
+// dog : 2014-12-04 Koko Mill, LLC
+dog
+
+// doha : 2014-09-18 Communications Regulatory Authority (CRA)
+doha
+
+// domains : 2013-10-17 Sugar Cross, LLC
+domains
+
+// doosan : 2014-04-03 Doosan Corporation
+doosan
+
+// 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
+
+// dstv : 2015-03-12 MultiChoice (Proprietary) Limited
+dstv
+
+// 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
+
+// dwg : 2015-07-23 Autodesk, Inc.
+dwg
+
+// earth : 2014-12-04 Interlink Co., Ltd.
+earth
+
+// eat : 2014-01-23 Charleston Road Registry Inc.
+eat
+
+// edeka : 2014-12-18 EDEKA Verband kaufmännischer Genossenschaften e.V.
+edeka
+
+// education : 2013-11-07 Brice Way, LLC
+education
+
+// email : 2013-10-31 Spring Madison, LLC
+email
+
+// emerck : 2014-04-03 Merck KGaA
+emerck
+
+// emerson : 2015-07-23 Emerson Electric Co.
+emerson
+
+// energy : 2014-09-11 Binky Birch, LLC
+energy
+
+// engineer : 2014-03-06 United TLD Holdco Ltd.
+engineer
+
+// engineering : 2014-03-06 Romeo Canyon
+engineering
+
+// enterprises : 2013-09-20 Snow Oaks, LLC
+enterprises
+
+// epost : 2015-07-23 Deutsche Post AG
+epost
+
+// epson : 2014-12-04 Seiko Epson Corporation
+epson
+
+// equipment : 2013-08-27 Corn Station, 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 Trixy Park, LLC
+estate
+
+// esurance : 2015-07-23 Esurance Insurance Company
+esurance
+
+// eurovision : 2014-04-24 European Broadcasting Union (EBU)
+eurovision
+
+// eus : 2013-12-12 Puntueus Fundazioa
+eus
+
+// events : 2013-12-05 Pioneer Maple, LLC
+events
+
+// everbank : 2014-05-15 EverBank
+everbank
+
+// exchange : 2014-03-06 Spring Falls, LLC
+exchange
+
+// expert : 2013-11-21 Magic Pass, LLC
+expert
+
+// exposed : 2013-12-05 Victor Beach, LLC
+exposed
+
+// express : 2015-02-11 Sea Sunset, LLC
+express
+
+// extraspace : 2015-05-14 Extra Space Storage LLC
+extraspace
+
+// fage : 2014-12-18 Fage International S.A.
+fage
+
+// fail : 2014-03-06 Atomic Pipe, LLC
+fail
+
+// fairwinds : 2014-11-13 FairWinds Partners, LLC
+fairwinds
+
+// faith : 2014-11-20 dot Faith Limited
+faith
+
+// family : 2015-04-02
+family
+
+// fan : 2014-03-06
+fan
+
+// fans : 2014-11-07 Asiamix Digital Limited
+fans
+
+// farm : 2013-11-07 Just Maple, LLC
+farm
+
+// farmers : 2015-07-09 Farmers Insurance Exchange
+farmers
+
+// fashion : 2014-07-03 Top Level Domain Holdings Limited
+fashion
+
+// fast : 2014-12-18 Amazon EU S.à r.l.
+fast
+
+// feedback : 2013-12-19 Top Level Spectrum, Inc.
+feedback
+
+// ferrero : 2014-12-18 Ferrero Trading Lux S.A.
+ferrero
+
+// 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 Cotton Cypress, LLC
+finance
+
+// financial : 2014-03-06 Just Cover, LLC
+financial
+
+// fire : 2015-06-25 Amazon EU S.à r.l.
+fire
+
+// firestone : 2014-12-18 Bridgestone Corporation
+firestone
+
+// firmdale : 2014-03-27 Firmdale Holdings Limited
+firmdale
+
+// fish : 2013-12-12 Fox Woods, LLC
+fish
+
+// fishing : 2013-11-21 Top Level Domain Holdings Limited
+fishing
+
+// fit : 2014-11-07 Top Level Domain Holdings Limited
+fit
+
+// fitness : 2014-03-06 Brice Orchard, LLC
+fitness
+
+// flickr : 2015-04-02 Yahoo! Domain Services Inc.
+flickr
+
+// flights : 2013-12-05 Fox Station, LLC
+flights
+
+// flir : 2015-07-23 FLIR Systems, Inc.
+flir
+
+// florist : 2013-11-07 Half Cypress, LLC
+florist
+
+// flowers : 2014-10-09 Uniregistry, Corp.
+flowers
+
+// flsmidth : 2014-07-24 FLSmidth A/S
+flsmidth
+
+// fly : 2014-05-08 Charleston Road Registry Inc.
+fly
+
+// foo : 2014-01-23 Charleston Road Registry Inc.
+foo
+
+// foodnetwork : 2015-07-02 Lifestyle Domain Holdings, Inc.
+foodnetwork
+
+// football : 2014-12-18 Foggy Farms, LLC
+football
+
+// ford : 2014-11-13 Ford Motor Company
+ford
+
+// forex : 2014-12-11 IG Group Holdings PLC
+forex
+
+// forsale : 2014-05-22
+forsale
+
+// forum : 2015-04-02 Fegistry, LLC
+forum
+
+// foundation : 2013-12-05 John Dale, LLC
+foundation
+
+// 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
+
+// fujixerox : 2015-07-23 Xerox DNHC LLC
+fujixerox
+
+// fund : 2014-03-20 John Castle, LLC
+fund
+
+// furniture : 2014-03-20 Lone Fields, LLC
+furniture
+
+// futbol : 2013-09-20
+futbol
+
+// fyi : 2015-04-02 Silver Tigers, LLC
+fyi
+
+// gal : 2013-11-07 Asociación puntoGAL
+gal
+
+// gallery : 2013-09-13 Sugar House, LLC
+gallery
+
+// gallo : 2015-06-11 Gallo Vineyards, Inc.
+gallo
+
+// gallup : 2015-02-19 Gallup, Inc.
+gallup
+
+// game : 2015-05-28 Uniregistry, Corp.
+game
+
+// games : 2015-05-28 Foggy Beach, LLC
+games
+
+// garden : 2014-06-26 Top Level Domain Holdings Limited
+garden
+
+// 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 GROUP NV/SA
+gent
+
+// genting : 2015-03-12 Resorts World Inc Pte. Ltd.
+genting
+
+// ggee : 2014-01-09 GMO Internet, Inc.
+ggee
+
+// gift : 2013-10-17 Uniregistry, Corp.
+gift
+
+// gifts : 2014-07-03 Goose Sky, LLC
+gifts
+
+// gives : 2014-03-06 United TLD Holdco Ltd.
+gives
+
+// giving : 2014-11-13 Giving Limited
+giving
+
+// glade : 2015-07-23 Johnson Shareholdings, Inc.
+glade
+
+// glass : 2013-11-07 Black Cover, LLC
+glass
+
+// gle : 2014-07-24 Charleston Road Registry Inc.
+gle
+
+// global : 2014-04-17 Dot GLOBAL AS
+global
+
+// globo : 2013-12-19 Globo Comunicação e Participações S.A
+globo
+
+// gmail : 2014-05-01 Charleston Road Registry Inc.
+gmail
+
+// 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 June Edge, LLC
+gold
+
+// goldpoint : 2014-11-20 YODOBASHI CAMERA CO.,LTD.
+goldpoint
+
+// golf : 2014-12-18 Lone falls, 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 EU S.à r.l.
+got
+
+// gotv : 2015-03-12 MultiChoice (Proprietary) Limited
+gotv
+
+// grainger : 2015-05-07 Grainger Registry Services, LLC
+grainger
+
+// graphics : 2013-09-13 Over Madison, LLC
+graphics
+
+// gratis : 2014-03-20 Pioneer Tigers, LLC
+gratis
+
+// green : 2014-05-08 Afilias Limited
+green
+
+// gripe : 2014-03-06 Corn Sunset, LLC
+gripe
+
+// group : 2014-08-15 Romeo Town, LLC
+group
+
+// gucci : 2014-11-13 Guccio Gucci S.p.a.
+gucci
+
+// guge : 2014-08-28 Charleston Road Registry Inc.
+guge
+
+// guide : 2013-09-13 Snow Moon, LLC
+guide
+
+// guitars : 2013-11-14 Uniregistry, Corp.
+guitars
+
+// guru : 2013-08-27 Pioneer Cypress, LLC
+guru
+
+// hamburg : 2014-02-20 Hamburg Top-Level-Domain GmbH
+hamburg
+
+// hangout : 2014-11-13 Charleston Road Registry Inc.
+hangout
+
+// haus : 2013-12-05
+haus
+
+// hdfcbank : 2015-02-12 HDFC Bank Limited
+hdfcbank
+
+// health : 2015-02-11 DotHealth, LLC
+health
+
+// healthcare : 2014-06-12 Silver Glen, LLC
+healthcare
+
+// help : 2014-06-26 Uniregistry, 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 Uniregistry, Corp.
+hiphop
+
+// hisamitsu : 2015-07-16 Hisamitsu Pharmaceutical Co.,Inc.
+hisamitsu
+
+// hitachi : 2014-10-31 Hitachi, Ltd.
+hitachi
+
+// hiv : 2014-03-13 dotHIV gemeinnuetziger e.V.
+hiv
+
+// hkt : 2015-05-14 PCCW-HKT DataCom Services Limited
+hkt
+
+// hockey : 2015-03-19 Half Willow, LLC
+hockey
+
+// holdings : 2013-08-27 John Madison, LLC
+holdings
+
+// holiday : 2013-11-07 Goose Woods, LLC
+holiday
+
+// homedepot : 2015-04-02 Homer TLC, Inc.
+homedepot
+
+// homegoods : 2015-07-16 The TJX Companies, Inc.
+homegoods
+
+// homes : 2014-01-09 DERHomes, LLC
+homes
+
+// homesense : 2015-07-16 The TJX Companies, Inc.
+homesense
+
+// honda : 2014-12-18 Honda Motor Co., Ltd.
+honda
+
+// honeywell : 2015-07-23 Honeywell GTLD LLC
+honeywell
+
+// horse : 2013-11-21 Top Level Domain Holdings Limited
+horse
+
+// host : 2014-04-17 DotHost Inc.
+host
+
+// hosting : 2014-05-29 Uniregistry, Corp.
+hosting
+
+// hoteles : 2015-03-05 Travel Reservations SRL
+hoteles
+
+// hotmail : 2014-12-18 Microsoft Corporation
+hotmail
+
+// house : 2013-11-07 Sugar Park, LLC
+house
+
+// how : 2014-01-23 Charleston Road Registry Inc.
+how
+
+// hsbc : 2014-10-24 HSBC Holdings PLC
+hsbc
+
+// htc : 2015-04-02 HTC corporation
+htc
+
+// 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 One.com A/S
+icu
+
+// ieee : 2015-07-23 IEEE Global LLC
+ieee
+
+// ifm : 2014-01-30 ifm electronic gmbh
+ifm
+
+// iinet : 2014-07-03 Connect West Pty. Ltd.
+iinet
+
+// ikano : 2015-07-09 Ikano S.A.
+ikano
+
+// imdb : 2015-06-25 Amazon EU S.à r.l.
+imdb
+
+// immo : 2014-07-10 Auburn Bloom, LLC
+immo
+
+// immobilien : 2013-11-07 United TLD Holdco Ltd.
+immobilien
+
+// industries : 2013-12-05 Outer House, 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 Outer Maple, LLC
+institute
+
+// insurance : 2015-02-19 fTLD Registry Services LLC
+insurance
+
+// insure : 2014-03-20 Pioneer Willow, LLC
+insure
+
+// international : 2013-11-07 Wild Way, LLC
+international
+
+// investments : 2014-03-20 Holly Glen, LLC
+investments
+
+// ipiranga : 2014-08-28 Ipiranga Produtos de Petroleo S.A.
+ipiranga
+
+// irish : 2014-08-07 Dot-Irish LLC
+irish
+
+// iselect : 2015-02-11 iSelect Ltd
+iselect
+
+// 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
+
+// iwc : 2014-06-23 Richemont DNS Inc.
+iwc
+
+// jaguar : 2014-11-13 Jaguar Land Rover Ltd
+jaguar
+
+// java : 2014-06-19 Oracle Corporation
+java
+
+// jcb : 2014-11-20 JCB Co., Ltd.
+jcb
+
+// jcp : 2015-04-23 JCP Media, Inc.
+jcp
+
+// jetzt : 2014-01-09 New TLD Company AB
+jetzt
+
+// jewelry : 2015-03-05 Wild Bloom, LLC
+jewelry
+
+// jio : 2015-04-02 Affinity Names, Inc.
+jio
+
+// jlc : 2014-12-04 Richemont DNS Inc.
+jlc
+
+// 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 EU S.à r.l.
+jot
+
+// joy : 2014-12-18 Amazon EU S.à r.l.
+joy
+
+// jpmorgan : 2015-04-30 JPMorgan Chase & Co.
+jpmorgan
+
+// jprs : 2014-09-18 Japan Registry Services Co., Ltd.
+jprs
+
+// juegos : 2014-03-20 Uniregistry, Corp.
+juegos
+
+// kaufen : 2013-11-07 United TLD Holdco Ltd.
+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 EU S.à r.l.
+kindle
+
+// kitchen : 2013-09-20 Just Goodbye, LLC
+kitchen
+
+// kiwi : 2013-09-20 DOT KIWI LIMITED
+kiwi
+
+// koeln : 2014-01-09 NetCologne Gesellschaft für Telekommunikation mbH
+koeln
+
+// komatsu : 2015-01-08 Komatsu Ltd.
+komatsu
+
+// 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
+
+// kyknet : 2015-03-05 Electronic Media Network (Pty) Ltd
+kyknet
+
+// kyoto : 2014-11-07 Academic Institution: Kyoto Jyoho Gakuen
+kyoto
+
+// lacaixa : 2014-01-09 CAIXA D'ESTALVIS I PENSIONS DE BARCELONA
+lacaixa
+
+// lamborghini : 2015-06-04 Automobili Lamborghini S.p.A.
+lamborghini
+
+// lancaster : 2015-02-12 LANCASTER
+lancaster
+
+// lancome : 2015-07-23 L'Oréal
+lancome
+
+// land : 2013-09-10 Pine Moon, LLC
+land
+
+// landrover : 2014-11-13 Jaguar Land Rover Ltd
+landrover
+
+// 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
+
+// latrobe : 2014-06-16 La Trobe University
+latrobe
+
+// law : 2015-01-22 Minds + Machines Group Limited
+law
+
+// lawyer : 2014-03-20
+lawyer
+
+// lds : 2014-03-20 IRI Domain Management, LLC ("Applicant")
+lds
+
+// lease : 2014-03-06 Victor Trail, 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 Blue Falls, 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
+
+// liaison : 2014-10-02 Liaison Technologies, Incorporated
+liaison
+
+// lidl : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG
+lidl
+
+// life : 2014-02-06 Trixy Oaks, 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 John McCook, LLC
+lighting
+
+// like : 2014-12-18 Amazon EU S.à r.l.
+like
+
+// limited : 2014-03-06 Big Fest, LLC
+limited
+
+// limo : 2013-10-17 Hidden Frostbite, LLC
+limo
+
+// lincoln : 2014-11-13 Ford Motor Company
+lincoln
+
+// linde : 2014-12-04 Linde Aktiengesellschaft
+linde
+
+// link : 2013-11-14 Uniregistry, Corp.
+link
+
+// lipsy : 2015-06-25 Lipsy Ltd
+lipsy
+
+// live : 2014-12-04
+live
+
+// lixil : 2015-03-19 LIXIL Group Corporation
+lixil
+
+// loan : 2014-11-20 dot Loan Limited
+loan
+
+// loans : 2014-03-20 June Woods, LLC
+loans
+
+// locker : 2015-06-04 Dish DBS Corporation
+locker
+
+// locus : 2015-06-25 Locus Analytics LLC
+locus
+
+// lol : 2015-01-30 Uniregistry, 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
+
+// ltd : 2014-09-25 Over Corner, LLC
+ltd
+
+// ltda : 2014-04-17 DOMAIN ROBOT SERVICOS DE HOSPEDAGEM NA INTERNET LTDA
+ltda
+
+// lupin : 2014-11-07 LUPIN LIMITED
+lupin
+
+// luxe : 2014-01-09 Top Level Domain Holdings Limited
+luxe
+
+// luxury : 2013-10-17 Luxury Partners, LLC
+luxury
+
+// madrid : 2014-05-01 Comunidad de Madrid
+madrid
+
+// maif : 2014-10-02 Mutuelle Assurance Instituteur France (MAIF)
+maif
+
+// maison : 2013-12-05 Victor Frostbite, LLC
+maison
+
+// makeup : 2015-01-15 L'Oréal
+makeup
+
+// man : 2014-12-04 MAN SE
+man
+
+// management : 2013-11-07 John Goodbye, LLC
+management
+
+// mango : 2013-10-24 PUNTO FA S.L.
+mango
+
+// market : 2014-03-06
+market
+
+// marketing : 2013-11-07 Fern Pass, LLC
+marketing
+
+// markets : 2014-12-11 IG Group Holdings PLC
+markets
+
+// marriott : 2014-10-09 Marriott Worldwide Corporation
+marriott
+
+// marshalls : 2015-07-16 The TJX Companies, Inc.
+marshalls
+
+// mba : 2015-04-02 Lone Hollow, LLC
+mba
+
+// media : 2014-03-06 Grand Glen, LLC
+media
+
+// meet : 2014-01-16
+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 Wedding TLD2, LLC
+menu
+
+// meo : 2014-11-07 PT Comunicacoes S.A.
+meo
+
+// metlife : 2015-05-07 MetLife Services and Solutions, LLC
+metlife
+
+// miami : 2013-12-19 Top Level Domain Holdings Limited
+miami
+
+// microsoft : 2014-12-18 Microsoft Corporation
+microsoft
+
+// mini : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft
+mini
+
+// 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
+
+// mnet : 2015-03-05 Electronic Media Network (Pty) Ltd
+mnet
+
+// mobily : 2014-12-18 GreenTech Consultancy Company W.L.L.
+mobily
+
+// moda : 2013-11-07 United TLD Holdco Ltd.
+moda
+
+// moe : 2013-11-13 Interlink Co., Ltd.
+moe
+
+// moi : 2014-12-18 Amazon EU S.à r.l.
+moi
+
+// mom : 2015-04-16 Uniregistry, Corp.
+mom
+
+// monash : 2013-09-30 Monash University
+monash
+
+// money : 2014-10-16 Outer McCook, LLC
+money
+
+// montblanc : 2014-06-23 Richemont DNS Inc.
+montblanc
+
+// mormon : 2013-12-05 IRI Domain Management, LLC ("Applicant")
+mormon
+
+// mortgage : 2014-03-20
+mortgage
+
+// moscow : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+moscow
+
+// moto : 2015-06-04 Charleston Road Registry Inc.
+moto
+
+// motorcycles : 2014-01-09 DERMotorcycles, LLC
+motorcycles
+
+// mov : 2014-01-30 Charleston Road Registry Inc.
+mov
+
+// movie : 2015-02-05 New Frostbite, LLC
+movie
+
+// movistar : 2014-10-16 Telefónica S.A.
+movistar
+
+// msd : 2015-07-23 MSD Registry Holdings, Inc.
+msd
+
+// mtn : 2014-12-04 MTN Dubai Limited
+mtn
+
+// mtpc : 2014-11-20 Mitsubishi Tanabe Pharma Corporation
+mtpc
+
+// mtr : 2015-03-12 MTR Corporation Limited
+mtr
+
+// multichoice : 2015-03-12 MultiChoice (Proprietary) Limited
+multichoice
+
+// mutual : 2015-04-02 Northwestern Mutual MU TLD Registry, LLC
+mutual
+
+// mutuelle : 2015-06-18 Fédération Nationale de la Mutualité Française
+mutuelle
+
+// mzansimagic : 2015-03-05 Electronic Media Network (Pty) Ltd
+mzansimagic
+
+// nadex : 2014-12-11 IG Group Holdings PLC
+nadex
+
+// nagoya : 2013-10-24 GMO Registry, Inc.
+nagoya
+
+// naspers : 2015-02-12 Intelprop (Proprietary) Limited
+naspers
+
+// nationwide : 2015-07-23 Nationwide Mutual Insurance Company
+nationwide
+
+// natura : 2015-03-12 NATURA COSMÉTICOS S.A.
+natura
+
+// navy : 2014-03-06 United TLD Holdco Ltd.
+navy
+
+// 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 Trixy Manor, LLC
+network
+
+// neustar : 2013-12-05 NeuStar, Inc.
+neustar
+
+// new : 2014-01-30 Charleston Road Registry Inc.
+new
+
+// news : 2014-12-18
+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 United TLD Holdco Ltd.
+ninja
+
+// nissan : 2014-03-27 NISSAN MOTOR CO., LTD.
+nissan
+
+// nokia : 2015-01-08 Nokia Corporation
+nokia
+
+// northwesternmutual : 2015-06-18 Northwestern Mutual Registry, LLC
+northwesternmutual
+
+// norton : 2014-12-04 Symantec Corporation
+norton
+
+// now : 2015-06-25 Amazon EU S.à r.l.
+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 Guardian News and Media Limited
+observer
+
+// off : 2015-07-23 Johnson Shareholdings, Inc.
+off
+
+// office : 2015-03-12 Microsoft Corporation
+office
+
+// okinawa : 2013-12-05 BusinessRalliart Inc.
+okinawa
+
+// olayan : 2015-05-14 Crescent Holding GmbH
+olayan
+
+// olayangroup : 2015-05-14 Crescent Holding GmbH
+olayangroup
+
+// 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 I-Registry Ltd.
+onl
+
+// online : 2015-01-15 DotOnline Inc.
+online
+
+// onyourside : 2015-07-23 Nationwide Mutual Insurance Company
+onyourside
+
+// ooo : 2014-01-09 INFIBEAM INCORPORATION LIMITED
+ooo
+
+// oracle : 2014-06-19 Oracle Corporation
+oracle
+
+// orange : 2015-03-12 Orange Brand Services Limited
+orange
+
+// organic : 2014-03-27 Afilias Limited
+organic
+
+// orientexpress : 2015-02-05 Belmond Ltd.
+orientexpress
+
+// osaka : 2014-09-04 Interlink Co., Ltd.
+osaka
+
+// otsuka : 2013-10-11 Otsuka Holdings Co., Ltd.
+otsuka
+
+// ott : 2015-06-04 Dish DBS Corporation
+ott
+
+// ovh : 2014-01-16 OVH SAS
+ovh
+
+// page : 2014-12-04 Charleston Road Registry Inc.
+page
+
+// pamperedchef : 2015-02-05 The Pampered Chef, Ltd.
+pamperedchef
+
+// panerai : 2014-11-07 Richemont DNS Inc.
+panerai
+
+// 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 Magic Glen, LLC
+partners
+
+// parts : 2013-12-05 Sea Goodbye, LLC
+parts
+
+// party : 2014-09-11 Blue Sky Registry Limited
+party
+
+// passagens : 2015-03-05 Travel Reservations SRL
+passagens
+
+// payu : 2015-02-12 MIH PayU B.V.
+payu
+
+// pccw : 2015-05-14 PCCW Enterprises Limited
+pccw
+
+// pet : 2015-05-07 Afilias plc
+pet
+
+// pharmacy : 2014-06-19 National Association of Boards of Pharmacy
+pharmacy
+
+// philips : 2014-11-07 Koninklijke Philips N.V.
+philips
+
+// photo : 2013-11-14 Uniregistry, Corp.
+photo
+
+// photography : 2013-09-20 Sugar Glen, LLC
+photography
+
+// photos : 2013-10-17 Sea Corner, LLC
+photos
+
+// physio : 2014-05-01 PhysBiz Pty Ltd
+physio
+
+// piaget : 2014-10-16 Richemont DNS Inc.
+piaget
+
+// pics : 2013-11-14 Uniregistry, Corp.
+pics
+
+// pictet : 2014-06-26 Pictet Europe S.A.
+pictet
+
+// pictures : 2014-03-06 Foggy Sky, LLC
+pictures
+
+// pid : 2015-01-08 Top Level Spectrum, Inc.
+pid
+
+// pin : 2014-12-18 Amazon EU S.à r.l.
+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 Foggy Moon, LLC
+pizza
+
+// place : 2014-04-24 Snow Galley, LLC
+place
+
+// play : 2015-03-05 Charleston Road Registry Inc.
+play
+
+// playstation : 2015-07-02 Sony Computer Entertainment Inc.
+playstation
+
+// plumbing : 2013-09-10 Spring Tigers, LLC
+plumbing
+
+// plus : 2015-02-05 Sugar Mill, 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 Domains No. 5 Limited
+poker
+
+// porn : 2014-10-16 ICM Registry PN LLC
+porn
+
+// praxi : 2013-12-05 Praxi S.p.A.
+praxi
+
+// press : 2014-04-03 DotPress Inc.
+press
+
+// prime : 2015-06-25 Amazon EU S.à r.l.
+prime
+
+// prod : 2014-01-23 Charleston Road Registry Inc.
+prod
+
+// productions : 2013-12-05 Magic Birch, LLC
+productions
+
+// prof : 2014-07-24 Charleston Road Registry Inc.
+prof
+
+// progressive : 2015-07-23 Progressive Casualty Insurance Company
+progressive
+
+// promo : 2014-12-18 Play.PROMO Oy
+promo
+
+// properties : 2013-12-05 Big Pass, LLC
+properties
+
+// property : 2014-05-22 Uniregistry, Corp.
+property
+
+// protection : 2015-04-23
+protection
+
+// pub : 2013-12-12 United TLD Holdco Ltd.
+pub
+
+// qpon : 2013-11-14 dotCOOL, Inc.
+qpon
+
+// quebec : 2013-12-19 PointQuébec Inc
+quebec
+
+// quest : 2015-03-26 Quest ION Limited
+quest
+
+// racing : 2014-12-04 Premier Registry Limited
+racing
+
+// raid : 2015-07-23 Johnson Shareholdings, Inc.
+raid
+
+// read : 2014-12-18 Amazon EU S.à r.l.
+read
+
+// realtor : 2014-05-29 Real Estate Domains LLC
+realtor
+
+// realty : 2015-03-19 Fegistry, LLC
+realty
+
+// recipes : 2013-10-17 Grand Island, 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 United TLD Holdco Ltd.
+rehab
+
+// reise : 2014-03-13
+reise
+
+// reisen : 2014-03-06 New Cypress, 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 Beijing Qianxiang Wangjing Technology Development Co., Ltd.
+ren
+
+// rent : 2014-12-04 DERRent, LLC
+rent
+
+// rentals : 2013-12-05 Big Hollow,LLC
+rentals
+
+// repair : 2013-11-07 Lone Sunset, LLC
+repair
+
+// report : 2013-12-05 Binky Glen, LLC
+report
+
+// republican : 2014-03-20 United TLD Holdco Ltd.
+republican
+
+// rest : 2013-12-19 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+rest
+
+// restaurant : 2014-07-03 Snow Avenue, LLC
+restaurant
+
+// review : 2014-11-20 dot Review Limited
+review
+
+// reviews : 2013-09-13
+reviews
+
+// rexroth : 2015-06-18 Robert Bosch GMBH
+rexroth
+
+// rich : 2013-11-21 I-Registry Ltd.
+rich
+
+// richardli : 2015-05-14 Pacific Century Asset Management (HK) Limited
+richardli
+
+// ricoh : 2014-11-20 Ricoh Company, Ltd.
+ricoh
+
+// rightathome : 2015-07-23 Johnson Shareholdings, Inc.
+rightathome
+
+// ril : 2015-04-02 Reliance Industries Limited
+ril
+
+// rio : 2014-02-27 Empresa Municipal de Informática SA - IPLANRIO
+rio
+
+// rip : 2014-07-10 United TLD Holdco Ltd.
+rip
+
+// rocher : 2014-12-18 Ferrero Trading Lux S.A.
+rocher
+
+// rocks : 2013-11-14
+rocks
+
+// rodeo : 2013-12-19 Top Level Domain Holdings Limited
+rodeo
+
+// room : 2014-12-18 Amazon EU S.à r.l.
+room
+
+// rsvp : 2014-05-08 Charleston Road Registry Inc.
+rsvp
+
+// ruhr : 2013-10-02 regiodot GmbH & Co. KG
+ruhr
+
+// run : 2015-03-19 Snow Park, LLC
+run
+
+// rwe : 2015-04-02 RWE AG
+rwe
+
+// ryukyu : 2014-01-09 BusinessRalliart Inc.
+ryukyu
+
+// saarland : 2013-12-12 dotSaarland GmbH
+saarland
+
+// safe : 2014-12-18 Amazon EU S.à r.l.
+safe
+
+// safety : 2015-01-08 Safety Registry Services, LLC.
+safety
+
+// sakura : 2014-12-18 SAKURA Internet Inc.
+sakura
+
+// sale : 2014-10-16
+sale
+
+// salon : 2014-12-11 Outer Orchard, LLC
+salon
+
+// 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
+
+// sapo : 2014-11-07 PT Comunicacoes S.A.
+sapo
+
+// sarl : 2014-07-03 Delta Orchard, LLC
+sarl
+
+// sas : 2015-04-02 Research IP LLC
+sas
+
+// save : 2015-06-25 Amazon EU S.à r.l.
+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
+
+// schmidt : 2014-04-03 SALM S.A.S.
+schmidt
+
+// scholarships : 2014-04-24 Scholarships.com, LLC
+scholarships
+
+// school : 2014-12-18 Little Galley, LLC
+school
+
+// schule : 2014-03-06 Outer 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
+
+// scor : 2014-10-31 SCOR SE
+scor
+
+// scot : 2014-01-23 Dot Scot Registry Limited
+scot
+
+// seat : 2014-05-22 SEAT, S.A. (Sociedad Unipersonal)
+seat
+
+// security : 2015-05-14
+security
+
+// seek : 2014-12-04 Seek Limited
+seek
+
+// sener : 2014-10-24 Sener Ingeniería y Sistemas, S.A.
+sener
+
+// services : 2014-02-27 Fox Castle, LLC
+services
+
+// ses : 2015-07-23 SES
+ses
+
+// sew : 2014-07-17 SEW-EURODRIVE GmbH & Co KG
+sew
+
+// sex : 2014-11-13 ICM Registry SX LLC
+sex
+
+// sexy : 2013-09-11 Uniregistry, Corp.
+sexy
+
+// sharp : 2014-05-01 Sharp Corporation
+sharp
+
+// shaw : 2015-04-23 Shaw Cablesystems G.P.
+shaw
+
+// 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 Galley, LLC
+shoes
+
+// shouji : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD.
+shouji
+
+// show : 2015-03-05 Snow Beach, LLC
+show
+
+// shriram : 2014-01-23 Shriram Capital Ltd.
+shriram
+
+// silk : 2015-06-25 Amazon EU S.à r.l.
+silk
+
+// sina : 2015-03-12 Sina Corporation
+sina
+
+// singles : 2013-08-27 Fern Madison, LLC
+singles
+
+// site : 2015-01-15 DotSite Inc.
+site
+
+// ski : 2015-04-09 STARTING DOT LIMITED
+ski
+
+// skin : 2015-01-15 L'Oréal
+skin
+
+// sky : 2014-06-19 Sky IP International Ltd, a company incorporated in England and Wales, operating via its registered Swiss branch
+sky
+
+// skype : 2014-12-18 Microsoft Corporation
+skype
+
+// smart : 2015-07-09 Smart Communications, Inc. (SMART)
+smart
+
+// smile : 2014-12-18 Amazon EU S.à r.l.
+smile
+
+// sncf : 2015-02-19 Société Nationale des Chemins de fer Francais S N C F
+sncf
+
+// soccer : 2015-03-26 Foggy Shadow, LLC
+soccer
+
+// social : 2013-11-07 United TLD Holdco Ltd.
+social
+
+// softbank : 2015-07-02 SoftBank Corp.
+softbank
+
+// software : 2014-03-20
+software
+
+// sohu : 2013-12-19 Sohu.com Limited
+sohu
+
+// solar : 2013-11-07 Ruby Town, LLC
+solar
+
+// solutions : 2013-11-07 Silver Cover, LLC
+solutions
+
+// song : 2015-02-26 Amazon EU S.à r.l.
+song
+
+// sony : 2015-01-08 Sony Corporation
+sony
+
+// soy : 2014-01-23 Charleston Road Registry Inc.
+soy
+
+// space : 2014-04-03 DotSpace Inc.
+space
+
+// spiegel : 2014-02-05 SPIEGEL-Verlag Rudolf Augstein GmbH & Co. KG
+spiegel
+
+// spot : 2015-02-26 Amazon EU S.à r.l.
+spot
+
+// spreadbetting : 2014-12-11 IG Group Holdings PLC
+spreadbetting
+
+// srl : 2015-05-07 mySRL GmbH
+srl
+
+// stada : 2014-11-13 STADA Arzneimittel AG
+stada
+
+// star : 2015-01-08 Star India Private Limited
+star
+
+// starhub : 2015-02-05 StarHub Limited
+starhub
+
+// statebank : 2015-03-12 STATE BANK OF INDIA
+statebank
+
+// statoil : 2014-12-04 Statoil ASA
+statoil
+
+// 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 Self Storage Company LLC
+storage
+
+// store : 2015-04-09 DotStore Inc.
+store
+
+// studio : 2015-02-11
+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 Inc.
+sucks
+
+// supersport : 2015-03-05 SuperSport International Holdings Proprietary Limited
+supersport
+
+// supplies : 2013-12-19 Atomic Fields, LLC
+supplies
+
+// supply : 2013-12-19 Half Falls, LLC
+supply
+
+// support : 2013-10-24 Grand Orchard, LLC
+support
+
+// surf : 2014-01-09 Top Level Domain Holdings Limited
+surf
+
+// surgery : 2014-03-20 Tin Avenue, 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
+
+// symantec : 2014-12-04 Symantec Corporation
+symantec
+
+// systems : 2013-11-07 Dash Cypress, LLC
+systems
+
+// tab : 2014-12-04 Tabcorp Holdings Limited
+tab
+
+// taipei : 2014-07-10 Taipei City Government
+taipei
+
+// talk : 2015-04-09 Amazon EU S.à r.l.
+talk
+
+// taobao : 2015-01-15 Alibaba Group Holding Limited
+taobao
+
+// 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 Uniregistry, Corp.
+tattoo
+
+// tax : 2014-03-20 Storm Orchard, LLC
+tax
+
+// taxi : 2015-03-19 Pine Falls, 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 Atomic Lake, LLC
+team
+
+// tech : 2015-01-30 Dot Tech LLC
+tech
+
+// technology : 2013-09-13 Auburn Falls
+technology
+
+// telecity : 2015-02-19 TelecityGroup International Limited
+telecity
+
+// telefonica : 2014-10-16 Telefónica S.A.
+telefonica
+
+// temasek : 2014-08-07 Temasek Holdings (Private) Limited
+temasek
+
+// tennis : 2014-12-04 Cotton Bloom, LLC
+tennis
+
+// teva : 2015-07-02 Teva Pharmaceutical Industries Limited
+teva
+
+// thd : 2015-04-02 Homer TLC, Inc.
+thd
+
+// theater : 2015-03-19 Blue Tigers, LLC
+theater
+
+// theatre : 2015-05-07
+theatre
+
+// theguardian : 2015-04-30 Guardian News and Media Limited
+theguardian
+
+// tiaa : 2015-07-23 Teachers Insurance and Annuity Association of America
+tiaa
+
+// tickets : 2015-02-05 Accent Media Limited
+tickets
+
+// tienda : 2013-11-14 Victor Manor, LLC
+tienda
+
+// tiffany : 2015-01-30 Tiffany and Company
+tiffany
+
+// tips : 2013-09-20 Corn Willow, LLC
+tips
+
+// tires : 2014-11-07 Dog Edge, 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 Pearl Woods, LLC
+today
+
+// tokyo : 2013-11-13 GMO Registry, Inc.
+tokyo
+
+// tools : 2013-11-21 Pioneer North, LLC
+tools
+
+// top : 2014-03-20 Jiangsu Bangning Science & Technology Co.,Ltd.
+top
+
+// toray : 2014-12-18 Toray Industries, Inc.
+toray
+
+// toshiba : 2014-04-10 TOSHIBA Corporation
+toshiba
+
+// tours : 2015-01-22 Sugar Station, LLC
+tours
+
+// town : 2014-03-06 Koko Moon, LLC
+town
+
+// toyota : 2015-04-23 TOYOTA MOTOR CORPORATION
+toyota
+
+// toys : 2014-03-06 Pioneer Orchard, LLC
+toys
+
+// trade : 2014-01-23 Elite Registry Limited
+trade
+
+// trading : 2014-12-11 IG Group Holdings PLC
+trading
+
+// training : 2013-11-07 Wild Willow, LLC
+training
+
+// 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
+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 EU S.à r.l.
+tunes
+
+// tushu : 2014-12-18 Amazon EU S.à r.l.
+tushu
+
+// tvs : 2015-02-19 T V SUNDRAM IYENGAR & SONS LIMITED
+tvs
+
+// ubs : 2014-12-11 UBS AG
+ubs
+
+// university : 2014-03-06 Little Station, LLC
+university
+
+// uno : 2013-09-11 Dot Latin LLC
+uno
+
+// uol : 2014-05-01 UBN INTERNET LTDA.
+uol
+
+// ups : 2015-06-25 UPS Market Driver, Inc.
+ups
+
+// vacations : 2013-12-05 Atomic Tigers, LLC
+vacations
+
+// vana : 2014-12-11 Lifestyle Domain Holdings, Inc.
+vana
+
+// vegas : 2014-01-16 Dot Vegas, Inc.
+vegas
+
+// ventures : 2013-08-27 Binky Lake, LLC
+ventures
+
+// versicherung : 2014-03-20 dotversicherung-registry GmbH
+versicherung
+
+// vet : 2014-03-06
+vet
+
+// viajes : 2013-10-17 Black Madison, LLC
+viajes
+
+// video : 2014-10-16
+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 New Sky, LLC
+villas
+
+// vin : 2015-06-18 Holly Shadow, LLC
+vin
+
+// vip : 2015-01-22 Minds + Machines Group Limited
+vip
+
+// virgin : 2014-09-25 Virgin Enterprises Limited
+virgin
+
+// vision : 2013-12-05 Koko Station, LLC
+vision
+
+// vista : 2014-09-18 Vistaprint Limited
+vista
+
+// vistaprint : 2014-09-18 Vistaprint Limited
+vistaprint
+
+// viva : 2014-11-07 Saudi Telecom Company
+viva
+
+// vlaanderen : 2014-02-06 DNS.be vzw
+vlaanderen
+
+// vodka : 2013-12-19 Top Level Domain Holdings Limited
+vodka
+
+// volkswagen : 2015-05-14 Volkswagen Group of America Inc.
+volkswagen
+
+// 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 Ruby House, LLC
+voyage
+
+// vuelos : 2015-03-05 Travel Reservations SRL
+vuelos
+
+// wales : 2014-05-08 Nominet UK
+wales
+
+// walter : 2014-11-13 Sandvik AB
+walter
+
+// wang : 2013-10-24 Zodiac Leo Limited
+wang
+
+// wanggou : 2014-12-18 Amazon EU S.à r.l.
+wanggou
+
+// warman : 2015-06-18 Weir Group IP Limited
+warman
+
+// watch : 2013-11-14 Sand Shadow, LLC
+watch
+
+// watches : 2014-12-22 Richemont DNS Inc.
+watches
+
+// weather : 2015-01-08 The Weather Channel, LLC
+weather
+
+// weatherchannel : 2015-03-12 The Weather Channel, LLC
+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
+
+// wed : 2013-10-01 Atgron, Inc.
+wed
+
+// wedding : 2014-04-24 Top Level Domain Holdings 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 June Station, LLC
+wine
+
+// winners : 2015-07-16 The TJX Companies, Inc.
+winners
+
+// wme : 2014-02-13 William Morris Endeavor Entertainment, LLC
+wme
+
+// woodside : 2015-07-09 Woodside Petroleum Limited
+woodside
+
+// work : 2013-12-19 Top Level Domain Holdings Limited
+work
+
+// works : 2013-11-14 Little Dynamite, LLC
+works
+
+// world : 2014-06-12 Bitter Fields, LLC
+world
+
+// wtc : 2013-12-19 World Trade Centers Association, Inc.
+wtc
+
+// wtf : 2014-03-06 Hidden Way, 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 QIHOO 360 TECHNOLOGY CO. LTD.
+xihuan
+
+// xin : 2014-12-11 Elegant Leader Limited
+xin
+
+// xn--11b4c3d : 2015-01-15 VeriSign Sarl
+कॉम
+
+// xn--1ck2e1b : 2015-02-26 Amazon EU S.à r.l.
+セール
+
+// 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
+在线
+
+// 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 Scorpio Limited
+八卦
+
+// xn--4gbrim : 2013-10-04 Suhub Electronic Establishment
+موقع
+
+// xn--55qw42g : 2013-11-08 China Organizational Name Administration Center
+公益
+
+// xn--55qx5d : 2013-11-14 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center)
+公司
+
+// 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--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 EU S.à r.l.
+ファッション
+
+// xn--c1avg : 2013-11-14 Public Interest Registry
+орг
+
+// xn--c2br7g : 2015-01-15 VeriSign Sarl
+नेट
+
+// xn--cck2b3b : 2015-02-26 Amazon EU S.à r.l.
+ストア
+
+// xn--cg4bki : 2013-09-27 SAMSUNG SDS CO., LTD
+삼성
+
+// xn--czr694b : 2014-01-16 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY.HONGKONG LIMITED
+商标
+
+// xn--czrs0t : 2013-12-19 Wild Island, LLC
+商店
+
+// xn--czru2d : 2013-11-21 Zodiac Capricorn Limited
+商城
+
+// xn--d1acj3b : 2013-11-20 The Foundation for Network Initiatives “The Smart Internet”
+дети
+
+// xn--eckvdtc9d : 2014-12-18 Amazon EU S.à r.l.
+ポイント
+
+// xn--efvy88h : 2014-08-22 Xinhua News Agency Guangdong Branch 新华通讯社广东分社
+新闻
+
+// xn--estv75g : 2015-02-19 Industrial and Commercial Bank of China Limited
+工行
+
+// xn--fct429k : 2015-04-09 Amazon EU S.à r.l.
+家電
+
+// xn--fhbei : 2015-01-15 VeriSign Sarl
+كوم
+
+// xn--fiq228c5hs : 2013-09-08 TLD REGISTRY LIMITED
+中文网
+
+// xn--fiq64b : 2013-10-14 CITIC Group Corporation
+中信
+
+// xn--fjq720a : 2014-05-22 Will Bloom, LLC
+娱乐
+
+// xn--flw351e : 2014-07-31 Charleston Road Registry Inc.
+谷歌
+
+// xn--fzys8d69uvgm : 2015-05-14 PCCW Enterprises Limited
+電訊盈科
+
+// xn--g2xx48c : 2015-01-30 Minds + Machines Group Limited
+购物
+
+// xn--gckr3f0f : 2015-02-26 Amazon EU S.à r.l.
+クラウド
+
+// xn--hxt814e : 2014-05-15 Zodiac Libra Limited
+网店
+
+// xn--i1b6b1a6a2e : 2013-11-14 Public Interest Registry
+संगठन
+
+// xn--imr513n : 2014-12-11 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG LIMITED
+餐厅
+
+// xn--io0a7i : 2013-11-14 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center)
+网络
+
+// xn--j1aef : 2015-01-15 VeriSign Sarl
+ком
+
+// xn--jlq61u9w7b : 2015-01-08 Nokia Corporation
+诺基亚
+
+// xn--jvr189m : 2015-02-26 Amazon EU S.à r.l.
+食品
+
+// xn--kcrx77d1x4a : 2014-11-07 Koninklijke Philips N.V.
+飞利浦
+
+// xn--kpu716f : 2014-12-22 Richemont DNS Inc.
+手表
+
+// 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--mgbab2bd : 2013-10-31 CORE Association
+بازار
+
+// xn--mgbb9fbpob : 2014-12-18 GreenTech Consultancy Company W.L.L.
+موبايلي
+
+// 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--nqv7f : 2013-11-14 Public Interest Registry
+机构
+
+// xn--nqv7fs00ema : 2013-11-14 Public Interest Registry
+组织机构
+
+// xn--nyqy26a : 2014-11-07 Stable Tone Limited
+健康
+
+// xn--p1acf : 2013-12-12 Rusnames Limited
+рус
+
+// xn--pbt977c : 2014-12-22 Richemont DNS Inc.
+珠宝
+
+// 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 EU S.à r.l.
+書籍
+
+// xn--ses554g : 2014-01-16
+网址
+
+// xn--t60b56a : 2015-01-15 VeriSign Sarl
+닷넷
+
+// xn--tckwe : 2015-01-15 VeriSign Sarl
+コム
+
+// xn--unup4y : 2013-07-14 Spring Fields, 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 Dash McCook, LLC
+企业
+
+// xn--vuq861b : 2014-10-16 Beijing Tele-info Network Technology Co., Ltd.
+信息
+
+// xn--w4r85el8fhu5dnra : 2015-04-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
+政务
+
+// xperia : 2015-05-14 Sony Mobile Communications AB
+xperia
+
+// xyz : 2013-12-05 XYZ.COM LLC
+xyz
+
+// yachts : 2014-01-09 DERYachts, LLC
+yachts
+
+// yahoo : 2015-04-02 Yahoo! Domain Services Inc.
+yahoo
+
+// yamaxun : 2014-12-18 Amazon EU S.à r.l.
+yamaxun
+
+// yandex : 2014-04-10 YANDEX, LLC
+yandex
+
+// yodobashi : 2014-11-20 YODOBASHI CAMERA CO.,LTD.
+yodobashi
+
+// yoga : 2014-05-29 Top Level Domain Holdings Limited
+yoga
+
+// yokohama : 2013-12-12 GMO Registry, Inc.
+yokohama
+
+// you : 2015-04-09 Amazon EU S.à r.l.
+you
+
+// youtube : 2014-05-01 Charleston Road Registry Inc.
+youtube
+
+// yun : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD.
+yun
+
+// zappos : 2015-06-25 Amazon EU S.à r.l.
+zappos
+
+// zara : 2014-11-07 Industria de Diseño Textil, S.A. (INDITEX, S.A.)
+zara
+
+// zero : 2014-12-18 Amazon EU S.à r.l.
+zero
+
+// zip : 2014-05-08 Charleston Road Registry Inc.
+zip
+
+// zippo : 2015-07-02 Zadco Company
+zippo
+
+// zone : 2013-11-14 Outer Falls, 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)
+
+// Amazon CloudFront : https://aws.amazon.com/cloudfront/
+// Submitted by Donavan Miller <donavanm@amazon.com> 2013-03-22
+cloudfront.net
+
+// Amazon Elastic Compute Cloud: https://aws.amazon.com/ec2/
+// Submitted by Osman Surkatty <osmans@amazon.com> 2014-12-16
+ap-northeast-1.compute.amazonaws.com
+ap-southeast-1.compute.amazonaws.com
+ap-southeast-2.compute.amazonaws.com
+cn-north-1.compute.amazonaws.cn
+compute.amazonaws.cn
+compute.amazonaws.com
+compute-1.amazonaws.com
+eu-west-1.compute.amazonaws.com
+eu-central-1.compute.amazonaws.com
+sa-east-1.compute.amazonaws.com
+us-east-1.amazonaws.com
+us-gov-west-1.compute.amazonaws.com
+us-west-1.compute.amazonaws.com
+us-west-2.compute.amazonaws.com
+z-1.compute-1.amazonaws.com
+z-2.compute-1.amazonaws.com
+
+// Amazon Elastic Beanstalk : https://aws.amazon.com/elasticbeanstalk/
+// Submitted by Adam Stein <astein@amazon.com> 2013-04-02
+elasticbeanstalk.com
+
+// Amazon Elastic Load Balancing : https://aws.amazon.com/elasticloadbalancing/
+// Submitted by Scott Vidmar <svidmar@amazon.com> 2013-03-27
+elb.amazonaws.com
+
+// Amazon S3 : https://aws.amazon.com/s3/
+// Submitted by Eric Kinolik <kilo@amazon.com> 2015-04-08
+s3.amazonaws.com
+s3-ap-northeast-1.amazonaws.com
+s3-ap-southeast-1.amazonaws.com
+s3-ap-southeast-2.amazonaws.com
+s3-external-1.amazonaws.com
+s3-external-2.amazonaws.com
+s3-fips-us-gov-west-1.amazonaws.com
+s3-eu-central-1.amazonaws.com
+s3-eu-west-1.amazonaws.com
+s3-sa-east-1.amazonaws.com
+s3-us-gov-west-1.amazonaws.com
+s3-us-west-1.amazonaws.com
+s3-us-west-2.amazonaws.com
+s3.cn-north-1.amazonaws.com.cn
+s3.eu-central-1.amazonaws.com
+
+// BetaInABox
+// Submitted by adrian@betainabox.com 2012-09-13
+betainabox.com
+
+// CentralNic : http://www.centralnic.com/names/domains
+// Submitted by registry <gavin.brown@centralnic.com> 2012-09-27
+ae.org
+ar.com
+br.com
+cn.com
+com.de
+com.se
+de.com
+eu.com
+gb.com
+gb.net
+hu.com
+hu.net
+jp.net
+jpn.com
+kr.com
+mex.com
+no.com
+qc.com
+ru.com
+sa.com
+se.com
+se.net
+uk.com
+uk.net
+us.com
+uy.com
+za.bz
+za.com
+
+// Africa.com Web Solutions Ltd : https://registry.africa.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+africa.com
+
+// iDOT Services Limited : http://www.domain.gr.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+gr.com
+
+// Radix FZC : http://domains.in.net
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+in.net
+
+// US REGISTRY LLC : http://us.org
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+us.org
+
+// co.com Registry, LLC : https://registry.co.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+co.com
+
+// c.la : http://www.c.la/
+c.la
+
+// cloudControl : https://www.cloudcontrol.com/
+// Submitted by Tobias Wilken <tw@cloudcontrol.com> 2013-07-23
+cloudcontrolled.com
+cloudcontrolapp.com
+
+// co.ca : http://registry.co.ca/
+co.ca
+
+// CDN77.com : http://www.cdn77.com
+// Submitted by Jan Krpes <jan.krpes@cdn77.com> 2015-07-13
+c.cdn77.org
+cdn77-ssl.net
+r.cdn77.net
+rsc.cdn77.org
+ssl.origin.cdn77-secure.org
+
+// CoDNS B.V.
+co.nl
+co.no
+
+// Commerce Guys, SAS
+// Submitted by Damien Tournoud <damien@commerceguys.com> 2015-01-22
+*.platform.sh
+
+// Cupcake : https://cupcake.io/
+// Submitted by Jonathan Rudenberg <jonathan@cupcake.io> 2013-10-08
+cupcake.is
+
+// DreamHost : http://www.dreamhost.com/
+// Submitted by Andrew Farmer <andrew.farmer@dreamhost.com> 2012-10-02
+dreamhosters.com
+
+// DuckDNS : http://www.duckdns.org/
+// Submitted by Richard Harper <richard@duckdns.org> 2015-05-17
+duckdns.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
+
+// EU.org https://eu.org/
+// Submitted by Pierre Beyssac <hostmaster@eu.org> 2015-04-17
+
+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
+
+// Fastly Inc. http://www.fastly.com/
+// Submitted by Vladimir Vuksan <vladimir@fastly.com> 2013-05-31
+a.ssl.fastly.net
+b.ssl.fastly.net
+global.ssl.fastly.net
+a.prod.fastly.net
+global.prod.fastly.net
+
+// Firebase, Inc.
+// Submitted by Chris Raynor <chris@firebase.com> 2014-01-21
+firebaseapp.com
+
+// Flynn : https://flynn.io
+// Submitted by Jonathan Rudenberg <jonathan@flynn.io> 2014-07-12
+flynnhub.com
+
+// GDS : https://www.gov.uk/service-manual/operations/operating-servicegovuk-subdomains
+// Submitted by David Illsley <david.illsley@digital.cabinet-office.gov.uk> 2014-08-28
+service.gov.uk
+
+// GitHub, Inc.
+// Submitted by Ben Toews <btoews@github.com> 2014-02-06
+github.io
+githubusercontent.com
+
+// GlobeHosting, Inc.
+// Submitted by Zoltan Egresi <egresi@globehosting.com> 2013-07-12
+ro.com
+
+// Google, Inc.
+// Submitted by Eduardo Vela <evn@google.com> 2014-12-19
+appspot.com
+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
+codespot.com
+googleapis.com
+googlecode.com
+pagespeedmobilizer.com
+withgoogle.com
+
+// Heroku : https://www.heroku.com/
+// Submitted by Tom Maher <tmaher@heroku.com> 2013-05-02
+herokuapp.com
+herokussl.com
+
+// iki.fi
+// Submitted by Hannu Aronsson <haa@iki.fi> 2009-11-05
+iki.fi
+
+// info.at : http://www.info.at/
+biz.at
+info.at
+
+// Michau Enterprises Limited : http://www.co.pl/
+co.pl
+
+// Microsoft : http://microsoft.com
+// Submitted by Barry Dorrans <bdorrans@microsoft.com> 2014-01-24
+azurewebsites.net
+azure-mobile.net
+cloudapp.net
+
+// Mozilla Foundation : https://mozilla.org/
+// Submited by glob <glob@mozilla.com> 2015-07-06
+bmoattachments.org
+
+// Neustar Inc.
+// Submitted by Trung Tran <Trung.Tran@neustar.biz> 2015-04-23
+4u.com
+
+// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/
+// Submitted by Jeff Wheelhouse <support@nearlyfreespeech.net> 2014-02-02
+nfshost.com
+
+// NYC.mn : http://www.information.nyc.mn
+// Submitted by Matthew Brown <mattbrown@nyc.mn> 2013-03-11
+nyc.mn
+
+// One Fold Media : http://www.onefoldmedia.com/
+// Submitted by Eddie Jones <eddie@onefoldmedia.com> 2014-06-10
+nid.io
+
+// Opera Software, A.S.A.
+// Submitted by Yngve Pettersen <yngve@opera.com> 2009-11-26
+operaunite.com
+
+// OutSystems
+// Submitted by Duarte Santos <domain-admin@outsystemscloud.com> 2014-03-11
+outsystemscloud.com
+
+// .pl domains (grandfathered)
+art.pl
+gliwice.pl
+krakow.pl
+poznan.pl
+wroc.pl
+zakopane.pl
+
+// priv.at : http://www.nic.priv.at/
+// Submitted by registry <lendl@nic.at> 2008-06-09
+priv.at
+
+// Red Hat, Inc. OpenShift : https://openshift.redhat.com/
+// Submitted by Tim Kramer <tkramer@rhcloud.com> 2012-10-24
+rhcloud.com
+
+// SinaAppEngine : http://sae.sina.com.cn/
+// Submitted by SinaAppEngine <saesupport@sinacloud.com> 2015-02-02
+sinaapp.com
+vipsinaapp.com
+1kapp.com
+
+// TASK geographical domains (www.task.gda.pl/uslugi/dns)
+gda.pl
+gdansk.pl
+gdynia.pl
+med.pl
+sopot.pl
+
+// UDR Limited : http://www.udr.hk.com
+// Submitted by registry <hostmaster@udr.hk.com> 2014-11-07
+hk.com
+hk.org
+ltd.hk
+inc.hk
+
+// Yola : https://www.yola.com/
+// Submitted by Stefano Rivera <stefano@yola.com> 2014-07-09
+yolasite.com
+
+// ZaNiC : http://www.za.net/
+// Submitted by registry <hostmaster@nic.za.net> 2009-10-03
+za.net
+za.org
+
+// ===END PRIVATE DOMAINS===
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
new file mode 100644
index 0000000000..72b5577749
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
@@ -0,0 +1,779 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(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(PR_GetCurrentThread() != gSocketThread);
+ 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(&ServiceWatcher::OnMsgAttach);
+ }
+
+ void Close()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
+
+ if (!gSocketTransportService) {
+ Deallocate();
+ return;
+ }
+
+ PostEvent(&ServiceWatcher::OnMsgClose);
+ }
+
+private:
+ ~ServiceWatcher() = default;
+
+ void Deallocate()
+ {
+ if (mService) {
+ DNSServiceRefDeallocate(mService);
+ mService = nullptr;
+ }
+ mOperatorHolder = nullptr;
+ }
+
+ nsresult PostEvent(void(ServiceWatcher::*func)(void))
+ {
+ return gSocketTransportService->Dispatch(NewRunnableMethod(this, func),
+ NS_DISPATCH_NORMAL);
+ }
+
+ void OnMsgClose()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+
+ if (!gSocketTransportService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &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..a932baa7cc
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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..7aa5b3759a
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp
@@ -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/. */
+
+#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)
+ : 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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)
+ : 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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)
+ : 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);
+ }
+}
+
+ResolveReplyRunnable::~ResolveReplyRunnable()
+{
+}
+
+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(PR_GetCurrentThread() == gSocketThread);
+
+ 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)
+ : mSdRef(aSdRef)
+ , mFlags(aFlags)
+ , mInterfaceIndex(aInterfaceIndex)
+ , mErrorCode(aErrorCode)
+ , mHostName(aHostName)
+ , mAddress(aAddress)
+ , mTTL(aTTL)
+ , mContext(aContext)
+{
+}
+
+GetAddrInfoReplyRunnable::~GetAddrInfoReplyRunnable()
+{
+}
+
+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(PR_GetCurrentThread() == gSocketThread);
+
+ 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..794a585f80
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 "nsIThread.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();
+
+ 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();
+
+ 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..771f9a794d
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm
@@ -0,0 +1,244 @@
+// -*- 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";
+
+this.EXPORTED_SYMBOLS = ["MulticastDNS"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Messaging.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+var log = Cu.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: type
+ };
+
+ for (let i in data) {
+ try {
+ msg[i] = data[i];
+ } catch (e) {
+ }
+ }
+
+ Messaging.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,
+
+ registerEvent: function() {
+ log("registerEvent");
+ Messaging.addListener(this.onServiceFound.bind(this), "NsdManager:ServiceFound");
+ Messaging.addListener(this.onServiceLost.bind(this), "NsdManager:ServiceLost");
+ },
+
+ unregisterEvent: function() {
+ log("unregisterEvent");
+ Messaging.removeListener("NsdManager:ServiceFound");
+ Messaging.removeListener("NsdManager:ServiceLost");
+ },
+
+ addListener: function(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: function(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: function(aServiceInfo) {
+ let listeners = this.listeners[aServiceInfo.serviceType];
+ if (listeners) {
+ for (let listener of listeners) {
+ listener.onServiceFound(aServiceInfo);
+ }
+ } else {
+ log("no listener");
+ }
+ return {};
+ },
+
+ onServiceLost: function(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 = [];
+ let enumerator = bag.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let name = enumerator.getNext().QueryInterface(Ci.nsIProperty).name;
+ let value = bag.getPropertyAsACString(name);
+ attributes.push({
+ "name": name,
+ "value": value
+ });
+ }
+
+ return attributes;
+}
+
+function MulticastDNS() {
+ this.serviceManager = new ServiceManager();
+}
+
+MulticastDNS.prototype = {
+ startDiscovery: function(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: function(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: function(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: function(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: function(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/fallback/DNSPacket.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm
new file mode 100644
index 0000000000..f0539fccb0
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm
@@ -0,0 +1,297 @@
+/* -*- 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/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['DNSPacket'];
+
+const { utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/Services.jsm');
+
+Cu.import('resource://gre/modules/DataReader.jsm');
+Cu.import('resource://gre/modules/DataWriter.jsm');
+Cu.import('resource://gre/modules/DNSRecord.jsm');
+Cu.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..f5d48731fc
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm
@@ -0,0 +1,70 @@
+/* -*- 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/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['DNSRecord'];
+
+const { utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/DataWriter.jsm');
+Cu.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) ? true : false;
+ classCode &= 0xff;
+
+ return new this({
+ name: name,
+ recordType: recordType,
+ classCode: classCode,
+ cacheFlush: 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..ba0072a50a
--- /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/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['DNSResourceRecord'];
+
+const { utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/DataReader.jsm');
+Cu.import('resource://gre/modules/DataWriter.jsm');
+Cu.import('resource://gre/modules/DNSRecord.jsm');
+Cu.import('resource://gre/modules/DNSTypes.jsm');
+
+function debug(msg) {
+ Services.console.logStringMessage('MulticastDNS: ' + msg);
+}
+
+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..8c54706390
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm
@@ -0,0 +1,100 @@
+/* -*- 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/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.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..a20c1dc320
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DataReader.jsm
@@ -0,0 +1,133 @@
+/* -*- 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/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.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..af20d65f5d
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.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..f43dfd5f8a
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
@@ -0,0 +1,875 @@
+/* -*- 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/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['MulticastDNS'];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/Timer.jsm');
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+Cu.import('resource://gre/modules/DNSPacket.jsm');
+Cu.import('resource://gre/modules/DNSRecord.jsm');
+Cu.import('resource://gre/modules/DNSResourceRecord.jsm');
+Cu.import('resource://gre/modules/DNSTypes.jsm');
+
+const NS_NETWORK_LINK_TOPIC = 'network:link-status-changed';
+
+let observerService = Cc["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+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);
+ observerService.addObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC, false);
+ this._isNetworkLinkObserverAttached = true;
+ }
+ }
+
+ _detachNetworkLinkObserver() {
+ if (this._isNetworkLinkObserverAttached) {
+ if (this._networkLinkObserverTimeout) {
+ clearTimeout(this._networkLinkObserverTimeout);
+ }
+
+ this._networkLinkObserverTimeout = setTimeout(() => {
+ DEBUG && debug('Detaching observer ' + NS_NETWORK_LINK_TOPIC);
+ observerService.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, data.length);
+ } 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 [serviceKey, 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: 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, data.length);
+ });
+
+ // 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);
+ return;
+ }
+ });
+ }
+
+ _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: serviceName,
+ serviceType: serviceType,
+ host: host,
+ address: address,
+ port: port,
+ domainName: 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: serviceInfo,
+ expireTime: 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.indexOf('%p2p') === -1 && // No WiFi Direct interfaces
+ address.indexOf(':') === -1 && // 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);
+ let propEnum = propBag.enumerator;
+ while (propEnum.hasMoreElements()) {
+ let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
+ 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..d2dca4955f
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/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/.
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' or \
+ (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['ANDROID_VERSION'] >= '16'):
+ UNIFIED_SOURCES += [
+ 'MDNSResponderOperator.cpp',
+ 'MDNSResponderReply.cpp',
+ 'nsDNSServiceDiscovery.cpp',
+ ]
+
+ LOCAL_INCLUDES += [
+ '/netwerk/base',
+ ]
+
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ LOCAL_INCLUDES += [
+ '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+ 'external/mdnsresponder/mDNSShared',
+ ]
+ ]
+
+else:
+ EXTRA_COMPONENTS += [
+ 'nsDNSServiceDiscovery.js',
+ 'nsDNSServiceDiscovery.manifest',
+ ]
+
+ EXTRA_JS_MODULES += [
+ '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',
+ 'nsMulticastDNSModule.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ 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..8ffa74b71c
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+#ifdef MOZ_WIDGET_GONK
+#include <cutils/properties.h>
+#endif // MOZ_WIDGET_GONK
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+inline void
+StartService()
+{
+#ifdef MOZ_WIDGET_GONK
+ char value[PROPERTY_VALUE_MAX] = { '\0' };
+ property_get("init.svc.mdnsd", value, "");
+
+ if (strcmp(value, "running") == 0) {
+ return;
+ }
+ property_set("ctl.start", "mdnsd");
+#endif // MOZ_WIDGET_GONK
+}
+
+inline void
+StopService()
+{
+#ifdef MOZ_WIDGET_GONK
+ char value[PROPERTY_VALUE_MAX] = { '\0' };
+ property_get("init.svc.mdnsd", value, "");
+
+ if (strcmp(value, "stopped") == 0) {
+ return;
+ }
+ property_set("ctl.stop", "mdnsd");
+#endif // MOZ_WIDGET_GONK
+}
+
+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 anonymous
+
+NS_IMPL_ISUPPORTS(nsDNSServiceDiscovery, nsIDNSServiceDiscovery)
+
+nsDNSServiceDiscovery::~nsDNSServiceDiscovery()
+{
+#ifdef MOZ_WIDGET_GONK
+ StopService();
+#endif
+}
+
+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, 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, 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..9bf2c798a8
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.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 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();
+
+ 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/nsDNSServiceDiscovery.js b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js
new file mode 100644
index 0000000000..b94f672979
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let { PlatformInfo } = ExtensionUtils;
+
+if (PlatformInfo.os == "android" && !Services.prefs.getBoolPref("network.mdns.use_js_fallback")) {
+ Cu.import("resource://gre/modules/MulticastDNSAndroid.jsm");
+} else {
+ Cu.import("resource://gre/modules/MulticastDNS.jsm");
+}
+
+const DNSSERVICEDISCOVERY_CID = Components.ID("{f9346d98-f27a-4e89-b744-493843416480}");
+const DNSSERVICEDISCOVERY_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1";
+const DNSSERVICEINFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1";
+
+function log(aMsg) {
+ dump("-*- nsDNSServiceDiscovery.js : " + aMsg + "\n");
+}
+
+function generateUuid() {
+ var uuidGenerator = Components.classes["@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: function (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: function(aServiceType) {
+ this.discoveryStarting = false;
+ this.listener.onDiscoveryStarted(aServiceType);
+
+ if (this.stopDiscovery) {
+ this.mdns.stopDiscovery(aServiceType, this);
+ }
+ },
+ onDiscoveryStopped: function(aServiceType) {
+ this.listener.onDiscoveryStopped(aServiceType);
+ },
+ onStartDiscoveryFailed: function(aServiceType, aErrorCode) {
+ log('onStartDiscoveryFailed: ' + aServiceType + ' (' + aErrorCode + ')');
+ this.discoveryStarting = false;
+ this.stopDiscovery = true;
+ this.listener.onStartDiscoveryFailed(aServiceType, aErrorCode);
+ },
+ onStopDiscoveryFailed: function(aServiceType, aErrorCode) {
+ log('onStopDiscoveryFailed: ' + aServiceType + ' (' + aErrorCode + ')');
+ this.listener.onStopDiscoveryFailed(aServiceType, aErrorCode);
+ },
+
+ /* transform types */
+ onServiceFound: function(aServiceInfo) {
+ this.listener.onServiceFound(this.makeServiceInfo(aServiceInfo));
+ },
+ onServiceLost: function(aServiceInfo) {
+ this.listener.onServiceLost(this.makeServiceInfo(aServiceInfo));
+ },
+ onServiceRegistered: function(aServiceInfo) {
+ this.registrationStarting = false;
+ this.listener.onServiceRegistered(this.makeServiceInfo(aServiceInfo));
+
+ if (this.stopRegistration) {
+ this.mdns.unregisterService(aServiceInfo, this);
+ }
+ },
+ onServiceUnregistered: function(aServiceInfo) {
+ this.listener.onServiceUnregistered(this.makeServiceInfo(aServiceInfo));
+ },
+ onServiceResolved: function(aServiceInfo) {
+ this.listener.onServiceResolved(this.makeServiceInfo(aServiceInfo));
+ },
+
+ onRegistrationFailed: function(aServiceInfo, aErrorCode) {
+ log('onRegistrationFailed: (' + aErrorCode + ')');
+ this.registrationStarting = false;
+ this.stopRegistration = true;
+ this.listener.onRegistrationFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
+ },
+ onUnregistrationFailed: function(aServiceInfo, aErrorCode) {
+ log('onUnregistrationFailed: (' + aErrorCode + ')');
+ this.listener.onUnregistrationFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
+ },
+ onResolveFailed: function(aServiceInfo, aErrorCode) {
+ log('onResolveFailed: (' + aErrorCode + ')');
+ this.listener.onResolveFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
+ }
+};
+
+function nsDNSServiceDiscovery() {
+ log("nsDNSServiceDiscovery");
+ this.mdns = new MulticastDNS();
+}
+
+nsDNSServiceDiscovery.prototype = {
+ classID: DNSSERVICEDISCOVERY_CID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIDNSServiceDiscovery]),
+
+ startDiscovery: function(aServiceType, aListener) {
+ log("startDiscovery");
+ let listener = new ListenerWrapper(aListener, this.mdns);
+ listener.discoveryStarting = true;
+ this.mdns.startDiscovery(aServiceType, listener);
+
+ return {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+ cancel: (function() {
+ if (this.discoveryStarting || this.stopDiscovery) {
+ this.stopDiscovery = true;
+ return;
+ }
+ this.mdns.stopDiscovery(aServiceType, listener);
+ }).bind(listener)
+ };
+ },
+
+ registerService: function(aServiceInfo, aListener) {
+ log("registerService");
+ let listener = new ListenerWrapper(aListener, this.mdns);
+ listener.registrationStarting = true;
+ this.mdns.registerService(aServiceInfo, listener);
+
+ return {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+ cancel: (function() {
+ if (this.registrationStarting || this.stopRegistration) {
+ this.stopRegistration = true;
+ return;
+ }
+ this.mdns.unregisterService(aServiceInfo, listener);
+ }).bind(listener)
+ };
+ },
+
+ resolveService: function(aServiceInfo, aListener) {
+ log("resolveService");
+ this.mdns.resolveService(aServiceInfo, new ListenerWrapper(aListener));
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([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/nsDNSServiceDiscovery.manifest b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.manifest
new file mode 100644
index 0000000000..c17e719f25
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.manifest
@@ -0,0 +1,3 @@
+# nsDNSServiceDiscovery.js
+component {f9346d98-f27a-4e89-b744-493843416480} nsDNSServiceDiscovery.js
+contract @mozilla.org/toolkit/components/mdnsresponder/dns-sd;1 {f9346d98-f27a-4e89-b744-493843416480}
diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp
new file mode 100644
index 0000000000..7c67b49ac7
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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)))) {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ if (NS_WARN_IF(NS_FAILED(attributes->GetEnumerator(getter_AddRefs(enumerator))))) {
+ return;
+ }
+
+ nsCOMPtr<nsIWritablePropertyBag2> newAttributes = new nsHashPropertyBag();
+
+ bool hasMoreElements;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
+ hasMoreElements) {
+ nsCOMPtr<nsISupports> element;
+ Unused <<
+ NS_WARN_IF(NS_FAILED(enumerator->GetNext(getter_AddRefs(element))));
+ nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
+ MOZ_ASSERT(property);
+
+ nsAutoString name;
+ nsCOMPtr<nsIVariant> value;
+ Unused << NS_WARN_IF(NS_FAILED(property->GetName(name)));
+ Unused << NS_WARN_IF(NS_FAILED(property->GetValue(getter_AddRefs(value))));
+ nsAutoCString valueStr;
+ Unused << NS_WARN_IF(NS_FAILED(value->GetAsACString(valueStr)));
+
+ Unused << NS_WARN_IF(NS_FAILED(newAttributes->SetPropertyAsACString(name, valueStr)));
+ }
+
+ 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..cca9c5d01e
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_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/libmdns/nsMulticastDNSModule.cpp b/netwerk/dns/mdns/libmdns/nsMulticastDNSModule.cpp
new file mode 100644
index 0000000000..22bad3bc75
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsMulticastDNSModule.cpp
@@ -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/. */
+
+#if defined(MOZ_WIDGET_COCOA) || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
+#define ENABLE_DNS_SERVICE_DISCOVERY
+#endif
+
+#include "mozilla/ModuleUtils.h"
+
+#ifdef ENABLE_DNS_SERVICE_DISCOVERY
+#include "nsDNSServiceDiscovery.h"
+#endif
+
+#include "nsDNSServiceInfo.h"
+
+#ifdef ENABLE_DNS_SERVICE_DISCOVERY
+using mozilla::net::nsDNSServiceDiscovery;
+#define DNSSERVICEDISCOVERY_CID \
+ {0x8df43d23, 0xd3f9, 0x4dd5, \
+ { 0xb9, 0x65, 0xde, 0x2c, 0xa3, 0xf6, 0xa4, 0x2c }}
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDNSServiceDiscovery, Init)
+NS_DEFINE_NAMED_CID(DNSSERVICEDISCOVERY_CID);
+#endif // ENABLE_DNS_SERVICE_DISCOVERY
+
+using mozilla::net::nsDNSServiceInfo;
+#define DNSSERVICEINFO_CID \
+ {0x14a50f2b, 0x7ff6, 0x48a5, \
+ { 0x88, 0xe3, 0x61, 0x5f, 0xd1, 0x11, 0xf5, 0xd3 }}
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDNSServiceInfo)
+NS_DEFINE_NAMED_CID(DNSSERVICEINFO_CID);
+
+static const mozilla::Module::CIDEntry knsDNSServiceDiscoveryCIDs[] = {
+#ifdef ENABLE_DNS_SERVICE_DISCOVERY
+ { &kDNSSERVICEDISCOVERY_CID, false, nullptr, nsDNSServiceDiscoveryConstructor },
+#endif
+ { &kDNSSERVICEINFO_CID, false, nullptr, nsDNSServiceInfoConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry knsDNSServiceDiscoveryContracts[] = {
+#ifdef ENABLE_DNS_SERVICE_DISCOVERY
+ { DNSSERVICEDISCOVERY_CONTRACT_ID, &kDNSSERVICEDISCOVERY_CID },
+#endif
+ { DNSSERVICEINFO_CONTRACT_ID, &kDNSSERVICEINFO_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry knsDNSServiceDiscoveryCategories[] = {
+ { nullptr }
+};
+
+static const mozilla::Module knsDNSServiceDiscoveryModule = {
+ mozilla::Module::kVersion,
+ knsDNSServiceDiscoveryCIDs,
+ knsDNSServiceDiscoveryContracts,
+ knsDNSServiceDiscoveryCategories
+};
+
+NSMODULE_DEFN(nsDNSServiceDiscoveryModule) = &knsDNSServiceDiscoveryModule;
diff --git a/netwerk/dns/mdns/moz.build b/netwerk/dns/mdns/moz.build
new file mode 100644
index 0000000000..190bb48e56
--- /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..f788d9a33c
--- /dev/null
+++ b/netwerk/dns/moz.build
@@ -0,0 +1,78 @@
+# -*- 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 += [
+ 'mdns',
+]
+
+XPIDL_SOURCES += [
+ 'nsIDNSListener.idl',
+ 'nsIDNSRecord.idl',
+ 'nsIDNSService.idl',
+ 'nsIEffectiveTLDService.idl',
+ 'nsIIDNService.idl',
+ 'nsPIDNSService.idl',
+]
+
+XPIDL_MODULE = 'necko_dns'
+
+EXPORTS.mozilla.net += [
+ 'ChildDNSService.h',
+ 'DNS.h',
+ 'DNSListenerProxy.h',
+ 'DNSRequestChild.h',
+ 'DNSRequestParent.h',
+ 'PDNSParams.h',
+]
+
+SOURCES += [
+ 'nsEffectiveTLDService.cpp', # Excluded from UNIFIED_SOURCES due to special build flags.
+ 'nsHostResolver.cpp', # Redefines LOG
+]
+
+UNIFIED_SOURCES += [
+ 'ChildDNSService.cpp',
+ 'DNS.cpp',
+ 'DNSListenerProxy.cpp',
+ 'DNSRequestChild.cpp',
+ 'DNSRequestParent.cpp',
+ 'GetAddrInfo.cpp',
+ 'nsDNSService2.cpp',
+ 'nsIDNService.cpp',
+ 'punycode.c',
+]
+
+IPDL_SOURCES = [
+ 'PDNSRequest.ipdl',
+ 'PDNSRequestParams.ipdlh',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+GENERATED_FILES = [
+ 'etld_data.inc',
+]
+etld_data = GENERATED_FILES['etld_data.inc']
+etld_data.script = 'prepare_tlds.py'
+etld_data.inputs = ['effective_tld_names.dat']
+
+# need to include etld_data.inc
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
+
+if CONFIG['ENABLE_INTL_API']:
+ DEFINES['IDNA2008'] = True
+ USE_LIBS += ['icu']
+else:
+ UNIFIED_SOURCES += [
+ 'nameprep.c',
+ ]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/dns/nameprep.c b/netwerk/dns/nameprep.c
new file mode 100644
index 0000000000..039797885d
--- /dev/null
+++ b/netwerk/dns/nameprep.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2001,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.
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "nsIDNKitInterface.h"
+
+#define UCS_MAX 0x7fffffff
+#define UNICODE_MAX 0x10ffff
+
+
+/*
+ * Load NAMEPREP compiled tables.
+ */
+#include "nameprepdata.c"
+
+/*
+ * Define mapping/checking functions for each version of the draft.
+ */
+
+#define VERSION id11
+#include "nameprep_template.c"
+#undef VERSION
+
+typedef const char *(*nameprep_mapproc)(uint32_t v);
+typedef int (*nameprep_checkproc)(uint32_t v);
+typedef idn_biditype_t (*nameprep_biditypeproc)(uint32_t v);
+
+static struct idn_nameprep {
+ char *version;
+ nameprep_mapproc map_proc;
+ nameprep_checkproc prohibited_proc;
+ nameprep_checkproc unassigned_proc;
+ nameprep_biditypeproc biditype_proc;
+} nameprep_versions[] = {
+#define MAKE_NAMEPREP_HANDLE(version, id) \
+ { version, \
+ compose_sym2(nameprep_map_, id), \
+ compose_sym2(nameprep_prohibited_, id), \
+ compose_sym2(nameprep_unassigned_, id), \
+ compose_sym2(nameprep_biditype_, id), }
+ MAKE_NAMEPREP_HANDLE("nameprep-11", id11),
+ { NULL, NULL, NULL, NULL, NULL },
+};
+
+static idn_result_t idn_nameprep_check(nameprep_checkproc proc,
+ const uint32_t *str,
+ const uint32_t **found);
+
+idn_result_t
+idn_nameprep_create(const char *version, idn_nameprep_t *handlep) {
+ idn_nameprep_t handle;
+
+ assert(handlep != NULL);
+
+ TRACE(("idn_nameprep_create(version=%-.50s)\n",
+ version == NULL ? "<NULL>" : version));
+
+ if (version == NULL)
+ version = IDN_NAMEPREP_CURRENT;
+
+ /*
+ * Lookup table for the specified version. Since the number of
+ * versions won't be large (I don't want see draft-23 or such :-),
+ * simple linear search is OK.
+ */
+ for (handle = nameprep_versions; handle->version != NULL; handle++) {
+ if (strcmp(handle->version, version) == 0) {
+ *handlep = handle;
+ return (idn_success);
+ }
+ }
+ return (idn_notfound);
+}
+
+void
+idn_nameprep_destroy(idn_nameprep_t handle) {
+ assert(handle != NULL);
+
+ TRACE(("idn_nameprep_destroy()\n"));
+
+ /* Nothing to do. */
+}
+
+idn_result_t
+idn_nameprep_map(idn_nameprep_t handle, const uint32_t *from,
+ uint32_t *to, size_t tolen) {
+ assert(handle != NULL && from != NULL && to != NULL);
+
+ TRACE(("idn_nameprep_map(ctx=%s, from=\"%s\")\n",
+ handle->version, idn__debug_ucs4xstring(from, 50)));
+
+ while (*from != '\0') {
+ uint32_t v = *from;
+ const char *mapped;
+
+ if (v > UCS_MAX) {
+ /* This cannot happen, but just in case.. */
+ return (idn_invalid_codepoint);
+ } else if (v > UNICODE_MAX) {
+ /* No mapping is possible. */
+ mapped = NULL;
+ } else {
+ /* Try mapping. */
+ mapped = (*handle->map_proc)(v);
+ }
+
+ if (mapped == NULL) {
+ /* No mapping. Just copy verbatim. */
+ if (tolen < 1)
+ return (idn_buffer_overflow);
+ *to++ = v;
+ tolen--;
+ } else {
+ const unsigned char *mappeddata;
+ size_t mappedlen;
+
+ mappeddata = (const unsigned char *)mapped + 1;
+ mappedlen = *mapped;
+
+ if (tolen < (mappedlen + 3) / 4)
+ return (idn_buffer_overflow);
+ tolen -= (mappedlen + 3) / 4;
+ while (mappedlen >= 4) {
+ *to = *mappeddata++;
+ *to |= *mappeddata++ << 8;
+ *to |= *mappeddata++ << 16;
+ *to |= *mappeddata++ << 24;
+ mappedlen -= 4;
+ to++;
+ }
+ if (mappedlen > 0) {
+ *to = *mappeddata++;
+ *to |= (mappedlen >= 2) ?
+ *mappeddata++ << 8: 0;
+ *to |= (mappedlen >= 3) ?
+ *mappeddata++ << 16: 0;
+ to++;
+ }
+ }
+ from++;
+ }
+ if (tolen == 0)
+ return (idn_buffer_overflow);
+ *to = '\0';
+ return (idn_success);
+}
+
+idn_result_t
+idn_nameprep_isprohibited(idn_nameprep_t handle, const uint32_t *str,
+ const uint32_t **found) {
+ assert(handle != NULL && str != NULL && found != NULL);
+
+ TRACE(("idn_nameprep_isprohibited(ctx=%s, str=\"%s\")\n",
+ handle->version, idn__debug_ucs4xstring(str, 50)));
+
+ return (idn_nameprep_check(handle->prohibited_proc, str, found));
+}
+
+idn_result_t
+idn_nameprep_isunassigned(idn_nameprep_t handle, const uint32_t *str,
+ const uint32_t **found) {
+ assert(handle != NULL && str != NULL && found != NULL);
+
+ TRACE(("idn_nameprep_isunassigned(handle->version, str=\"%s\")\n",
+ handle->version, idn__debug_ucs4xstring(str, 50)));
+
+ return (idn_nameprep_check(handle->unassigned_proc, str, found));
+}
+
+static idn_result_t
+idn_nameprep_check(nameprep_checkproc proc, const uint32_t *str,
+ const uint32_t **found) {
+ uint32_t v;
+
+ while (*str != '\0') {
+ v = *str;
+
+ if (v > UCS_MAX) {
+ /* This cannot happen, but just in case.. */
+ return (idn_invalid_codepoint);
+ } else if (v > UNICODE_MAX) {
+ /* It is invalid.. */
+ *found = str;
+ return (idn_success);
+ } else if ((*proc)(v)) {
+ *found = str;
+ return (idn_success);
+ }
+ str++;
+ }
+ *found = NULL;
+ return (idn_success);
+}
+
+idn_result_t
+idn_nameprep_isvalidbidi(idn_nameprep_t handle, const uint32_t *str,
+ const uint32_t **found) {
+ uint32_t v;
+ idn_biditype_t first_char;
+ idn_biditype_t last_char;
+ int found_r_al;
+
+ assert(handle != NULL && str != NULL && found != NULL);
+
+ TRACE(("idn_nameprep_isvalidbidi(ctx=%s, str=\"%s\")\n",
+ handle->version, idn__debug_ucs4xstring(str, 50)));
+
+ if (*str == '\0') {
+ *found = NULL;
+ return (idn_success);
+ }
+
+ /*
+ * check first character's type and initialize variables.
+ */
+ found_r_al = 0;
+ if (*str > UCS_MAX) {
+ /* This cannot happen, but just in case.. */
+ return (idn_invalid_codepoint);
+ } else if (*str > UNICODE_MAX) {
+ /* It is invalid.. */
+ *found = str;
+ return (idn_success);
+ }
+ first_char = last_char = (*(handle->biditype_proc))(*str);
+ if (first_char == idn_biditype_r_al) {
+ found_r_al = 1;
+ }
+ str++;
+
+ /*
+ * see whether string is valid or not.
+ */
+ while (*str != '\0') {
+ v = *str;
+
+ if (v > UCS_MAX) {
+ /* This cannot happen, but just in case.. */
+ return (idn_invalid_codepoint);
+ } else if (v > UNICODE_MAX) {
+ /* It is invalid.. */
+ *found = str;
+ return (idn_success);
+ } else {
+ last_char = (*(handle->biditype_proc))(v);
+ if (found_r_al && last_char == idn_biditype_l) {
+ *found = str;
+ return (idn_success);
+ }
+ if (first_char != idn_biditype_r_al && last_char == idn_biditype_r_al) {
+ *found = str;
+ return (idn_success);
+ }
+ if (last_char == idn_biditype_r_al) {
+ found_r_al = 1;
+ }
+ }
+ str++;
+ }
+
+ if (found_r_al) {
+ if (last_char != idn_biditype_r_al) {
+ *found = str - 1;
+ return (idn_success);
+ }
+ }
+
+ *found = NULL;
+ return (idn_success);
+}
+
+idn_result_t
+idn_nameprep_createproc(const char *parameter, void **handlep) {
+ return idn_nameprep_create(parameter, (idn_nameprep_t *)handlep);
+}
+
+void
+idn_nameprep_destroyproc(void *handle) {
+ idn_nameprep_destroy((idn_nameprep_t)handle);
+}
+
+idn_result_t
+idn_nameprep_mapproc(void *handle, const uint32_t *from,
+ uint32_t *to, size_t tolen) {
+ return idn_nameprep_map((idn_nameprep_t)handle, from, to, tolen);
+}
+
+idn_result_t
+idn_nameprep_prohibitproc(void *handle, const uint32_t *str,
+ const uint32_t **found) {
+ return idn_nameprep_isprohibited((idn_nameprep_t)handle, str, found);
+}
+
+idn_result_t
+idn_nameprep_unassignedproc(void *handle, const uint32_t *str,
+ const uint32_t **found) {
+ return idn_nameprep_isunassigned((idn_nameprep_t)handle, str, found);
+}
+
+idn_result_t
+idn_nameprep_bidiproc(void *handle, const uint32_t *str,
+ const uint32_t **found) {
+ return idn_nameprep_isvalidbidi((idn_nameprep_t)handle, str, found);
+}
diff --git a/netwerk/dns/nameprep_template.c b/netwerk/dns/nameprep_template.c
new file mode 100644
index 0000000000..7fe36518ab
--- /dev/null
+++ b/netwerk/dns/nameprep_template.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2001 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.
+ */
+
+/*
+ * Include this file once for each version of NAMEPREP.
+ * VERSION should be defined to appropriate value before inclusion.
+ */
+
+#ifndef NAMEPREP_TEMPLATE_INIT
+#define NAMEPREP_TEMPLATE_INIT
+
+/* Symbol composition. */
+#define compose_sym2(a, b) compose_sym2X(a, b)
+#define compose_sym2X(a, b) a ## b
+#define compose_sym3(a, b, c) compose_sym3X(a, b, c)
+#define compose_sym3X(a, b, c) a ## b ## c
+
+/* The table is based on "Optimized Two-Stage Table" mentioned in
+ * Unicode 3.0 page 106, extended to handle 21bit data instead of 16 bit.
+ */
+
+/* Index calculation for multi-level index tables. */
+#define IDX0(type, v) IDX_0(v, BITS1(type), BITS2(type))
+#define IDX1(type, v) IDX_1(v, BITS1(type), BITS2(type))
+#define IDX2(type, v) IDX_2(v, BITS1(type), BITS2(type))
+
+#define IDX_0(v, bits1, bits2) ((v) >> ((bits1) + (bits2)))
+#define IDX_1(v, bits1, bits2) (((v) >> (bits2)) & ((1 << (bits1)) - 1))
+#define IDX_2(v, bits1, bits2) ((v) & ((1 << (bits2)) - 1))
+
+#define BITS1(type) type ## _BITS_1
+#define BITS2(type) type ## _BITS_2
+
+#endif /* NAMEPREP_TEMPLATE_INIT */
+
+static const char *
+compose_sym2(nameprep_map_, VERSION) (uint32_t v) {
+ int idx0 = IDX0(MAP, v);
+ int idx1 = IDX1(MAP, v);
+ int idx2 = IDX2(MAP, v);
+ int offset;
+
+#define IMAP compose_sym3(nameprep_, VERSION, _map_imap)
+#define TABLE compose_sym3(nameprep_, VERSION, _map_table)
+#define DATA compose_sym3(nameprep_, VERSION, _map_data)
+ offset = TABLE[IMAP[IMAP[idx0] + idx1]].tbl[idx2];
+ if (offset == 0)
+ return (NULL); /* no mapping */
+ return (const char *)(DATA + offset);
+#undef IMAP
+#undef TABLE
+#undef DATA
+}
+
+static int
+compose_sym2(nameprep_prohibited_, VERSION) (uint32_t v) {
+ int idx0 = IDX0(PROH, v);
+ int idx1 = IDX1(PROH, v);
+ int idx2 = IDX2(PROH, v);
+ const unsigned char *bm;
+
+#define IMAP compose_sym3(nameprep_, VERSION, _prohibited_imap)
+#define BITMAP compose_sym3(nameprep_, VERSION, _prohibited_bitmap)
+ bm = BITMAP[IMAP[IMAP[idx0] + idx1]].bm;
+ return (bm[idx2 / 8] & (1 << (idx2 % 8)));
+#undef IMAP
+#undef BITMAP
+}
+
+static int
+compose_sym2(nameprep_unassigned_, VERSION) (uint32_t v) {
+ int idx0 = IDX0(UNAS, v);
+ int idx1 = IDX1(UNAS, v);
+ int idx2 = IDX2(UNAS, v);
+ const unsigned char *bm;
+
+#define IMAP compose_sym3(nameprep_, VERSION, _unassigned_imap)
+#define BITMAP compose_sym3(nameprep_, VERSION, _unassigned_bitmap)
+ bm = BITMAP[IMAP[IMAP[idx0] + idx1]].bm;
+ return (bm[idx2 / 8] & (1 << (idx2 % 8)));
+#undef IMAP
+#undef BITMAP
+}
+
+static idn_biditype_t
+compose_sym2(nameprep_biditype_, VERSION) (uint32_t v) {
+ int idx0 = IDX0(BIDI, v);
+ int idx1 = IDX1(BIDI, v);
+ int idx2 = IDX2(BIDI, v);
+ int offset;
+
+#define IMAP compose_sym3(nameprep_, VERSION, _bidi_imap)
+#define TABLE compose_sym3(nameprep_, VERSION, _bidi_table)
+#define DATA compose_sym3(nameprep_, VERSION, _bidi_data)
+ offset = TABLE[IMAP[IMAP[idx0] + idx1]].tbl[idx2];
+ return DATA[offset];
+#undef IMAP
+#undef TABLE
+#undef DATA
+}
diff --git a/netwerk/dns/nameprepdata.c b/netwerk/dns/nameprepdata.c
new file mode 100644
index 0000000000..cd7e2db0e0
--- /dev/null
+++ b/netwerk/dns/nameprepdata.c
@@ -0,0 +1,2588 @@
+/*
+ * Copyright (c) 2001,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.
+ */
+
+ /*
+ * Do not edit this file!
+ * This file is generated from NAMEPREP specification.
+ */
+
+#define MAP_BITS_0 9
+#define MAP_BITS_1 7
+#define MAP_BITS_2 5
+
+#define PROH_BITS_0 7
+#define PROH_BITS_1 7
+#define PROH_BITS_2 7
+
+#define UNAS_BITS_0 7
+#define UNAS_BITS_1 7
+#define UNAS_BITS_2 7
+
+#define BIDI_BITS_0 9
+#define BIDI_BITS_1 7
+#define BIDI_BITS_2 5
+
+
+static const unsigned short nameprep_id11_map_imap[] = {
+ 272, 400, 528, 656, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 912,
+ 1040, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 1168, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 784, 784, 784, 784, 784, 784, 784, 784,
+ 0, 0, 1, 0, 0, 2, 3, 0,
+ 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 0, 0, 0, 0, 0, 0,
+ 0, 0, 14, 15, 16, 17, 18, 19,
+ 20, 21, 0, 22, 23, 24, 25, 26,
+ 27, 28, 29, 0, 30, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 31, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 0, 0, 49, 0, 50, 0, 0,
+ 51, 52, 53, 54, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 55, 56, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 57, 58, 59, 60, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 61, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 62, 0, 0, 0, 0, 0, 0, 63,
+ 0, 64, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 65, 66, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 67, 68, 69, 70, 71, 72, 73, 74,
+ 75, 76, 77, 78, 79, 67, 68, 69,
+ 70, 80, 81, 73, 74, 82, 83, 84,
+ 85, 86, 87, 88, 89, 90, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const struct {
+ unsigned short tbl[32];
+} nameprep_id11_map_table[] = {
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
+ 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42,
+ 44, 46, 48, 50, 52, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 54,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77,
+ 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99,
+ 101, 0, 103, 105, 107, 109, 111, 113, 115, 117,
+ }},
+ {{
+ 123, 0, 126, 0, 129, 0, 132, 0, 135, 0, 138,
+ 0, 141, 0, 144, 0, 147, 0, 150, 0, 153, 0,
+ 156, 0, 159, 0, 162, 0, 165, 0, 168, 0,
+ }},
+ {{
+ 171, 0, 174, 0, 177, 0, 180, 0, 183, 0, 186,
+ 0, 189, 0, 192, 0, 195, 0, 202, 0, 205, 0,
+ 208, 0, 0, 211, 0, 214, 0, 217, 0, 220,
+ }},
+ {{
+ 0, 223, 0, 226, 0, 229, 0, 232, 0, 235, 241,
+ 0, 244, 0, 247, 0, 250, 0, 253, 0, 256, 0,
+ 259, 0, 262, 0, 265, 0, 268, 0, 271, 0,
+ }},
+ {{
+ 274, 0, 277, 0, 280, 0, 283, 0, 286, 0, 289,
+ 0, 292, 0, 295, 0, 298, 0, 301, 0, 304, 0,
+ 307, 0, 310, 312, 0, 315, 0, 318, 0, 38,
+ }},
+ {{
+ 0, 321, 324, 0, 327, 0, 330, 333, 0, 336, 339,
+ 342, 0, 0, 345, 348, 351, 354, 0, 357, 360, 0,
+ 363, 366, 369, 0, 0, 0, 372, 375, 0, 378,
+ }},
+ {{
+ 381, 0, 384, 0, 387, 0, 390, 393, 0, 396, 0,
+ 0, 399, 0, 402, 405, 0, 408, 411, 414, 0, 417,
+ 0, 420, 423, 0, 0, 0, 426, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 429, 429, 0, 432, 432, 0, 435,
+ 435, 0, 438, 0, 441, 0, 444, 0, 447, 0, 450,
+ 0, 453, 0, 456, 0, 459, 0, 0, 462, 0,
+ }},
+ {{
+ 465, 0, 468, 0, 471, 0, 474, 0, 477, 0, 480,
+ 0, 483, 0, 486, 0, 489, 496, 496, 0, 499, 0,
+ 502, 505, 508, 0, 511, 0, 514, 0, 517, 0,
+ }},
+ {{
+ 520, 0, 523, 0, 526, 0, 529, 0, 532, 0, 535,
+ 0, 538, 0, 541, 0, 544, 0, 547, 0, 550, 0,
+ 553, 0, 556, 0, 559, 0, 562, 0, 565, 0,
+ }},
+ {{
+ 568, 0, 571, 0, 574, 0, 577, 0, 580, 0, 583,
+ 0, 586, 0, 589, 0, 592, 0, 595, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 598, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 601, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 608, 0, 611, 614, 617,
+ 0, 620, 0, 623, 626, 629, 640, 643, 646, 649, 652,
+ 655, 658, 661, 598, 664, 667, 54, 670, 673, 676,
+ }},
+ {{
+ 679, 682, 0, 685, 688, 691, 694, 697, 700, 703, 706,
+ 709, 0, 0, 0, 0, 712, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 685, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 643, 661, 691, 623, 709, 694,
+ 679, 0, 723, 0, 726, 0, 729, 0, 732, 0,
+ }},
+ {{
+ 735, 0, 738, 0, 741, 0, 744, 0, 747, 0, 750,
+ 0, 753, 0, 756, 0, 664, 682, 685, 0, 661, 652,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 759, 762, 765, 768, 771, 774, 777, 780, 783, 786, 789,
+ 792, 795, 798, 801, 804, 807, 810, 813, 816, 819, 822,
+ 825, 828, 831, 834, 837, 840, 843, 846, 849, 852,
+ }},
+ {{
+ 855, 858, 861, 864, 867, 870, 873, 876, 879, 882, 885,
+ 888, 891, 894, 897, 900, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 903, 0, 906, 0, 909, 0, 912, 0, 915, 0, 918,
+ 0, 921, 0, 924, 0, 927, 0, 930, 0, 933, 0,
+ 936, 0, 939, 0, 942, 0, 945, 0, 948, 0,
+ }},
+ {{
+ 951, 0, 0, 0, 0, 0, 0, 0, 0, 0, 954,
+ 0, 957, 0, 960, 0, 963, 0, 966, 0, 969, 0,
+ 972, 0, 975, 0, 978, 0, 981, 0, 984, 0,
+ }},
+ {{
+ 987, 0, 990, 0, 993, 0, 996, 0, 999, 0, 1002,
+ 0, 1005, 0, 1008, 0, 1011, 0, 1014, 0, 1017, 0,
+ 1020, 0, 1023, 0, 1026, 0, 1029, 0, 1032, 0,
+ }},
+ {{
+ 0, 1035, 0, 1038, 0, 1041, 0, 1044, 0, 1047, 0,
+ 1050, 0, 1053, 0, 0, 1056, 0, 1059, 0, 1062, 0,
+ 1065, 0, 1068, 0, 1071, 0, 1074, 0, 1077, 0,
+ }},
+ {{
+ 1080, 0, 1083, 0, 1086, 0, 1089, 0, 1092, 0, 1095,
+ 0, 1098, 0, 1101, 0, 1104, 0, 1107, 0, 1110, 0,
+ 0, 0, 1113, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 1116, 0, 1119, 0, 1122, 0, 1125, 0, 1128, 0, 1131,
+ 0, 1134, 0, 1137, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1140, 1143, 1146, 1149, 1152,
+ 1155, 1158, 1161, 1164, 1167, 1170, 1173, 1176, 1179, 1182,
+ }},
+ {{
+ 1185, 1188, 1191, 1194, 1197, 1200, 1203, 1206, 1209, 1212, 1215,
+ 1218, 1221, 1224, 1227, 1230, 1233, 1236, 1239, 1242, 1245, 1248,
+ 1251, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 1254, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 1261, 0, 1264, 0, 1267, 0, 1270, 0, 1273, 0, 1276,
+ 0, 1279, 0, 1282, 0, 1285, 0, 1288, 0, 1291, 0,
+ 1294, 0, 1297, 0, 1300, 0, 1303, 0, 1306, 0,
+ }},
+ {{
+ 1309, 0, 1312, 0, 1315, 0, 1318, 0, 1321, 0, 1324,
+ 0, 1327, 0, 1330, 0, 1333, 0, 1336, 0, 1339, 0,
+ 1342, 0, 1345, 0, 1348, 0, 1351, 0, 1354, 0,
+ }},
+ {{
+ 1357, 0, 1360, 0, 1363, 0, 1366, 0, 1369, 0, 1372,
+ 0, 1375, 0, 1378, 0, 1381, 0, 1384, 0, 1387, 0,
+ 1390, 0, 1393, 0, 1396, 0, 1399, 0, 1402, 0,
+ }},
+ {{
+ 1405, 0, 1408, 0, 1411, 0, 1414, 0, 1417, 0, 1420,
+ 0, 1423, 0, 1426, 0, 1429, 0, 1432, 0, 1435, 0,
+ 1438, 0, 1441, 0, 1444, 0, 1447, 0, 1450, 0,
+ }},
+ {{
+ 1453, 0, 1456, 0, 1459, 0, 1462, 0, 1465, 0, 1468,
+ 0, 1471, 0, 1474, 0, 1477, 0, 1480, 0, 1483, 0,
+ 1486, 1493, 1500, 1507, 1514, 1405, 0, 0, 0, 0,
+ }},
+ {{
+ 1521, 0, 1524, 0, 1527, 0, 1530, 0, 1533, 0, 1536,
+ 0, 1539, 0, 1542, 0, 1545, 0, 1548, 0, 1551, 0,
+ 1554, 0, 1557, 0, 1560, 0, 1563, 0, 1566, 0,
+ }},
+ {{
+ 1569, 0, 1572, 0, 1575, 0, 1578, 0, 1581, 0, 1584,
+ 0, 1587, 0, 1590, 0, 1593, 0, 1596, 0, 1599, 0,
+ 1602, 0, 1605, 0, 1608, 0, 1611, 0, 1614, 0,
+ }},
+ {{
+ 1617, 0, 1620, 0, 1623, 0, 1626, 0, 1629, 0, 1632,
+ 0, 1635, 0, 1638, 0, 1641, 0, 1644, 0, 1647, 0,
+ 1650, 0, 1653, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 1656, 1659, 1662,
+ 1665, 1668, 1671, 1674, 1677, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1680, 1683, 1686, 1689, 1692, 1695, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 1698, 1701, 1704,
+ 1707, 1710, 1713, 1716, 1719, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1722, 1725, 1728, 1731, 1734, 1737, 1740, 1743,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 1746, 1749, 1752,
+ 1755, 1758, 1761, 0, 0, 1764, 0, 1771, 0, 1782, 0,
+ 1793, 0, 0, 1804, 0, 1807, 0, 1810, 0, 1813,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 1816, 1819, 1822,
+ 1825, 1828, 1831, 1834, 1837, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 1840, 1847, 1854, 1861, 1868, 1875, 1882, 1889, 1840, 1847, 1854,
+ 1861, 1868, 1875, 1882, 1889, 1896, 1903, 1910, 1917, 1924, 1931,
+ 1938, 1945, 1896, 1903, 1910, 1917, 1924, 1931, 1938, 1945,
+ }},
+ {{
+ 1952, 1959, 1966, 1973, 1980, 1987, 1994, 2001, 1952, 1959, 1966,
+ 1973, 1980, 1987, 1994, 2001, 0, 0, 2008, 2015, 2022, 0,
+ 2029, 2036, 2047, 2050, 2053, 2056, 2015, 0, 598, 0,
+ }},
+ {{
+ 0, 0, 2059, 2066, 2073, 0, 2080, 2087, 2098, 2101, 2104,
+ 2107, 2066, 0, 0, 0, 0, 0, 2110, 629, 0, 0,
+ 2121, 2128, 2139, 2142, 2145, 2148, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 2151, 712, 2162, 0, 2169, 2176, 2187, 2190, 2193,
+ 2196, 2199, 0, 0, 0, 0, 0, 2202, 2209, 2216, 0,
+ 2223, 2230, 2241, 2244, 2247, 2250, 2209, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 2253, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 6, 2259, 0, 0, 0, 351, 0, 2265, 0,
+ 16, 16, 16, 0, 0, 18, 18, 24, 0, 0, 28,
+ 2271, 0, 0, 32, 34, 36, 36, 36, 0, 0,
+ }},
+ {{
+ 2277, 2283, 2293, 0, 52, 0, 703, 0, 52, 0, 22,
+ 67, 4, 6, 0, 0, 10, 12, 0, 26, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 646, 679,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2299, 2302, 2305, 2308, 2311, 2314, 2317, 2320, 2323, 2326, 2329,
+ 2332, 2335, 2338, 2341, 2344, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2347, 2350, 2353, 2356, 2359, 2362, 2365, 2368, 2371, 2374,
+ }},
+ {{
+ 2377, 2380, 2383, 2386, 2389, 2392, 2395, 2398, 2401, 2404, 2407,
+ 2410, 2413, 2416, 2419, 2422, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2425, 0, 2435, 0, 2441,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2447, 2453, 2459, 2465, 2471, 2477, 2483, 2489, 0, 0, 2495,
+ 2501, 2507, 0, 0, 0, 2513, 2519, 2529, 2539, 2549, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2447, 2559,
+ 2569, 2579, 0, 0, 0, 0, 0, 0, 0, 2589, 2595,
+ 2601, 2607, 2613, 2607, 2619, 2625, 2631, 2637, 2643, 2637,
+ }},
+ {{
+ 2649, 2656, 0, 2663, 0, 0, 2669, 2683, 2693, 2699, 0,
+ 2705, 0, 2711, 2717, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2723, 0, 2729, 2739, 0, 2745, 2751, 0, 0,
+ }},
+ {{
+ 2757, 2763, 2769, 2775, 2785, 2795, 2795, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 2801, 2808, 2815,
+ 2822, 2829, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ }},
+ {{
+ 0, 2836, 2839, 2842, 2845, 2848, 2851, 2854, 2857, 2860, 2863,
+ 2866, 2869, 2872, 2875, 2878, 2881, 2884, 2887, 2890, 2893, 2896,
+ 2899, 2902, 2905, 2908, 2911, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2914, 2918, 2922, 2926, 2930, 2934, 2938, 2942, 2946, 2950, 2954,
+ 2958, 2962, 2966, 2970, 2974, 2978, 2982, 2986, 2990, 2994, 2998,
+ 3002, 3006, 3010, 3014, 3018, 3022, 3026, 3030, 3034, 3038,
+ }},
+ {{
+ 3042, 3046, 3050, 3054, 3058, 3062, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22,
+ 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44,
+ 46, 48, 50, 52, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4,
+ 6, 8, 10, 12, 14, 16, 18, 20, 22, 24,
+ }},
+ {{
+ 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46,
+ 48, 50, 52, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 6,
+ 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28,
+ 30, 32, 34, 36, 38, 40, 42, 44, 46, 48,
+ }},
+ {{
+ 50, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 6, 8,
+ }},
+ {{
+ 0, 0, 14, 0, 0, 20, 22, 0, 0, 28, 30,
+ 32, 34, 0, 38, 40, 42, 44, 46, 48, 50, 52,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 4, 6, 8, 10, 12,
+ 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,
+ }},
+ {{
+ 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 2, 4, 0, 8, 10, 12, 14,
+ 0, 0, 20, 22, 24, 26, 28, 30, 32, 34, 0,
+ 38, 40, 42, 44, 46, 48, 50, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 4, 0, 8, 10, 12, 14, 0,
+ }},
+ {{
+ 18, 20, 22, 24, 26, 0, 30, 0, 0, 0, 38,
+ 40, 42, 44, 46, 48, 50, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
+ 22, 24, 26, 28, 30, 32, 34, 36, 38, 40,
+ }},
+ {{
+ 42, 44, 46, 48, 50, 52, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 50, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 4, 6, 8,
+ }},
+ {{
+ 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
+ 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 640, 643, 646,
+ 649, 652, 655, 658, 661, 598, 664, 667, 54, 670, 673,
+ 676, 679, 682, 661, 685, 688, 691, 694, 697, 700,
+ }},
+ {{
+ 703, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 685, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 640, 643, 646, 649, 652, 655, 658, 661, 598,
+ 664, 667, 54, 670, 673, 676, 679, 682, 661, 685, 688,
+ 691, 694, 697, 700, 703, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 685, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 640, 643, 646, 649,
+ }},
+ {{
+ 652, 655, 658, 661, 598, 664, 667, 54, 670, 673, 676,
+ 679, 682, 661, 685, 688, 691, 694, 697, 700, 703, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 685, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 640, 643, 646, 649, 652, 655, 658, 661, 598, 664,
+ }},
+ {{
+ 667, 54, 670, 673, 676, 679, 682, 661, 685, 688, 691,
+ 694, 697, 700, 703, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 685, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 640, 643, 646, 649, 652, 655,
+ 658, 661, 598, 664, 667, 54, 670, 673, 676, 679,
+ }},
+ {{
+ 682, 661, 685, 688, 691, 694, 697, 700, 703, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 685, 0, 0, 0, 0,
+ }},
+};
+
+static const unsigned char nameprep_id11_map_data[] = {
+ 0, 0, 1, 97, 1, 98, 1, 99, 1, 100, 1, 101,
+ 1, 102, 1, 103, 1, 104, 1, 105, 1, 106, 1, 107,
+ 1, 108, 1, 109, 1, 110, 1, 111, 1, 112, 1, 113,
+ 1, 114, 1, 115, 1, 116, 1, 117, 1, 118, 1, 119,
+ 1, 120, 1, 121, 1, 122, 2, 188, 3, 1, 224, 1,
+ 225, 1, 226, 1, 227, 1, 228, 1, 229, 1, 230, 1,
+ 231, 1, 232, 1, 233, 1, 234, 1, 235, 1, 236, 1,
+ 237, 1, 238, 1, 239, 1, 240, 1, 241, 1, 242, 1,
+ 243, 1, 244, 1, 245, 1, 246, 1, 248, 1, 249, 1,
+ 250, 1, 251, 1, 252, 1, 253, 1, 254, 5, 115, 0,
+ 0, 0, 115, 2, 1, 1, 2, 3, 1, 2, 5, 1,
+ 2, 7, 1, 2, 9, 1, 2, 11, 1, 2, 13, 1,
+ 2, 15, 1, 2, 17, 1, 2, 19, 1, 2, 21, 1,
+ 2, 23, 1, 2, 25, 1, 2, 27, 1, 2, 29, 1,
+ 2, 31, 1, 2, 33, 1, 2, 35, 1, 2, 37, 1,
+ 2, 39, 1, 2, 41, 1, 2, 43, 1, 2, 45, 1,
+ 2, 47, 1, 6, 105, 0, 0, 0, 7, 3, 2, 51,
+ 1, 2, 53, 1, 2, 55, 1, 2, 58, 1, 2, 60,
+ 1, 2, 62, 1, 2, 64, 1, 2, 66, 1, 2, 68,
+ 1, 2, 70, 1, 2, 72, 1, 5, 188, 2, 0, 0,
+ 110, 2, 75, 1, 2, 77, 1, 2, 79, 1, 2, 81,
+ 1, 2, 83, 1, 2, 85, 1, 2, 87, 1, 2, 89,
+ 1, 2, 91, 1, 2, 93, 1, 2, 95, 1, 2, 97,
+ 1, 2, 99, 1, 2, 101, 1, 2, 103, 1, 2, 105,
+ 1, 2, 107, 1, 2, 109, 1, 2, 111, 1, 2, 113,
+ 1, 2, 115, 1, 2, 117, 1, 2, 119, 1, 1, 255,
+ 2, 122, 1, 2, 124, 1, 2, 126, 1, 2, 83, 2,
+ 2, 131, 1, 2, 133, 1, 2, 84, 2, 2, 136, 1,
+ 2, 86, 2, 2, 87, 2, 2, 140, 1, 2, 221, 1,
+ 2, 89, 2, 2, 91, 2, 2, 146, 1, 2, 96, 2,
+ 2, 99, 2, 2, 105, 2, 2, 104, 2, 2, 153, 1,
+ 2, 111, 2, 2, 114, 2, 2, 117, 2, 2, 161, 1,
+ 2, 163, 1, 2, 165, 1, 2, 128, 2, 2, 168, 1,
+ 2, 131, 2, 2, 173, 1, 2, 136, 2, 2, 176, 1,
+ 2, 138, 2, 2, 139, 2, 2, 180, 1, 2, 182, 1,
+ 2, 146, 2, 2, 185, 1, 2, 189, 1, 2, 198, 1,
+ 2, 201, 1, 2, 204, 1, 2, 206, 1, 2, 208, 1,
+ 2, 210, 1, 2, 212, 1, 2, 214, 1, 2, 216, 1,
+ 2, 218, 1, 2, 220, 1, 2, 223, 1, 2, 225, 1,
+ 2, 227, 1, 2, 229, 1, 2, 231, 1, 2, 233, 1,
+ 2, 235, 1, 2, 237, 1, 2, 239, 1, 6, 106, 0,
+ 0, 0, 12, 3, 2, 243, 1, 2, 245, 1, 2, 149,
+ 1, 2, 191, 1, 2, 249, 1, 2, 251, 1, 2, 253,
+ 1, 2, 255, 1, 2, 1, 2, 2, 3, 2, 2, 5,
+ 2, 2, 7, 2, 2, 9, 2, 2, 11, 2, 2, 13,
+ 2, 2, 15, 2, 2, 17, 2, 2, 19, 2, 2, 21,
+ 2, 2, 23, 2, 2, 25, 2, 2, 27, 2, 2, 29,
+ 2, 2, 31, 2, 2, 158, 1, 2, 35, 2, 2, 37,
+ 2, 2, 39, 2, 2, 41, 2, 2, 43, 2, 2, 45,
+ 2, 2, 47, 2, 2, 49, 2, 2, 51, 2, 2, 185,
+ 3, 6, 32, 0, 0, 0, 185, 3, 2, 172, 3, 2,
+ 173, 3, 2, 174, 3, 2, 175, 3, 2, 204, 3, 2,
+ 205, 3, 2, 206, 3, 10, 185, 3, 0, 0, 8, 3,
+ 0, 0, 1, 3, 2, 177, 3, 2, 178, 3, 2, 179,
+ 3, 2, 180, 3, 2, 181, 3, 2, 182, 3, 2, 183,
+ 3, 2, 184, 3, 2, 186, 3, 2, 187, 3, 2, 189,
+ 3, 2, 190, 3, 2, 191, 3, 2, 192, 3, 2, 193,
+ 3, 2, 195, 3, 2, 196, 3, 2, 197, 3, 2, 198,
+ 3, 2, 199, 3, 2, 200, 3, 2, 201, 3, 2, 202,
+ 3, 2, 203, 3, 10, 197, 3, 0, 0, 8, 3, 0,
+ 0, 1, 3, 2, 217, 3, 2, 219, 3, 2, 221, 3,
+ 2, 223, 3, 2, 225, 3, 2, 227, 3, 2, 229, 3,
+ 2, 231, 3, 2, 233, 3, 2, 235, 3, 2, 237, 3,
+ 2, 239, 3, 2, 80, 4, 2, 81, 4, 2, 82, 4,
+ 2, 83, 4, 2, 84, 4, 2, 85, 4, 2, 86, 4,
+ 2, 87, 4, 2, 88, 4, 2, 89, 4, 2, 90, 4,
+ 2, 91, 4, 2, 92, 4, 2, 93, 4, 2, 94, 4,
+ 2, 95, 4, 2, 48, 4, 2, 49, 4, 2, 50, 4,
+ 2, 51, 4, 2, 52, 4, 2, 53, 4, 2, 54, 4,
+ 2, 55, 4, 2, 56, 4, 2, 57, 4, 2, 58, 4,
+ 2, 59, 4, 2, 60, 4, 2, 61, 4, 2, 62, 4,
+ 2, 63, 4, 2, 64, 4, 2, 65, 4, 2, 66, 4,
+ 2, 67, 4, 2, 68, 4, 2, 69, 4, 2, 70, 4,
+ 2, 71, 4, 2, 72, 4, 2, 73, 4, 2, 74, 4,
+ 2, 75, 4, 2, 76, 4, 2, 77, 4, 2, 78, 4,
+ 2, 79, 4, 2, 97, 4, 2, 99, 4, 2, 101, 4,
+ 2, 103, 4, 2, 105, 4, 2, 107, 4, 2, 109, 4,
+ 2, 111, 4, 2, 113, 4, 2, 115, 4, 2, 117, 4,
+ 2, 119, 4, 2, 121, 4, 2, 123, 4, 2, 125, 4,
+ 2, 127, 4, 2, 129, 4, 2, 139, 4, 2, 141, 4,
+ 2, 143, 4, 2, 145, 4, 2, 147, 4, 2, 149, 4,
+ 2, 151, 4, 2, 153, 4, 2, 155, 4, 2, 157, 4,
+ 2, 159, 4, 2, 161, 4, 2, 163, 4, 2, 165, 4,
+ 2, 167, 4, 2, 169, 4, 2, 171, 4, 2, 173, 4,
+ 2, 175, 4, 2, 177, 4, 2, 179, 4, 2, 181, 4,
+ 2, 183, 4, 2, 185, 4, 2, 187, 4, 2, 189, 4,
+ 2, 191, 4, 2, 194, 4, 2, 196, 4, 2, 198, 4,
+ 2, 200, 4, 2, 202, 4, 2, 204, 4, 2, 206, 4,
+ 2, 209, 4, 2, 211, 4, 2, 213, 4, 2, 215, 4,
+ 2, 217, 4, 2, 219, 4, 2, 221, 4, 2, 223, 4,
+ 2, 225, 4, 2, 227, 4, 2, 229, 4, 2, 231, 4,
+ 2, 233, 4, 2, 235, 4, 2, 237, 4, 2, 239, 4,
+ 2, 241, 4, 2, 243, 4, 2, 245, 4, 2, 249, 4,
+ 2, 1, 5, 2, 3, 5, 2, 5, 5, 2, 7, 5,
+ 2, 9, 5, 2, 11, 5, 2, 13, 5, 2, 15, 5,
+ 2, 97, 5, 2, 98, 5, 2, 99, 5, 2, 100, 5,
+ 2, 101, 5, 2, 102, 5, 2, 103, 5, 2, 104, 5,
+ 2, 105, 5, 2, 106, 5, 2, 107, 5, 2, 108, 5,
+ 2, 109, 5, 2, 110, 5, 2, 111, 5, 2, 112, 5,
+ 2, 113, 5, 2, 114, 5, 2, 115, 5, 2, 116, 5,
+ 2, 117, 5, 2, 118, 5, 2, 119, 5, 2, 120, 5,
+ 2, 121, 5, 2, 122, 5, 2, 123, 5, 2, 124, 5,
+ 2, 125, 5, 2, 126, 5, 2, 127, 5, 2, 128, 5,
+ 2, 129, 5, 2, 130, 5, 2, 131, 5, 2, 132, 5,
+ 2, 133, 5, 2, 134, 5, 6, 101, 5, 0, 0, 130,
+ 5, 2, 1, 30, 2, 3, 30, 2, 5, 30, 2, 7,
+ 30, 2, 9, 30, 2, 11, 30, 2, 13, 30, 2, 15,
+ 30, 2, 17, 30, 2, 19, 30, 2, 21, 30, 2, 23,
+ 30, 2, 25, 30, 2, 27, 30, 2, 29, 30, 2, 31,
+ 30, 2, 33, 30, 2, 35, 30, 2, 37, 30, 2, 39,
+ 30, 2, 41, 30, 2, 43, 30, 2, 45, 30, 2, 47,
+ 30, 2, 49, 30, 2, 51, 30, 2, 53, 30, 2, 55,
+ 30, 2, 57, 30, 2, 59, 30, 2, 61, 30, 2, 63,
+ 30, 2, 65, 30, 2, 67, 30, 2, 69, 30, 2, 71,
+ 30, 2, 73, 30, 2, 75, 30, 2, 77, 30, 2, 79,
+ 30, 2, 81, 30, 2, 83, 30, 2, 85, 30, 2, 87,
+ 30, 2, 89, 30, 2, 91, 30, 2, 93, 30, 2, 95,
+ 30, 2, 97, 30, 2, 99, 30, 2, 101, 30, 2, 103,
+ 30, 2, 105, 30, 2, 107, 30, 2, 109, 30, 2, 111,
+ 30, 2, 113, 30, 2, 115, 30, 2, 117, 30, 2, 119,
+ 30, 2, 121, 30, 2, 123, 30, 2, 125, 30, 2, 127,
+ 30, 2, 129, 30, 2, 131, 30, 2, 133, 30, 2, 135,
+ 30, 2, 137, 30, 2, 139, 30, 2, 141, 30, 2, 143,
+ 30, 2, 145, 30, 2, 147, 30, 2, 149, 30, 6, 104,
+ 0, 0, 0, 49, 3, 6, 116, 0, 0, 0, 8, 3,
+ 6, 119, 0, 0, 0, 10, 3, 6, 121, 0, 0, 0,
+ 10, 3, 6, 97, 0, 0, 0, 190, 2, 2, 161, 30,
+ 2, 163, 30, 2, 165, 30, 2, 167, 30, 2, 169, 30,
+ 2, 171, 30, 2, 173, 30, 2, 175, 30, 2, 177, 30,
+ 2, 179, 30, 2, 181, 30, 2, 183, 30, 2, 185, 30,
+ 2, 187, 30, 2, 189, 30, 2, 191, 30, 2, 193, 30,
+ 2, 195, 30, 2, 197, 30, 2, 199, 30, 2, 201, 30,
+ 2, 203, 30, 2, 205, 30, 2, 207, 30, 2, 209, 30,
+ 2, 211, 30, 2, 213, 30, 2, 215, 30, 2, 217, 30,
+ 2, 219, 30, 2, 221, 30, 2, 223, 30, 2, 225, 30,
+ 2, 227, 30, 2, 229, 30, 2, 231, 30, 2, 233, 30,
+ 2, 235, 30, 2, 237, 30, 2, 239, 30, 2, 241, 30,
+ 2, 243, 30, 2, 245, 30, 2, 247, 30, 2, 249, 30,
+ 2, 0, 31, 2, 1, 31, 2, 2, 31, 2, 3, 31,
+ 2, 4, 31, 2, 5, 31, 2, 6, 31, 2, 7, 31,
+ 2, 16, 31, 2, 17, 31, 2, 18, 31, 2, 19, 31,
+ 2, 20, 31, 2, 21, 31, 2, 32, 31, 2, 33, 31,
+ 2, 34, 31, 2, 35, 31, 2, 36, 31, 2, 37, 31,
+ 2, 38, 31, 2, 39, 31, 2, 48, 31, 2, 49, 31,
+ 2, 50, 31, 2, 51, 31, 2, 52, 31, 2, 53, 31,
+ 2, 54, 31, 2, 55, 31, 2, 64, 31, 2, 65, 31,
+ 2, 66, 31, 2, 67, 31, 2, 68, 31, 2, 69, 31,
+ 6, 197, 3, 0, 0, 19, 3, 10, 197, 3, 0, 0,
+ 19, 3, 0, 0, 0, 3, 10, 197, 3, 0, 0, 19,
+ 3, 0, 0, 1, 3, 10, 197, 3, 0, 0, 19, 3,
+ 0, 0, 66, 3, 2, 81, 31, 2, 83, 31, 2, 85,
+ 31, 2, 87, 31, 2, 96, 31, 2, 97, 31, 2, 98,
+ 31, 2, 99, 31, 2, 100, 31, 2, 101, 31, 2, 102,
+ 31, 2, 103, 31, 6, 0, 31, 0, 0, 185, 3, 6,
+ 1, 31, 0, 0, 185, 3, 6, 2, 31, 0, 0, 185,
+ 3, 6, 3, 31, 0, 0, 185, 3, 6, 4, 31, 0,
+ 0, 185, 3, 6, 5, 31, 0, 0, 185, 3, 6, 6,
+ 31, 0, 0, 185, 3, 6, 7, 31, 0, 0, 185, 3,
+ 6, 32, 31, 0, 0, 185, 3, 6, 33, 31, 0, 0,
+ 185, 3, 6, 34, 31, 0, 0, 185, 3, 6, 35, 31,
+ 0, 0, 185, 3, 6, 36, 31, 0, 0, 185, 3, 6,
+ 37, 31, 0, 0, 185, 3, 6, 38, 31, 0, 0, 185,
+ 3, 6, 39, 31, 0, 0, 185, 3, 6, 96, 31, 0,
+ 0, 185, 3, 6, 97, 31, 0, 0, 185, 3, 6, 98,
+ 31, 0, 0, 185, 3, 6, 99, 31, 0, 0, 185, 3,
+ 6, 100, 31, 0, 0, 185, 3, 6, 101, 31, 0, 0,
+ 185, 3, 6, 102, 31, 0, 0, 185, 3, 6, 103, 31,
+ 0, 0, 185, 3, 6, 112, 31, 0, 0, 185, 3, 6,
+ 177, 3, 0, 0, 185, 3, 6, 172, 3, 0, 0, 185,
+ 3, 6, 177, 3, 0, 0, 66, 3, 10, 177, 3, 0,
+ 0, 66, 3, 0, 0, 185, 3, 2, 176, 31, 2, 177,
+ 31, 2, 112, 31, 2, 113, 31, 6, 116, 31, 0, 0,
+ 185, 3, 6, 183, 3, 0, 0, 185, 3, 6, 174, 3,
+ 0, 0, 185, 3, 6, 183, 3, 0, 0, 66, 3, 10,
+ 183, 3, 0, 0, 66, 3, 0, 0, 185, 3, 2, 114,
+ 31, 2, 115, 31, 2, 116, 31, 2, 117, 31, 10, 185,
+ 3, 0, 0, 8, 3, 0, 0, 0, 3, 6, 185, 3,
+ 0, 0, 66, 3, 10, 185, 3, 0, 0, 8, 3, 0,
+ 0, 66, 3, 2, 208, 31, 2, 209, 31, 2, 118, 31,
+ 2, 119, 31, 10, 197, 3, 0, 0, 8, 3, 0, 0,
+ 0, 3, 6, 193, 3, 0, 0, 19, 3, 6, 197, 3,
+ 0, 0, 66, 3, 10, 197, 3, 0, 0, 8, 3, 0,
+ 0, 66, 3, 2, 224, 31, 2, 225, 31, 2, 122, 31,
+ 2, 123, 31, 2, 229, 31, 6, 124, 31, 0, 0, 185,
+ 3, 6, 201, 3, 0, 0, 185, 3, 6, 206, 3, 0,
+ 0, 185, 3, 6, 201, 3, 0, 0, 66, 3, 10, 201,
+ 3, 0, 0, 66, 3, 0, 0, 185, 3, 2, 120, 31,
+ 2, 121, 31, 2, 124, 31, 2, 125, 31, 5, 114, 0,
+ 0, 0, 115, 5, 176, 0, 0, 0, 99, 5, 176, 0,
+ 0, 0, 102, 5, 110, 0, 0, 0, 111, 5, 115, 0,
+ 0, 0, 109, 9, 116, 0, 0, 0, 101, 0, 0, 0,
+ 108, 5, 116, 0, 0, 0, 109, 2, 112, 33, 2, 113,
+ 33, 2, 114, 33, 2, 115, 33, 2, 116, 33, 2, 117,
+ 33, 2, 118, 33, 2, 119, 33, 2, 120, 33, 2, 121,
+ 33, 2, 122, 33, 2, 123, 33, 2, 124, 33, 2, 125,
+ 33, 2, 126, 33, 2, 127, 33, 2, 208, 36, 2, 209,
+ 36, 2, 210, 36, 2, 211, 36, 2, 212, 36, 2, 213,
+ 36, 2, 214, 36, 2, 215, 36, 2, 216, 36, 2, 217,
+ 36, 2, 218, 36, 2, 219, 36, 2, 220, 36, 2, 221,
+ 36, 2, 222, 36, 2, 223, 36, 2, 224, 36, 2, 225,
+ 36, 2, 226, 36, 2, 227, 36, 2, 228, 36, 2, 229,
+ 36, 2, 230, 36, 2, 231, 36, 2, 232, 36, 2, 233,
+ 36, 9, 104, 0, 0, 0, 112, 0, 0, 0, 97, 5,
+ 97, 0, 0, 0, 117, 5, 111, 0, 0, 0, 118, 5,
+ 112, 0, 0, 0, 97, 5, 110, 0, 0, 0, 97, 5,
+ 188, 3, 0, 0, 97, 5, 109, 0, 0, 0, 97, 5,
+ 107, 0, 0, 0, 97, 5, 107, 0, 0, 0, 98, 5,
+ 109, 0, 0, 0, 98, 5, 103, 0, 0, 0, 98, 5,
+ 112, 0, 0, 0, 102, 5, 110, 0, 0, 0, 102, 5,
+ 188, 3, 0, 0, 102, 5, 104, 0, 0, 0, 122, 9,
+ 107, 0, 0, 0, 104, 0, 0, 0, 122, 9, 109, 0,
+ 0, 0, 104, 0, 0, 0, 122, 9, 103, 0, 0, 0,
+ 104, 0, 0, 0, 122, 9, 116, 0, 0, 0, 104, 0,
+ 0, 0, 122, 9, 107, 0, 0, 0, 112, 0, 0, 0,
+ 97, 9, 109, 0, 0, 0, 112, 0, 0, 0, 97, 9,
+ 103, 0, 0, 0, 112, 0, 0, 0, 97, 5, 112, 0,
+ 0, 0, 118, 5, 110, 0, 0, 0, 118, 5, 188, 3,
+ 0, 0, 118, 5, 109, 0, 0, 0, 118, 5, 107, 0,
+ 0, 0, 118, 5, 112, 0, 0, 0, 119, 5, 110, 0,
+ 0, 0, 119, 5, 188, 3, 0, 0, 119, 5, 109, 0,
+ 0, 0, 119, 5, 107, 0, 0, 0, 119, 6, 107, 0,
+ 0, 0, 201, 3, 6, 109, 0, 0, 0, 201, 3, 5,
+ 98, 0, 0, 0, 113, 13, 99, 0, 0, 0, 21, 34,
+ 0, 0, 107, 0, 0, 0, 103, 9, 99, 0, 0, 0,
+ 111, 0, 0, 0, 46, 5, 100, 0, 0, 0, 98, 5,
+ 103, 0, 0, 0, 121, 5, 104, 0, 0, 0, 112, 5,
+ 107, 0, 0, 0, 107, 5, 107, 0, 0, 0, 109, 5,
+ 112, 0, 0, 0, 104, 9, 112, 0, 0, 0, 112, 0,
+ 0, 0, 109, 5, 112, 0, 0, 0, 114, 5, 115, 0,
+ 0, 0, 118, 5, 119, 0, 0, 0, 98, 5, 102, 0,
+ 0, 0, 102, 5, 102, 0, 0, 0, 105, 5, 102, 0,
+ 0, 0, 108, 9, 102, 0, 0, 0, 102, 0, 0, 0,
+ 105, 9, 102, 0, 0, 0, 102, 0, 0, 0, 108, 5,
+ 115, 0, 0, 0, 116, 6, 116, 5, 0, 0, 118, 5,
+ 6, 116, 5, 0, 0, 101, 5, 6, 116, 5, 0, 0,
+ 107, 5, 6, 126, 5, 0, 0, 118, 5, 6, 116, 5,
+ 0, 0, 109, 5, 2, 65, 255, 2, 66, 255, 2, 67,
+ 255, 2, 68, 255, 2, 69, 255, 2, 70, 255, 2, 71,
+ 255, 2, 72, 255, 2, 73, 255, 2, 74, 255, 2, 75,
+ 255, 2, 76, 255, 2, 77, 255, 2, 78, 255, 2, 79,
+ 255, 2, 80, 255, 2, 81, 255, 2, 82, 255, 2, 83,
+ 255, 2, 84, 255, 2, 85, 255, 2, 86, 255, 2, 87,
+ 255, 2, 88, 255, 2, 89, 255, 2, 90, 255, 3, 40,
+ 4, 1, 3, 41, 4, 1, 3, 42, 4, 1, 3, 43,
+ 4, 1, 3, 44, 4, 1, 3, 45, 4, 1, 3, 46,
+ 4, 1, 3, 47, 4, 1, 3, 48, 4, 1, 3, 49,
+ 4, 1, 3, 50, 4, 1, 3, 51, 4, 1, 3, 52,
+ 4, 1, 3, 53, 4, 1, 3, 54, 4, 1, 3, 55,
+ 4, 1, 3, 56, 4, 1, 3, 57, 4, 1, 3, 58,
+ 4, 1, 3, 59, 4, 1, 3, 60, 4, 1, 3, 61,
+ 4, 1, 3, 62, 4, 1, 3, 63, 4, 1, 3, 64,
+ 4, 1, 3, 65, 4, 1, 3, 66, 4, 1, 3, 67,
+ 4, 1, 3, 68, 4, 1, 3, 69, 4, 1, 3, 70,
+ 4, 1, 3, 71, 4, 1, 3, 72, 4, 1, 3, 73,
+ 4, 1, 3, 74, 4, 1, 3, 75, 4, 1, 3, 76,
+ 4, 1, 3, 77, 4, 1,
+};
+
+static const unsigned short nameprep_id11_prohibited_imap[] = {
+ 68, 196, 196, 324, 196, 196, 196, 452,
+ 196, 196, 196, 580, 196, 196, 196, 580,
+ 196, 196, 196, 580, 196, 196, 196, 580,
+ 196, 196, 196, 580, 196, 196, 196, 580,
+ 196, 196, 196, 580, 196, 196, 196, 580,
+ 196, 196, 196, 580, 196, 196, 196, 580,
+ 196, 196, 196, 580, 196, 196, 196, 580,
+ 708, 196, 196, 580, 836, 836, 836, 836,
+ 836, 836, 836, 836, 0, 1, 0, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 3, 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 5, 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 8, 5, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 10,
+ 0, 0, 0, 11, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 12, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 13, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 13, 14, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9,
+};
+
+static const struct {
+ unsigned char bm[16];
+} nameprep_id11_prohibited_bitmap[] = {
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 255,255,255,255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0,
+ }},
+ {{
+ 0,128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 255,199, 0, 0, 0,255, 0, 0, 0, 0, 0,128, 14,252, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, 15,
+ }},
+ {{
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,254,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,248, 7,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,192,
+ }},
+ {{
+ 2, 0, 0, 0,255,255,255,255,255,255,255,255,255,255,255,255,
+ }},
+};
+
+static const unsigned short nameprep_id11_unassigned_imap[] = {
+ 68, 196, 324, 452, 580, 708, 708, 836,
+ 964, 964, 1092, 1220, 708, 708, 708, 1348,
+ 708, 708, 708, 1348, 708, 708, 708, 1348,
+ 708, 708, 708, 1348, 708, 708, 708, 1348,
+ 708, 708, 708, 1348, 708, 708, 708, 1348,
+ 708, 708, 708, 1348, 708, 708, 708, 1348,
+ 708, 708, 708, 1348, 708, 708, 708, 1348,
+ 1476, 708, 708, 1348, 964, 964, 964, 964,
+ 964, 964, 964, 964, 0, 0, 0, 0,
+ 1, 2, 3, 4, 0, 5, 6, 7,
+ 8, 9, 10, 11, 12, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 0, 0, 0,
+ 36, 37, 38, 39, 40, 41, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 0, 42, 43, 44, 45, 46, 47, 48,
+ 0, 0, 0, 49, 50, 51, 0, 0,
+ 52, 53, 54, 55, 0, 0, 0, 0,
+ 0, 0, 12, 12, 12, 12, 12, 12,
+ 12, 56, 0, 57, 58, 59, 60, 61,
+ 62, 63, 64, 65, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 66,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 67, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 68, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 69, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 70, 12, 71, 72, 0, 0, 73, 74,
+ 75, 76, 35, 77, 12, 12, 12, 12,
+ 12, 12, 78, 12, 79, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 0, 80, 81, 82,
+ 12, 12, 12, 12, 83, 84, 85, 0,
+ 0, 86, 0, 87, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 88, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 89, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 0, 0, 0, 0,
+ 90, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 88, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 88, 91, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12,
+};
+
+static const struct {
+ unsigned char bm[16];
+} nameprep_id11_unassigned_bitmap[] = {
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 2, 0,240,255,255,255, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0,192, 0, 0, 0, 0, 0, 0, 0,128,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, 0, 0,207,187,
+ }},
+ {{
+ 15, 40, 0, 0, 4, 0, 0, 0, 0,128, 0, 0, 0, 0,128,255,
+ }},
+ {{
+ 128, 0, 0, 0, 0, 0, 0, 0, 0,128, 0, 0, 0, 0,192,252,
+ }},
+ {{
+ 0, 0,255,255,255,255, 1, 0, 0, 0,128, 1, 1, 0, 0, 0,
+ }},
+ {{
+ 0,249, 1, 0, 4, 0, 0, 4,224,255, 0, 0, 0,248,224,255,
+ }},
+ {{
+ 255,239,255,119, 1, 0, 0,248, 0, 0,192,255, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,192, 0,128,
+ }},
+ {{
+ 0, 64, 0, 0, 0,224, 0, 0, 0,248,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0,252,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 17, 0, 0, 0, 0, 0, 0, 12, 0,192,224, 0, 0, 0,254,255,
+ }},
+ {{
+ 17, 96, 6, 0, 0, 2, 58, 44, 96,198,127, 79, 48, 0, 0,248,
+ }},
+ {{
+ 27,120, 6, 0, 0, 2,146, 44,120,198,255,161, 63, 0,224,255,
+ }},
+ {{
+ 17, 80, 4, 0, 0, 2, 18, 12, 64,196,254,255, 62, 0,255,255,
+ }},
+ {{
+ 17, 96, 6, 0, 0, 2, 50, 12,112,198, 63, 79, 60, 0,254,255,
+ }},
+ {{
+ 19, 56,194, 41,231, 56, 64, 60, 56,194,127,255,127, 0,248,255,
+ }},
+ {{
+ 17, 32, 2, 0, 0, 2, 16, 60, 32,194,159,255, 60, 0,255,255,
+ }},
+ {{
+ 19, 32, 2, 0, 0, 2, 16, 60, 32,194,159,191, 60, 0,255,255,
+ }},
+ {{
+ 19, 32, 2, 0, 0, 2, 0, 60, 48,194,127,255, 60, 0,255,255,
+ }},
+ {{
+ 19, 0,128, 3, 0, 0, 4,208,128,123,160, 0,255,255,227,255,
+ }},
+ {{
+ 1, 0, 0, 0, 0, 0, 0,120, 0, 0, 0,240,255,255,255,255,
+ }},
+ {{
+ 105,218, 15, 1, 81, 19, 0,196,160,192, 0,204,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,248, 1, 0,
+ }},
+ {{
+ 0,240, 0, 1, 0, 0, 0, 32, 0, 96,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 4, 9, 56,252, 0, 0, 0,252,255,255,255,255,
+ }},
+ {{
+ 255,255,255,255, 0, 0, 0, 0,192,255, 0, 0, 0, 0, 0,246,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,124, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0,248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,252,
+ }},
+ {{
+ 128, 0, 0, 0, 0, 0, 0, 0,128,194,128,194, 0, 0, 0, 0,
+ }},
+ {{
+ 128,194, 0, 0, 0,128,194,128,194,128,128, 0, 0,128, 0, 0,
+ }},
+ {{
+ 0,128,194,128, 0, 0, 0, 0,128, 0, 0,248, 1, 0, 0,224,
+ }},
+ {{
+ 255,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,224,255,
+ }},
+ {{
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,128,255,
+ }},
+ {{
+ 0, 0, 0,224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,254,255,
+ }},
+ {{
+ 0, 32,224,255, 0, 0,128,255, 0, 0,240,255, 0, 32,242,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,224, 0,252,255,255,
+ }},
+ {{
+ 0,128, 0,252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0,252,255,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0,240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,252,
+ }},
+ {{
+ 0, 0,192,192, 0, 0, 0, 0,192,192, 0, 85, 0, 0, 0,192,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 32, 0, 32, 0, 48, 16, 0, 0, 35,128,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,120,127,240, 3, 12, 0,
+ }},
+ {{
+ 0,128,255,255, 0, 0,252,255,255,255, 0, 0, 0,248,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 24, 0,240, 7, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 240,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,128,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0,128,255,255,255, 0,248,255,255, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,128,
+ }},
+ {{
+ 0, 0, 48, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,192,
+ }},
+ {{
+ 0,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 33, 12, 0, 0, 0, 1, 0, 0, 0, 80,184,128, 1, 0, 0, 0,
+ }},
+ {{
+ 0, 0,224, 0, 0, 0, 1,128,255,255, 0, 0, 0,240, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,240,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,192,255,255,255, 0,240,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0,128, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 31, 0, 0, 0, 0,224, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0,128, 0, 0, 0, 0, 0,255,255,255,255,255,255,255, 0, 0,
+ }},
+ {{
+ 0, 0, 0,224, 0, 0, 0, 0,240,255, 1, 0, 0, 0, 0,112,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,240, 0, 0, 0, 0, 0,128,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,128, 7,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,192, 0, 0, 0,128,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0,192,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0,192,255,255,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 0,224, 0, 0, 0, 0, 0, 0,128,255,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0,240,255,255,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0,192, 0, 0, 0, 0, 0, 0, 0,248,255,255,
+ }},
+ {{
+ 128,255, 7, 31, 0, 0,128,160, 36, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0,252,255,255,255, 7, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0,255,255, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 3, 0, 0, 0, 0, 0, 0,255, 0, 0, 0, 0, 0,224,
+ }},
+ {{
+ 0, 0,255,255,240,255, 0, 0,128, 1, 8, 0,128,240, 32, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0,128, 3, 3, 3,227,128,128,255, 1,
+ }},
+ {{
+ 0, 0, 0,128,240,255, 0, 0, 0,248,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0,192, 0, 0, 0, 0,192,255,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,192,255,
+ }},
+ {{
+ 0, 0, 0, 0,128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,192,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 32,155, 33, 0, 20, 18, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 64, 24, 32, 32, 0, 0, 0,132,160, 3, 2, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0,240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 63,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,128,255,255,255,255,255,
+ }},
+ {{
+ 0, 0, 0,192,255,255,255,255,255,255,255,255,255,255,255,255,
+ }},
+ {{
+ 253,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+};
+
+static const unsigned short nameprep_id11_bidi_imap[] = {
+ 272, 400, 528, 656, 784, 912, 912, 912,
+ 912, 1040, 1168, 912, 912, 1296, 912, 1424,
+ 1552, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1808, 1680, 1680,
+ 912, 912, 912, 912, 912, 912, 912, 912,
+ 912, 912, 1936, 1680, 1680, 1680, 1680, 2064,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 1680, 1680, 1680, 1680, 1680, 1680, 1680, 1680,
+ 912, 912, 912, 912, 912, 912, 912, 912,
+ 912, 912, 912, 912, 912, 912, 912, 2192,
+ 912, 912, 912, 912, 912, 912, 912, 912,
+ 912, 912, 912, 912, 912, 912, 912, 2192,
+ 0, 0, 1, 1, 0, 2, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 5, 6, 4, 4, 7, 8, 9,
+ 0, 0, 0, 10, 11, 12, 13, 14,
+ 4, 4, 4, 4, 15, 4, 13, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 29, 30, 31,
+ 32, 33, 0, 0, 29, 34, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 35, 36, 37, 38, 39, 40, 41, 42,
+ 43, 44, 45, 46, 47, 48, 49, 50,
+ 39, 51, 41, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 60,
+ 61, 64, 65, 60, 66, 67, 68, 69,
+ 20, 70, 71, 0, 72, 73, 74, 0,
+ 75, 76, 77, 78, 79, 80, 81, 0,
+ 4, 82, 83, 0, 0, 4, 84, 85,
+ 4, 4, 86, 4, 4, 87, 4, 88,
+ 89, 4, 90, 4, 91, 92, 93, 13,
+ 92, 4, 94, 95, 0, 4, 4, 96,
+ 20, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 97, 1, 4, 4, 98,
+ 99, 100, 101, 102, 4, 103, 104, 105,
+ 106, 4, 4, 83, 4, 107, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 4, 4, 4, 108, 4, 4, 88,
+ 109, 4, 110, 111, 4, 112, 113, 114,
+ 115, 0, 0, 116, 0, 0, 0, 0,
+ 117, 118, 119, 4, 120, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 121, 4, 122, 123, 0, 0, 0,
+ 0, 0, 0, 0, 124, 4, 4, 105,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 125, 126, 20, 4, 127, 20, 4, 128,
+ 129, 130, 4, 4, 13, 83, 0, 6,
+ 131, 4, 120, 132, 4, 98, 133, 134,
+ 4, 4, 4, 135, 4, 4, 111, 134,
+ 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, 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, 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, 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, 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, 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, 4, 4,
+ 4, 4, 4, 4, 4, 14, 0, 0,
+ 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, 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, 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, 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, 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, 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, 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, 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,
+ 4, 4, 4, 4, 4, 136, 0, 0,
+ 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, 4, 4, 4, 137, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 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, 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, 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, 120, 0, 0,
+ 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, 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, 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, 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, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 138, 4, 139, 0, 0, 0, 0,
+ 140, 141, 142, 29, 29, 143, 144, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 145, 146, 29, 147, 29, 148, 149,
+ 0, 0, 0, 150, 29, 29, 29, 151,
+ 0, 1, 1, 152, 4, 134, 153, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 134, 154, 139, 0, 0, 0, 0, 0,
+ 4, 155, 156, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 4, 4, 4, 4, 4, 4, 14,
+ 4, 157, 4, 158, 159, 160, 111, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 4, 161, 4, 162, 163, 164, 4,
+ 165, 166, 167, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 168, 4, 4,
+ 4, 4, 4, 4, 4, 4, 105, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 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, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 97, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 111, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 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, 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, 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, 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, 111,
+};
+
+static const struct {
+ unsigned char tbl[32];
+} nameprep_id11_bidi_table[] = {
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, 2, 0, 2, 0, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ }},
+ {{
+ 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
+ }},
+ {{
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2,
+ }},
+ {{
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2,
+ 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2,
+ 0, 2, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2,
+ }},
+ {{
+ 2, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 2, 0, 2,
+ }},
+ {{
+ 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2,
+ 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2,
+ 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 2, 2,
+ }},
+ {{
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 2, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
+ 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2,
+ 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2,
+ 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2,
+ }},
+ {{
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2,
+ 0, 2, 2, 0, 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 0,
+ }},
+ {{
+ 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 2, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2,
+ 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 0, 2, 0, 2, 2,
+ }},
+ {{
+ 0, 0, 0, 2, 2, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2,
+ 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 2, 2,
+ }},
+ {{
+ 0, 2, 2, 0, 0, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2,
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2,
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2,
+ }},
+ {{
+ 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
+ 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 2, 2, 0, 2, 0, 0, 2, 2, 0, 2, 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 0, 2, 2, 2, 0, 2, 0, 2, 0, 0, 2, 2, 0, 2, 2, 2, 2,
+ 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 2, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 0, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2,
+ }},
+ {{
+ 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2,
+ 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0,
+ }},
+ {{
+ 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2,
+ 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0,
+ }},
+ {{
+ 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2,
+ 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0,
+ 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ }},
+ {{
+ 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 2, 2, 2, 2, 0, 2, 2,
+ 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0,
+ }},
+ {{
+ 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ }},
+ {{
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0,
+ 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }},
+ {{
+ 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2,
+ }},
+ {{
+ 0, 0, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 0, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2,
+ }},
+ {{
+ 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2,
+ 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0,
+ }},
+ {{
+ 2, 2, 2, 2, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+ {{
+ 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ }},
+};
+
+static const unsigned char nameprep_id11_bidi_data[] = {
+ idn_biditype_others,
+ idn_biditype_r_al,
+ idn_biditype_l,
+};
+
diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp
new file mode 100644
index 0000000000..05831139ec
--- /dev/null
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -0,0 +1,1071 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 "nsICancelable.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIServiceManager.h"
+#include "nsIXPConnect.h"
+#include "nsProxyRelease.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsAutoPtr.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 "nsIOService.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsNetAddr.h"
+#include "nsProxyRelease.h"
+#include "nsIObserverService.h"
+#include "nsINetworkLinkService.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/ChildDNSService.h"
+#include "mozilla/net/DNSListenerProxy.h"
+#include "mozilla/Services.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 kPrefDnsOfflineLocalhost[] = "network.dns.offline-localhost";
+static const char kPrefDnsNotifyResolution[] = "network.dns.notifyResolution";
+
+//-----------------------------------------------------------------------------
+
+class nsDNSRecord : public nsIDNSRecord
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRECORD
+
+ explicit nsDNSRecord(nsHostRecord *hostRecord)
+ : mHostRecord(hostRecord)
+ , mIter(nullptr)
+ , mIterGenCnt(-1)
+ , mDone(false) {}
+
+private:
+ virtual ~nsDNSRecord() = default;
+
+ RefPtr<nsHostRecord> mHostRecord;
+ NetAddrElement *mIter;
+ int mIterGenCnt; // the generation count of
+ // mHostRecord->addr_info when we
+ // start iterating
+ bool mDone;
+};
+
+NS_IMPL_ISUPPORTS(nsDNSRecord, nsIDNSRecord)
+
+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);
+
+ // if the record is for an IP address literal, then the canonical
+ // host name is the IP address literal.
+ const char *cname;
+ {
+ MutexAutoLock lock(mHostRecord->addr_info_lock);
+ if (mHostRecord->addr_info)
+ cname = mHostRecord->addr_info->mCanonicalName ?
+ mHostRecord->addr_info->mCanonicalName :
+ mHostRecord->addr_info->mHostName;
+ else
+ cname = mHostRecord->host;
+ result.Assign(cname);
+ }
+ 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 = nullptr;
+ mIterGenCnt = mHostRecord->addr_info_gencnt;
+ }
+
+ bool startedFresh = !mIter;
+
+ do {
+ if (!mIter) {
+ mIter = mHostRecord->addr_info->mAddresses.getFirst();
+ } else {
+ mIter = mIter->getNext();
+ }
+ }
+ while (mIter && mHostRecord->Blacklisted(&mIter->mAddress));
+
+ if (!mIter && startedFresh) {
+ // If everything was blacklisted we want to reset the blacklist (and
+ // likely relearn it) and return the first address. That is better
+ // than nothing.
+ mHostRecord->ResetBlacklist();
+ mIter = mHostRecord->addr_info->mAddresses.getFirst();
+ }
+
+ if (mIter) {
+ memcpy(addr, &mIter->mAddress, sizeof(NetAddr));
+ }
+
+ mHostRecord->addr_info_lock.Unlock();
+
+ if (!mIter) {
+ mDone = true;
+ 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, 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 (NetAddrElement *iter = mHostRecord->addr_info->mAddresses.getFirst();
+ iter; iter = iter->getNext()) {
+ if (mHostRecord->Blacklisted(&iter->mAddress)) {
+ continue;
+ }
+ NetAddr *addr = aAddressArray.AppendElement(NetAddr());
+ memcpy(addr, &iter->mAddress, sizeof(NetAddr));
+ 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, 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;
+
+ NS_ADDREF(*result = new nsNetAddr(&addr));
+
+ 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 (NetAddrToString(&addr, 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;
+ }
+
+ NetAddrElement *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 = nullptr;
+ mIterGenCnt = -1;
+ mDone = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::ReportUnusable(uint16_t aPort)
+{
+ // right now we don't use the port in the blacklist
+
+ 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 &&
+ mIter) {
+ mHostRecord->ReportUnusable(&mIter->mAddress);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+class nsDNSAsyncRequest final : public nsResolveHostCallback
+ , public nsICancelable
+{
+ ~nsDNSAsyncRequest() = default;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ nsDNSAsyncRequest(nsHostResolver *res,
+ const nsACString &host,
+ nsIDNSListener *listener,
+ uint16_t flags,
+ uint16_t af,
+ const nsACString &netInterface)
+ : mResolver(res)
+ , mHost(host)
+ , mListener(listener)
+ , mFlags(flags)
+ , mAF(af)
+ , mNetworkInterface(netInterface) {}
+
+ void OnLookupComplete(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
+ nsCOMPtr<nsIDNSListener> mListener;
+ uint16_t mFlags;
+ uint16_t mAF;
+ nsCString mNetworkInterface;
+};
+
+void
+nsDNSAsyncRequest::OnLookupComplete(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)) {
+ NS_ASSERTION(hostRecord, "no host record");
+ rec = new nsDNSRecord(hostRecord);
+ }
+
+ mListener->OnLookupComplete(this, rec, status);
+ mListener = nullptr;
+
+ // release the reference to ourselves that was added before we were
+ // handed off to the host resolver.
+ NS_RELEASE_THIS();
+}
+
+bool
+nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
+{
+ nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(mListener);
+ if (wrapper) {
+ nsCOMPtr<nsIDNSListener> originalListener;
+ wrapper->GetOriginalListener(getter_AddRefs(originalListener));
+ return aListener == originalListener;
+ }
+ return (aListener == mListener);
+}
+
+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_IMPL_ISUPPORTS(nsDNSAsyncRequest, nsICancelable)
+
+NS_IMETHODIMP
+nsDNSAsyncRequest::Cancel(nsresult reason)
+{
+ NS_ENSURE_ARG(NS_FAILED(reason));
+ mResolver->DetachCallback(mHost.get(), mFlags, mAF, mNetworkInterface.get(),
+ this, reason);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+class nsDNSSyncRequest : public nsResolveHostCallback
+{
+public:
+ explicit nsDNSSyncRequest(PRMonitor *mon)
+ : mDone(false)
+ , mStatus(NS_OK)
+ , mMonitor(mon) {}
+ virtual ~nsDNSSyncRequest() = default;
+
+ void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult) override;
+ bool EqualsAsyncListener(nsIDNSListener *aListener) override;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override;
+
+ bool mDone;
+ nsresult mStatus;
+ RefPtr<nsHostRecord> mHostRecord;
+
+private:
+ PRMonitor *mMonitor;
+};
+
+void
+nsDNSSyncRequest::OnLookupComplete(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)
+ : 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)
+ , mFirstTime(true)
+ , mNotifyResolution(false)
+ , mOfflineLocalhost(false)
+{
+}
+
+nsDNSService::~nsDNSService() = default;
+
+NS_IMPL_ISUPPORTS(nsDNSService, nsIDNSService, nsPIDNSService, nsIObserver,
+ nsIMemoryReporter)
+
+/******************************************************************************
+ * nsDNSService impl:
+ * singleton instance ctor/dtor methods
+ ******************************************************************************/
+static nsDNSService *gDNSService;
+
+nsIDNSService*
+nsDNSService::GetXPCOMSingleton()
+{
+ if (IsNeckoChild()) {
+ return ChildDNSService::GetSingleton();
+ }
+
+ return GetSingleton();
+}
+
+nsDNSService*
+nsDNSService::GetSingleton()
+{
+ NS_ASSERTION(!IsNeckoChild(), "not a parent process");
+
+ if (gDNSService) {
+ NS_ADDREF(gDNSService);
+ return gDNSService;
+ }
+
+ gDNSService = new nsDNSService();
+ if (gDNSService) {
+ NS_ADDREF(gDNSService);
+ if (NS_FAILED(gDNSService->Init())) {
+ NS_RELEASE(gDNSService);
+ }
+ }
+
+ return gDNSService;
+}
+
+NS_IMETHODIMP
+nsDNSService::Init()
+{
+ if (mResolver)
+ return NS_OK;
+ NS_ENSURE_TRUE(!mResolver, NS_ERROR_ALREADY_INITIALIZED);
+ // prefs
+ uint32_t maxCacheEntries = 400;
+ uint32_t defaultCacheLifetime = 120; // seconds
+ uint32_t defaultGracePeriod = 60; // seconds
+ bool disableIPv6 = false;
+ bool offlineLocalhost = true;
+ bool disablePrefetch = false;
+ bool blockDotOnion = true;
+ int proxyType = nsIProtocolProxyService::PROXYCONFIG_DIRECT;
+ bool notifyResolution = false;
+
+ nsAdoptingCString ipv4OnlyDomains;
+ nsAdoptingCString localDomains;
+
+ // read prefs
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ int32_t val;
+ if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheEntries, &val)))
+ maxCacheEntries = (uint32_t) val;
+ if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheExpiration, &val)))
+ defaultCacheLifetime = val;
+ if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheGrace, &val)))
+ defaultGracePeriod = val;
+
+ // ASSUMPTION: pref branch does not modify out params on failure
+ prefs->GetBoolPref(kPrefDisableIPv6, &disableIPv6);
+ prefs->GetCharPref(kPrefIPv4OnlyDomains, getter_Copies(ipv4OnlyDomains));
+ prefs->GetCharPref(kPrefDnsLocalDomains, getter_Copies(localDomains));
+ prefs->GetBoolPref(kPrefDnsOfflineLocalhost, &offlineLocalhost);
+ prefs->GetBoolPref(kPrefDisablePrefetch, &disablePrefetch);
+ prefs->GetBoolPref(kPrefBlockDotOnion, &blockDotOnion);
+
+ // If a manual proxy is in use, disable prefetch implicitly
+ prefs->GetIntPref("network.proxy.type", &proxyType);
+ prefs->GetBoolPref(kPrefDnsNotifyResolution, &notifyResolution);
+
+ if (mFirstTime) {
+ mFirstTime = false;
+
+ // 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(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
+ // If a manual proxy is in use, disable prefetch implicitly
+ prefs->AddObserver("network.proxy.type", this, false);
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "last-pb-context-exited", false);
+ observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ }
+
+ nsDNSPrefetch::Initialize(this);
+
+ nsCOMPtr<nsIIDNService> idn = do_GetService(NS_IDNSERVICE_CONTRACTID);
+
+ RefPtr<nsHostResolver> res;
+ nsresult rv = nsHostResolver::Create(maxCacheEntries,
+ defaultCacheLifetime,
+ defaultGracePeriod,
+ getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv)) {
+ // now, set all of our member variables while holding the lock
+ MutexAutoLock lock(mLock);
+ mResolver = res;
+ mIDN = idn;
+ mIPv4OnlyDomains = ipv4OnlyDomains; // exchanges buffer ownership
+ mOfflineLocalhost = offlineLocalhost;
+ mDisableIPv6 = disableIPv6;
+ mBlockDotOnion = blockDotOnion;
+
+ // Disable prefetching either by explicit preference or if a manual proxy is configured
+ mDisablePrefetch = disablePrefetch || (proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL);
+
+ mLocalDomains.Clear();
+ if (localDomains) {
+ nsCCharSeparatedTokenizer tokenizer(localDomains, ',',
+ nsCCharSeparatedTokenizer::SEPARATOR_OPTIONAL);
+
+ while (tokenizer.hasMoreTokens()) {
+ mLocalDomains.PutEntry(tokenizer.nextToken());
+ }
+ }
+ mNotifyResolution = notifyResolution;
+ }
+
+ RegisterWeakMemoryReporter(this);
+
+ return rv;
+}
+
+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");
+ }
+
+ 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;
+}
+
+nsresult
+nsDNSService::PreprocessHostname(bool aLocalDomain,
+ const nsACString &aInput,
+ nsIIDNService *aIDN,
+ nsACString &aACE)
+{
+ // Enforce RFC 7686
+ if (mBlockDotOnion &&
+ StringEndsWith(aInput, NS_LITERAL_CSTRING(".onion"))) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (aLocalDomain) {
+ aACE.AssignLiteral("localhost");
+ 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;
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolve(const nsACString &aHostname,
+ uint32_t flags,
+ nsIDNSListener *listener,
+ nsIEventTarget *target_,
+ nsICancelable **result)
+{
+ return AsyncResolveExtended(aHostname, flags, EmptyCString(), listener, target_,
+ result);
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolveExtended(const nsACString &aHostname,
+ uint32_t flags,
+ const nsACString &aNetworkInterface,
+ nsIDNSListener *listener,
+ nsIEventTarget *target_,
+ nsICancelable **result)
+{
+ // grab reference to global host resolver and IDN service. beware
+ // simultaneous shutdown!!
+ RefPtr<nsHostResolver> res;
+ nsCOMPtr<nsIIDNService> idn;
+ nsCOMPtr<nsIEventTarget> target = target_;
+ 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;
+
+ 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) {
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ target = do_QueryInterface(mainThread);
+ }
+
+ if (target) {
+ listener = new DNSListenerProxy(listener, target);
+ }
+
+ uint16_t af = GetAFForLookup(hostname, flags);
+
+ auto *req =
+ new nsDNSAsyncRequest(res, hostname, listener, flags, af,
+ aNetworkInterface);
+ if (!req)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = req);
+
+ // addref for resolver; will be released when OnLookupComplete is called.
+ NS_ADDREF(req);
+ rv = res->ResolveHost(req->mHost.get(), flags, af,
+ req->mNetworkInterface.get(), req);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(req);
+ NS_RELEASE(*result);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolve(const nsACString &aHostname,
+ uint32_t aFlags,
+ nsIDNSListener *aListener,
+ nsresult aReason)
+{
+ return CancelAsyncResolveExtended(aHostname, aFlags, EmptyCString(), aListener,
+ aReason);
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolveExtended(const nsACString &aHostname,
+ uint32_t aFlags,
+ const nsACString &aNetworkInterface,
+ nsIDNSListener *aListener,
+ nsresult aReason)
+{
+ // 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 = GetAFForLookup(hostname, aFlags);
+
+ res->CancelAsyncRequest(hostname.get(), aFlags, af,
+ nsPromiseFlatCString(aNetworkInterface).get(), aListener,
+ aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::Resolve(const nsACString &aHostname,
+ uint32_t flags,
+ 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;
+ }
+
+ //
+ // 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);
+ nsDNSSyncRequest syncReq(mon);
+
+ uint16_t af = GetAFForLookup(hostname, flags);
+
+ rv = res->ResolveHost(hostname.get(), 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");
+ auto *rec = new nsDNSRecord(syncReq.mHostRecord);
+ if (!rec)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ else
+ NS_ADDREF(*result = rec);
+ }
+ }
+
+ 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)
+{
+ // We are only getting called if a preference has changed or there's a
+ // network link event.
+ NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0 ||
+ strcmp(topic, "last-pb-context-exited") == 0 ||
+ strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0,
+ "unexpected observe call");
+
+ 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;
+ }
+ if (flushCache) {
+ mResolver->FlushCache();
+ return NS_OK;
+ }
+
+ //
+ // Shutdown and this function are both only called on the UI thread, so we don't
+ // have to worry about mResolver being cleared out from under us.
+ //
+ // NOTE Shutting down and reinitializing the service like this is obviously
+ // suboptimal if Observe gets called several times in a row, but we don't
+ // expect that to be the case.
+ //
+
+ if (mResolver) {
+ Shutdown();
+ }
+ Init();
+ 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;
+}
+
+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);
+ 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;
+}
+
diff --git a/netwerk/dns/nsDNSService2.h b/netwerk/dns/nsDNSService2.h
new file mode 100644
index 0000000000..79454b901b
--- /dev/null
+++ b/netwerk/dns/nsDNSService2.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 "nsPIDNSService.h"
+#include "nsIIDNService.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsHostResolver.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Attributes.h"
+
+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 nsIDNSService* GetXPCOMSingleton();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ bool GetOffline() const;
+
+private:
+ ~nsDNSService();
+
+ static nsDNSService* GetSingleton();
+
+ uint16_t GetAFForLookup(const nsACString &host, uint32_t flags);
+
+ nsresult PreprocessHostname(bool aLocalDomain,
+ const nsACString &aInput,
+ nsIIDNService *aIDN,
+ nsACString &aACE);
+
+ RefPtr<nsHostResolver> mResolver;
+ nsCOMPtr<nsIIDNService> mIDN;
+
+ // mLock protects access to mResolver and mIPv4OnlyDomains
+ 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.
+ nsAdoptingCString mIPv4OnlyDomains;
+ bool mDisableIPv6;
+ bool mDisablePrefetch;
+ bool mBlockDotOnion;
+ bool mFirstTime;
+ bool mNotifyResolution;
+ bool mOfflineLocalhost;
+ nsTHashtable<nsCStringHashKey> mLocalDomains;
+};
+
+#endif //nsDNSService2_h__
diff --git a/netwerk/dns/nsEffectiveTLDService.cpp b/netwerk/dns/nsEffectiveTLDService.cpp
new file mode 100644
index 0000000000..4dab2178fa
--- /dev/null
+++ b/netwerk/dns/nsEffectiveTLDService.cpp
@@ -0,0 +1,368 @@
+/* -*- 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/MemoryReporting.h"
+
+#include "nsEffectiveTLDService.h"
+#include "nsIIDNService.h"
+#include "nsNetUtil.h"
+#include "prnetdb.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsEffectiveTLDService, nsIEffectiveTLDService,
+ nsIMemoryReporter)
+
+// ----------------------------------------------------------------------
+
+#define ETLD_STR_NUM_1(line) str##line
+#define ETLD_STR_NUM(line) ETLD_STR_NUM_1(line)
+#define ETLD_ENTRY_OFFSET(name) offsetof(struct etld_string_list, ETLD_STR_NUM(__LINE__))
+
+const ETLDEntry ETLDEntry::entries[] = {
+#define ETLD_ENTRY(name, ex, wild) { ETLD_ENTRY_OFFSET(name), ex, wild },
+#include "etld_data.inc"
+#undef ETLD_ENTRY
+};
+
+const union ETLDEntry::etld_strings ETLDEntry::strings = {
+ {
+#define ETLD_ENTRY(name, ex, wild) name,
+#include "etld_data.inc"
+#undef ETLD_ENTRY
+ }
+};
+
+/* static */ const ETLDEntry*
+ETLDEntry::GetEntry(const char* aDomain)
+{
+ size_t i;
+ if (BinarySearchIf(entries, 0, ArrayLength(ETLDEntry::entries),
+ Cmp(aDomain), &i)) {
+ return &entries[i];
+ }
+ return nullptr;
+}
+
+// Dummy function to statically ensure that our indices don't overflow
+// the storage provided for them.
+void
+ETLDEntry::FuncForStaticAsserts(void)
+{
+#define ETLD_ENTRY(name, ex, wild) \
+ static_assert(ETLD_ENTRY_OFFSET(name) < (1 << ETLD_ENTRY_N_INDEX_BITS), \
+ "invalid strtab index");
+#include "etld_data.inc"
+#undef ETLD_ENTRY
+}
+
+#undef ETLD_ENTRY_OFFSET
+#undef ETLD_STR_NUM
+#undef ETLD_STR_NUM1
+
+// ----------------------------------------------------------------------
+
+static nsEffectiveTLDService *gService = nullptr;
+
+nsEffectiveTLDService::nsEffectiveTLDService()
+{
+}
+
+nsresult
+nsEffectiveTLDService::Init()
+{
+ nsresult rv;
+ mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef DEBUG
+ // Sanity-check the eTLD entries.
+ for (uint32_t i = 0; i < ArrayLength(ETLDEntry::entries); i++) {
+ const char* domain = ETLDEntry::entries[i].GetEffectiveTLDName();
+ nsDependentCString name(domain);
+ nsAutoCString normalizedName(domain);
+ MOZ_ASSERT(NS_SUCCEEDED(NormalizeHostname(normalizedName)),
+ "normalization failure!");
+ MOZ_ASSERT(name.Equals(normalizedName), "domain not normalized!");
+
+ // Domains must be in sorted order for binary search to work.
+ if (i > 0) {
+ const char* domain0 = ETLDEntry::entries[i - 1].GetEffectiveTLDName();
+ MOZ_ASSERT(strcmp(domain0, domain) < 0, "domains not in sorted order!");
+ }
+ }
+#endif
+
+ MOZ_ASSERT(!gService);
+ gService = this;
+ RegisterWeakMemoryReporter(this);
+
+ return NS_OK;
+}
+
+nsEffectiveTLDService::~nsEffectiveTLDService()
+{
+ UnregisterWeakMemoryReporter(this);
+ gService = nullptr;
+}
+
+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);
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_ARG_POINTER(innerURI);
+
+ nsAutoCString host;
+ nsresult rv = innerURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ return GetBaseDomainInternal(host, 0, 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);
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_ARG_POINTER(innerURI);
+
+ nsAutoCString host;
+ nsresult rv = innerURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ return GetBaseDomainInternal(host, aAdditionalParts + 1, 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, 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, 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, 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,
+ nsACString &aBaseDomain)
+{
+ 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;
+
+ // 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)
+ 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;
+ 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 == '.')
+ return NS_ERROR_INVALID_ARG;
+
+ // Perform the lookup.
+ const ETLDEntry* entry = ETLDEntry::GetEntry(currDomain);
+ if (entry) {
+ if (entry->IsWild() && prevDomain) {
+ // wildcard rules imply an eTLD one level inferior to the match.
+ eTLD = prevDomain;
+ break;
+
+ } else if (entry->IsNormal() || !nextDot) {
+ // specific match, or we've hit the top domain level
+ eTLD = currDomain;
+ break;
+
+ } else if (entry->IsException()) {
+ // 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, '.');
+ }
+
+ 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)
+ return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
+
+ aBaseDomain = Substring(iter, end);
+ // 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;
+}
diff --git a/netwerk/dns/nsEffectiveTLDService.h b/netwerk/dns/nsEffectiveTLDService.h
new file mode 100644
index 0000000000..92e7abe059
--- /dev/null
+++ b/netwerk/dns/nsEffectiveTLDService.h
@@ -0,0 +1,98 @@
+//* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef EffectiveTLDService_h
+#define EffectiveTLDService_h
+
+#include "nsIEffectiveTLDService.h"
+
+#include "nsIMemoryReporter.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/MemoryReporting.h"
+
+class nsIIDNService;
+
+// struct for static data generated from effective_tld_names.dat
+struct ETLDEntry {
+ friend class nsEffectiveTLDService;
+
+public:
+ bool IsNormal() const { return wild || !exception; }
+ bool IsException() const { return exception; }
+ bool IsWild() const { return wild; }
+
+ const char* GetEffectiveTLDName() const
+ {
+ return strings.strtab + strtab_index;
+ }
+
+ static const ETLDEntry* GetEntry(const char* aDomain);
+
+ static const size_t ETLD_ENTRY_N_INDEX_BITS = 30;
+
+ // These fields must be public to allow static construction.
+ uint32_t strtab_index : ETLD_ENTRY_N_INDEX_BITS;
+ uint32_t exception : 1;
+ uint32_t wild : 1;
+
+private:
+ struct Cmp {
+ int operator()(const ETLDEntry aEntry) const
+ {
+ return strcmp(mName, aEntry.GetEffectiveTLDName());
+ }
+ explicit Cmp(const char* aName) : mName(aName) {}
+ const char* mName;
+ };
+
+#define ETLD_STR_NUM_1(line) str##line
+#define ETLD_STR_NUM(line) ETLD_STR_NUM_1(line)
+ struct etld_string_list {
+#define ETLD_ENTRY(name, ex, wild) char ETLD_STR_NUM(__LINE__)[sizeof(name)];
+#include "etld_data.inc"
+#undef ETLD_ENTRY
+ };
+
+ // This static string table is all the eTLD domain names packed together.
+ static const union etld_strings {
+ struct etld_string_list list;
+ char strtab[1];
+ } strings;
+
+ // This is the static entries table. Each entry has an index into the string
+ // table. The entries are in sorted order so that binary search can be used.
+ static const ETLDEntry entries[];
+
+ void FuncForStaticAsserts(void);
+#undef ETLD_STR_NUM
+#undef ETLD_STR_NUM1
+};
+
+class nsEffectiveTLDService final
+ : public nsIEffectiveTLDService
+ , public nsIMemoryReporter
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEFFECTIVETLDSERVICE
+ NS_DECL_NSIMEMORYREPORTER
+
+ nsEffectiveTLDService();
+ nsresult Init();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+private:
+ nsresult GetBaseDomainInternal(nsCString &aHostname, int32_t aAdditionalParts, nsACString &aBaseDomain);
+ nsresult NormalizeHostname(nsCString &aHostname);
+ ~nsEffectiveTLDService();
+
+ nsCOMPtr<nsIIDNService> mIDNService;
+};
+
+#endif // EffectiveTLDService_h
diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp
new file mode 100644
index 0000000000..f2e26cadd4
--- /dev/null
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -0,0 +1,1579 @@
+/* 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/. */
+
+#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 "nsAutoPtr.h"
+#include "nsPrintfCString.h"
+#include "prthread.h"
+#include "prerror.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "PLDHashTable.h"
+#include "plstr.h"
+#include "nsURLHelper.h"
+#include "nsThreadUtils.h"
+#include "GetAddrInfo.h"
+
+#include "mozilla/HashFunctions.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Preferences.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");
+
+//----------------------------------------------------------------------------
+
+static LazyLogModule gHostResolverLog("nsHostResolver");
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(gHostResolverLog, mozilla::LogLevel::Debug)
+
+#define LOG_HOST(host, interface) host, \
+ (interface && interface[0] != '\0') ? " on interface " : "", \
+ (interface && interface[0] != '\0') ? interface : ""
+
+//----------------------------------------------------------------------------
+
+static inline void
+MoveCList(PRCList &from, PRCList &to)
+{
+ if (!PR_CLIST_IS_EMPTY(&from)) {
+ to.next = from.next;
+ to.prev = from.prev;
+ to.next->prev = &to;
+ to.prev->next = &to;
+ PR_INIT_CLIST(&from);
+ }
+}
+
+//----------------------------------------------------------------------------
+
+#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)
+
+nsHostRecord::nsHostRecord(const nsHostKey *key)
+ : addr_info_lock("nsHostRecord.addr_info_lock")
+ , addr_info_gencnt(0)
+ , addr_info(nullptr)
+ , addr(nullptr)
+ , negative(false)
+ , resolving(false)
+ , onQueue(false)
+ , usingAnyThread(false)
+ , mDoomed(false)
+#if TTL_AVAILABLE
+ , mGetTtl(false)
+#endif
+ , mBlacklistedCount(0)
+ , mResolveAgain(false)
+{
+ host = ((char *) this) + sizeof(nsHostRecord);
+ memcpy((char *) host, key->host, strlen(key->host) + 1);
+ flags = key->flags;
+ af = key->af;
+ netInterface = host + strlen(key->host) + 1;
+ memcpy((char *) netInterface, key->netInterface,
+ strlen(key->netInterface) + 1);
+ PR_INIT_CLIST(this);
+ PR_INIT_CLIST(&callbacks);
+}
+
+nsresult
+nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
+{
+ size_t hostLen = strlen(key->host) + 1;
+ size_t netInterfaceLen = strlen(key->netInterface) + 1;
+ size_t size = hostLen + netInterfaceLen + sizeof(nsHostRecord);
+
+ // Use placement new to create the object with room for the hostname and
+ // network interface name allocated after it.
+ void *place = ::operator new(size);
+ *result = new(place) nsHostRecord(key);
+ NS_ADDREF(*result);
+
+ return NS_OK;
+}
+
+void
+nsHostRecord::SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, unsigned int grace)
+{
+ mValidStart = now;
+ 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;
+}
+
+nsHostRecord::~nsHostRecord()
+{
+ Telemetry::Accumulate(Telemetry::DNS_BLACKLIST_COUNT, mBlacklistedCount);
+ delete addr_info;
+ delete addr;
+}
+
+bool
+nsHostRecord::Blacklisted(NetAddr *aQuery)
+{
+ // must call locked
+ LOG(("Checking blacklist for host [%s%s%s], host record [%p].\n",
+ LOG_HOST(host, netInterface), this));
+
+ // skip the string conversion for the common case of no blacklist
+ if (!mBlacklistedItems.Length()) {
+ return false;
+ }
+
+ char buf[kIPv6CStrBufSize];
+ if (!NetAddrToString(aQuery, buf, sizeof(buf))) {
+ return false;
+ }
+ nsDependentCString strQuery(buf);
+
+ for (uint32_t i = 0; i < mBlacklistedItems.Length(); i++) {
+ if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) {
+ LOG(("Address [%s] is blacklisted for host [%s%s%s].\n", buf,
+ LOG_HOST(host, netInterface)));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsHostRecord::ReportUnusable(NetAddr *aAddress)
+{
+ // must call locked
+ LOG(("Adding address to blacklist for host [%s%s%s], host record [%p].\n",
+ LOG_HOST(host, netInterface), this));
+
+ ++mBlacklistedCount;
+
+ if (negative)
+ mDoomed = true;
+
+ char buf[kIPv6CStrBufSize];
+ if (NetAddrToString(aAddress, buf, sizeof(buf))) {
+ LOG(("Successfully adding address [%s] to blacklist for host "
+ "[%s%s%s].\n", buf, LOG_HOST(host, netInterface)));
+ mBlacklistedItems.AppendElement(nsCString(buf));
+ }
+}
+
+void
+nsHostRecord::ResetBlacklist()
+{
+ // must call locked
+ LOG(("Resetting blacklist for host [%s%s%s], host record [%p].\n",
+ LOG_HOST(host, netInterface), this));
+ mBlacklistedItems.Clear();
+}
+
+nsHostRecord::ExpirationStatus
+nsHostRecord::CheckExpiration(const mozilla::TimeStamp& now) const {
+ if (!mGraceStart.IsNull() && now >= mGraceStart
+ && !mValidEnd.IsNull() && now < mValidEnd) {
+ return nsHostRecord::EXP_GRACE;
+ } else if (!mValidEnd.IsNull() && now < mValidEnd) {
+ return nsHostRecord::EXP_VALID;
+ }
+
+ return nsHostRecord::EXP_EXPIRED;
+}
+
+
+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;
+ }
+
+ return addr_info || addr || negative;
+}
+
+static size_t
+SizeOfResolveHostCallbackListExcludingHead(const PRCList *head,
+ MallocSizeOf mallocSizeOf)
+{
+ size_t n = 0;
+ PRCList *curr = head->next;
+ while (curr != head) {
+ nsResolveHostCallback *callback =
+ static_cast<nsResolveHostCallback*>(curr);
+ n += callback->SizeOfIncludingThis(mallocSizeOf);
+ curr = curr->next;
+ }
+ return n;
+}
+
+size_t
+nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
+{
+ size_t n = mallocSizeOf(this);
+
+ // The |host| field (inherited from nsHostKey) actually points to extra
+ // memory that is allocated beyond the end of the nsHostRecord (see
+ // nsHostRecord::Create()). So it will be included in the
+ // |mallocSizeOf(this)| call above.
+
+ n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf);
+ n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0;
+ n += mallocSizeOf(addr);
+
+ n += mBlacklistedItems.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (size_t i = 0; i < mBlacklistedItems.Length(); i++) {
+ n += mBlacklistedItems[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ }
+ return n;
+}
+
+nsHostRecord::DnsPriority
+nsHostRecord::GetPriority(uint16_t aFlags)
+{
+ if (IsHighPriority(aFlags)){
+ return nsHostRecord::DNS_PRIORITY_HIGH;
+ } else if (IsMediumPriority(aFlags)) {
+ return nsHostRecord::DNS_PRIORITY_MEDIUM;
+ }
+
+ return nsHostRecord::DNS_PRIORITY_LOW;
+}
+
+// Returns true if the entry can be removed, or false if it should be left.
+// Sets mResolveAgain true for entries being resolved right now.
+bool
+nsHostRecord::RemoveOrRefresh()
+{
+ if (resolving) {
+ 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.
+ mResolveAgain = 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;
+}
+
+//----------------------------------------------------------------------------
+
+struct nsHostDBEnt : PLDHashEntryHdr
+{
+ nsHostRecord *rec;
+};
+
+static PLDHashNumber
+HostDB_HashKey(const void *key)
+{
+ const nsHostKey *hk = static_cast<const nsHostKey *>(key);
+ return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af,
+ HashString(hk->netInterface));
+}
+
+static bool
+HostDB_MatchEntry(const PLDHashEntryHdr *entry,
+ const void *key)
+{
+ const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry);
+ const nsHostKey *hk = static_cast<const nsHostKey *>(key);
+
+ return !strcmp(he->rec->host ? he->rec->host : "",
+ hk->host ? hk->host : "") &&
+ RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) &&
+ he->rec->af == hk->af &&
+ !strcmp(he->rec->netInterface, hk->netInterface);
+}
+
+static void
+HostDB_MoveEntry(PLDHashTable *table,
+ const PLDHashEntryHdr *from,
+ PLDHashEntryHdr *to)
+{
+ static_cast<nsHostDBEnt *>(to)->rec =
+ static_cast<const nsHostDBEnt *>(from)->rec;
+}
+
+static void
+HostDB_ClearEntry(PLDHashTable *table,
+ PLDHashEntryHdr *entry)
+{
+ nsHostDBEnt *he = static_cast<nsHostDBEnt*>(entry);
+ MOZ_ASSERT(he, "nsHostDBEnt is null!");
+
+ nsHostRecord *hr = he->rec;
+ MOZ_ASSERT(hr, "nsHostDBEnt has null host record!");
+
+ LOG(("Clearing cache db entry for host [%s%s%s].\n",
+ LOG_HOST(hr->host, hr->netInterface)));
+#if defined(DEBUG)
+ {
+ MutexAutoLock lock(hr->addr_info_lock);
+ if (!hr->addr_info) {
+ LOG(("No address info for host [%s%s%s].\n",
+ LOG_HOST(hr->host, hr->netInterface)));
+ } else {
+ if (!hr->mValidEnd.IsNull()) {
+ TimeDuration diff = hr->mValidEnd - TimeStamp::NowLoRes();
+ LOG(("Record for host [%s%s%s] expires in %f seconds.\n",
+ LOG_HOST(hr->host, hr->netInterface),
+ diff.ToSeconds()));
+ } else {
+ LOG(("Record for host [%s%s%s] not yet valid.\n",
+ LOG_HOST(hr->host, hr->netInterface)));
+ }
+
+ NetAddrElement *addrElement = nullptr;
+ char buf[kIPv6CStrBufSize];
+ do {
+ if (!addrElement) {
+ addrElement = hr->addr_info->mAddresses.getFirst();
+ } else {
+ addrElement = addrElement->getNext();
+ }
+
+ if (addrElement) {
+ NetAddrToString(&addrElement->mAddress, buf, sizeof(buf));
+ LOG((" [%s]\n", buf));
+ }
+ }
+ while (addrElement);
+ }
+ }
+#endif
+ NS_RELEASE(he->rec);
+}
+
+static void
+HostDB_InitEntry(PLDHashEntryHdr *entry,
+ const void *key)
+{
+ nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
+ nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec);
+}
+
+static const PLDHashTableOps gHostDB_ops =
+{
+ HostDB_HashKey,
+ HostDB_MatchEntry,
+ HostDB_MoveEntry,
+ HostDB_ClearEntry,
+ HostDB_InitEntry,
+};
+
+//----------------------------------------------------------------------------
+
+#if TTL_AVAILABLE
+static const char kPrefGetTtl[] = "network.dns.get-ttl";
+static bool sGetTtlEnabled = false;
+
+static void DnsPrefChanged(const char* aPref, void* aClosure)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Should be getting pref changed notification on main thread!");
+
+ if (strcmp(aPref, kPrefGetTtl) != 0) {
+ LOG(("DnsPrefChanged ignoring pref \"%s\"", aPref));
+ return;
+ }
+
+ auto self = static_cast<nsHostResolver*>(aClosure);
+ MOZ_ASSERT(self);
+
+ sGetTtlEnabled = Preferences::GetBool(kPrefGetTtl);
+}
+#endif
+
+nsHostResolver::nsHostResolver(uint32_t maxCacheEntries,
+ uint32_t defaultCacheEntryLifetime,
+ uint32_t defaultGracePeriod)
+ : mMaxCacheEntries(maxCacheEntries)
+ , mDefaultCacheLifetime(defaultCacheEntryLifetime)
+ , mDefaultGracePeriod(defaultGracePeriod)
+ , mLock("nsHostResolver.mLock")
+ , mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV")
+ , mDB(&gHostDB_ops, sizeof(nsHostDBEnt), 0)
+ , mEvictionQSize(0)
+ , mShutdown(true)
+ , mNumIdleThreads(0)
+ , mThreadCount(0)
+ , mActiveAnyThreadCount(0)
+ , mPendingCount(0)
+{
+ mCreationTime = PR_Now();
+ PR_INIT_CLIST(&mHighQ);
+ PR_INIT_CLIST(&mMediumQ);
+ PR_INIT_CLIST(&mLowQ);
+ PR_INIT_CLIST(&mEvictionQ);
+
+ mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds);
+ mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
+}
+
+nsHostResolver::~nsHostResolver() = default;
+
+nsresult
+nsHostResolver::Init()
+{
+ if (NS_FAILED(GetAddrInfoInit())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mShutdown = false;
+
+#if TTL_AVAILABLE
+ // 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.");
+ }
+#endif
+
+#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
+ return NS_OK;
+}
+
+void
+nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
+{
+ // loop through pending queue, erroring out pending lookups.
+ if (!PR_CLIST_IS_EMPTY(aPendingQ)) {
+ PRCList *node = aPendingQ->next;
+ while (node != aPendingQ) {
+ nsHostRecord *rec = static_cast<nsHostRecord *>(node);
+ node = node->next;
+ OnLookupComplete(rec, NS_ERROR_ABORT, nullptr);
+ }
+ }
+}
+
+//
+// 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()
+{
+ MutexAutoLock lock(mLock);
+ mEvictionQSize = 0;
+
+ // Clear the evictionQ and remove all its corresponding entries from
+ // the cache first
+ if (!PR_CLIST_IS_EMPTY(&mEvictionQ)) {
+ PRCList *node = mEvictionQ.next;
+ while (node != &mEvictionQ) {
+ nsHostRecord *rec = static_cast<nsHostRecord *>(node);
+ node = node->next;
+ PR_REMOVE_AND_INIT_LINK(rec);
+ mDB.Remove((nsHostKey *) rec);
+ NS_RELEASE(rec);
+ }
+ }
+
+ // Refresh the cache entries that are resolving RIGHT now, remove the rest.
+ for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<nsHostDBEnt *>(iter.Get());
+ // Try to remove the record, or mark it for refresh.
+ if (entry->rec->RemoveOrRefresh()) {
+ PR_REMOVE_LINK(entry->rec);
+ iter.Remove();
+ }
+ }
+}
+
+void
+nsHostResolver::Shutdown()
+{
+ LOG(("Shutting down host resolver.\n"));
+
+#if TTL_AVAILABLE
+ {
+ DebugOnly<nsresult> rv = Preferences::UnregisterCallback(
+ &DnsPrefChanged, kPrefGetTtl, this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Could not unregister DNS TTL pref callback.");
+ }
+#endif
+
+ PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ;
+ PR_INIT_CLIST(&pendingQHigh);
+ PR_INIT_CLIST(&pendingQMed);
+ PR_INIT_CLIST(&pendingQLow);
+ PR_INIT_CLIST(&evictionQ);
+
+ {
+ MutexAutoLock lock(mLock);
+
+ mShutdown = true;
+
+ MoveCList(mHighQ, pendingQHigh);
+ MoveCList(mMediumQ, pendingQMed);
+ MoveCList(mLowQ, pendingQLow);
+ MoveCList(mEvictionQ, evictionQ);
+ mEvictionQSize = 0;
+ mPendingCount = 0;
+
+ if (mNumIdleThreads)
+ mIdleThreadCV.NotifyAll();
+
+ // empty host database
+ mDB.Clear();
+ }
+
+ ClearPendingQueue(&pendingQHigh);
+ ClearPendingQueue(&pendingQMed);
+ ClearPendingQueue(&pendingQLow);
+
+ if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
+ PRCList *node = evictionQ.next;
+ while (node != &evictionQ) {
+ nsHostRecord *rec = static_cast<nsHostRecord *>(node);
+ node = node->next;
+ NS_RELEASE(rec);
+ }
+ }
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+
+ // Logically join the outstanding worker threads with a timeout.
+ // Use this approach instead of PR_JoinThread() because that does
+ // not allow a timeout which may be necessary for a semi-responsive
+ // shutdown if the thread is blocked on a very slow DNS resolution.
+ // mThreadCount is read outside of mLock, but the worst case
+ // scenario for that race is one extra 25ms sleep.
+
+ PRIntervalTime delay = PR_MillisecondsToInterval(25);
+ PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20);
+ while (mThreadCount && PR_IntervalNow() < stopTime)
+ PR_Sleep(delay);
+#endif
+
+ {
+ mozilla::DebugOnly<nsresult> rv = GetAddrInfoShutdown();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to shutdown GetAddrInfo");
+ }
+}
+
+void
+nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ)
+{
+ NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued");
+
+ PR_REMOVE_LINK(aRec);
+ PR_APPEND_LINK(aRec, &aDestQ);
+}
+
+nsresult
+nsHostResolver::ResolveHost(const char *host,
+ uint16_t flags,
+ uint16_t af,
+ const char *netInterface,
+ nsResolveHostCallback *callback)
+{
+ NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(netInterface, NS_ERROR_UNEXPECTED);
+
+ LOG(("Resolving host [%s%s%s]%s.\n", LOG_HOST(host, netInterface),
+ flags & RES_BYPASS_CACHE ? " - bypassing cache" : ""));
+
+ // ensure that we are working with a valid hostname before proceeding. see
+ // bug 304904 for details.
+ if (!net_IsValidHostName(nsDependentCString(host)))
+ return NS_ERROR_UNKNOWN_HOST;
+
+ // 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)
+ rv = NS_ERROR_NOT_INITIALIZED;
+ else {
+ // 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));
+
+ // 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.
+
+ nsHostKey key = { host, flags, af, netInterface };
+ auto he = static_cast<nsHostDBEnt*>(mDB.Add(&key, fallible));
+
+ // if the record is null, the hash table OOM'd.
+ if (!he) {
+ LOG((" Out of memory: no cache entry for host [%s%s%s].\n",
+ LOG_HOST(host, netInterface)));
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ // do we have a cached result that we can reuse?
+ else if (!(flags & RES_BYPASS_CACHE) &&
+ he->rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
+ LOG((" Using cached record for host [%s%s%s].\n",
+ LOG_HOST(host, netInterface)));
+ // put reference to host record on stack...
+ result = he->rec;
+ 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(he->rec, host);
+
+ if (he->rec->negative) {
+ LOG((" Negative cache entry for host [%s%s%s].\n",
+ LOG_HOST(host, netInterface)));
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NEGATIVE_HIT);
+ status = NS_ERROR_UNKNOWN_HOST;
+ }
+ }
+ // if the host name is an IP address literal and has been parsed,
+ // go ahead and use it.
+ else if (he->rec->addr) {
+ LOG((" Using cached address for IP Literal [%s].\n", host));
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_LITERAL);
+ result = he->rec;
+ }
+ // 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.)
+ else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) {
+ LOG((" Host is IP Literal [%s].\n", host));
+ // ok, just copy the result into the host record, and be done
+ // with it! ;-)
+ he->rec->addr = new NetAddr();
+ PRNetAddrToNetAddr(&tempAddr, he->rec->addr);
+ // put reference to host record on stack...
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_LITERAL);
+ result = he->rec;
+ }
+ else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
+ !IsHighPriority(flags) &&
+ !he->rec->resolving) {
+ LOG((" Lookup queue full: dropping %s priority request for "
+ "host [%s%s%s].\n",
+ IsMediumPriority(flags) ? "medium" : "low",
+ LOG_HOST(host, netInterface)));
+ 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;
+ }
+ else if (flags & RES_OFFLINE) {
+ LOG((" Offline request for host [%s%s%s]; ignoring.\n",
+ LOG_HOST(host, netInterface)));
+ rv = NS_ERROR_OFFLINE;
+ }
+
+ // If this is an IPV4 or IPV6 specific request, check if there is
+ // an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
+ else if (!he->rec->resolving) {
+ if (!(flags & RES_BYPASS_CACHE) &&
+ ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
+ // First, search for an entry with AF_UNSPEC
+ const nsHostKey unspecKey = { host, flags, PR_AF_UNSPEC,
+ netInterface };
+ auto unspecHe =
+ static_cast<nsHostDBEnt*>(mDB.Search(&unspecKey));
+ NS_ASSERTION(!unspecHe ||
+ (unspecHe && unspecHe->rec),
+ "Valid host entries should contain a record");
+ TimeStamp now = TimeStamp::NowLoRes();
+ if (unspecHe &&
+ unspecHe->rec->HasUsableResult(now, flags)) {
+
+ MOZ_ASSERT(unspecHe->rec->addr_info || unspecHe->rec->negative,
+ "Entry should be resolved or negative.");
+
+ LOG((" Trying AF_UNSPEC entry for host [%s%s%s] af: %s.\n",
+ LOG_HOST(host, netInterface),
+ (af == PR_AF_INET) ? "AF_INET" : "AF_INET6"));
+
+ he->rec->addr_info = nullptr;
+ if (unspecHe->rec->negative) {
+ he->rec->negative = unspecHe->rec->negative;
+ he->rec->CopyExpirationTimesAndFlagsFrom(unspecHe->rec);
+ } else if (unspecHe->rec->addr_info) {
+ // Search for any valid address in the AF_UNSPEC entry
+ // in the cache (not blacklisted and from the right
+ // family).
+ NetAddrElement *addrIter =
+ unspecHe->rec->addr_info->mAddresses.getFirst();
+ while (addrIter) {
+ if ((af == addrIter->mAddress.inet.family) &&
+ !unspecHe->rec->Blacklisted(&addrIter->mAddress)) {
+ if (!he->rec->addr_info) {
+ he->rec->addr_info = new AddrInfo(
+ unspecHe->rec->addr_info->mHostName,
+ unspecHe->rec->addr_info->mCanonicalName);
+ he->rec->CopyExpirationTimesAndFlagsFrom(unspecHe->rec);
+ }
+ he->rec->addr_info->AddAddress(
+ new NetAddrElement(*addrIter));
+ }
+ addrIter = addrIter->getNext();
+ }
+ }
+ // Now check if we have a new record.
+ if (he->rec->HasUsableResult(now, flags)) {
+ result = he->rec;
+ if (he->rec->negative) {
+ status = NS_ERROR_UNKNOWN_HOST;
+ }
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_HIT);
+ ConditionallyRefreshRecord(he->rec, host);
+ }
+ // 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.
+ else if (af == PR_AF_INET6) {
+ LOG((" No AF_INET6 in AF_UNSPEC entry: "
+ "host [%s%s%s] unknown host.",
+ LOG_HOST(host, netInterface)));
+ result = he->rec;
+ he->rec->negative = true;
+ status = NS_ERROR_UNKNOWN_HOST;
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NEGATIVE_HIT);
+ }
+ }
+ }
+ // If no valid address was found in the cache or this is an
+ // AF_UNSPEC request, then start a new lookup.
+ if (!result) {
+ LOG((" No usable address in cache for host [%s%s%s].",
+ LOG_HOST(host, netInterface)));
+
+ // Add callback to the list of pending callbacks.
+ PR_APPEND_LINK(callback, &he->rec->callbacks);
+ he->rec->flags = flags;
+ rv = IssueLookup(he->rec);
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NETWORK_FIRST);
+ if (NS_FAILED(rv)) {
+ PR_REMOVE_AND_INIT_LINK(callback);
+ }
+ else {
+ LOG((" DNS lookup for host [%s%s%s] blocking "
+ "pending 'getaddrinfo' query: callback [%p]",
+ LOG_HOST(host, netInterface), callback));
+ }
+ }
+ }
+ else {
+ LOG((" Host [%s%s%s] is being resolved. Appending callback "
+ "[%p].", LOG_HOST(host, netInterface), callback));
+
+ PR_APPEND_LINK(callback, &he->rec->callbacks);
+ if (he->rec->onQueue) {
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NETWORK_SHARED);
+
+ // Consider the case where we are on a pending queue of
+ // lower priority than the request is being made at.
+ // In that case we should upgrade to the higher queue.
+
+ if (IsHighPriority(flags) &&
+ !IsHighPriority(he->rec->flags)) {
+ // Move from (low|med) to high.
+ MoveQueue(he->rec, mHighQ);
+ he->rec->flags = flags;
+ ConditionallyCreateThread(he->rec);
+ } else if (IsMediumPriority(flags) &&
+ IsLowPriority(he->rec->flags)) {
+ // Move from low to med.
+ MoveQueue(he->rec, mMediumQ);
+ he->rec->flags = flags;
+ mIdleThreadCV.Notify();
+ }
+ }
+ }
+ }
+ }
+ if (result) {
+ callback->OnLookupComplete(this, result, status);
+ }
+
+ return rv;
+}
+
+void
+nsHostResolver::DetachCallback(const char *host,
+ uint16_t flags,
+ uint16_t af,
+ const char *netInterface,
+ nsResolveHostCallback *callback,
+ nsresult status)
+{
+ RefPtr<nsHostRecord> rec;
+ {
+ MutexAutoLock lock(mLock);
+
+ nsHostKey key = { host, flags, af, netInterface };
+ auto he = static_cast<nsHostDBEnt*>(mDB.Search(&key));
+ if (he) {
+ // walk list looking for |callback|... we cannot assume
+ // that it will be there!
+ PRCList *node = he->rec->callbacks.next;
+ while (node != &he->rec->callbacks) {
+ if (static_cast<nsResolveHostCallback *>(node) == callback) {
+ PR_REMOVE_LINK(callback);
+ rec = he->rec;
+ break;
+ }
+ node = node->next;
+ }
+ }
+ }
+
+ // 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->OnLookupComplete(this, rec, status);
+}
+
+nsresult
+nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
+{
+ if (mNumIdleThreads) {
+ // wake up idle thread to process this lookup
+ mIdleThreadCV.Notify();
+ }
+ else if ((mThreadCount < HighThreadThreshold) ||
+ (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) {
+ // dispatch new worker thread
+ NS_ADDREF_THIS(); // owning reference passed to thread
+
+ mThreadCount++;
+ PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD,
+ ThreadFunc,
+ this,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD,
+ 0);
+ if (!thr) {
+ mThreadCount--;
+ NS_RELEASE_THIS();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ LOG((" Unable to find a thread for looking up host [%s%s%s].\n",
+ LOG_HOST(rec->host, rec->netInterface)));
+ }
+ return NS_OK;
+}
+
+nsresult
+nsHostResolver::IssueLookup(nsHostRecord *rec)
+{
+ nsresult rv = NS_OK;
+ NS_ASSERTION(!rec->resolving, "record is already being resolved");
+
+ // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
+ // If rec is on mEvictionQ, then we can just move the owning
+ // reference over to the new active queue.
+ if (rec->next == rec)
+ NS_ADDREF(rec);
+ else {
+ PR_REMOVE_LINK(rec);
+ mEvictionQSize--;
+ }
+
+ switch (nsHostRecord::GetPriority(rec->flags)) {
+ case nsHostRecord::DNS_PRIORITY_HIGH:
+ PR_APPEND_LINK(rec, &mHighQ);
+ break;
+
+ case nsHostRecord::DNS_PRIORITY_MEDIUM:
+ PR_APPEND_LINK(rec, &mMediumQ);
+ break;
+
+ case nsHostRecord::DNS_PRIORITY_LOW:
+ PR_APPEND_LINK(rec, &mLowQ);
+ break;
+ }
+ mPendingCount++;
+
+ rec->resolving = true;
+ rec->onQueue = true;
+
+ rv = ConditionallyCreateThread(rec);
+
+ LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n",
+ static_cast<uint32_t>(mThreadCount),
+ static_cast<uint32_t>(mActiveAnyThreadCount),
+ static_cast<uint32_t>(mNumIdleThreads),
+ static_cast<uint32_t>(mPendingCount)));
+
+ return rv;
+}
+
+nsresult
+nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host)
+{
+ if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID
+ || rec->negative) && !rec->resolving) {
+ LOG((" Using %s cache entry for host [%s] but starting async renewal.",
+ rec->negative ? "negative" :"positive", host));
+ IssueLookup(rec);
+
+ if (!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(PRCList &aQ, nsHostRecord **aResult)
+{
+ *aResult = static_cast<nsHostRecord *>(aQ.next);
+ PR_REMOVE_AND_INIT_LINK(*aResult);
+ mPendingCount--;
+ (*aResult)->onQueue = false;
+}
+
+bool
+nsHostResolver::GetHostToLookup(nsHostRecord **result)
+{
+ bool timedOut = false;
+ PRIntervalTime epoch, now, timeout;
+
+ MutexAutoLock lock(mLock);
+
+ timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
+ epoch = PR_IntervalNow();
+
+ while (!mShutdown) {
+ // remove next record from Q; hand over owning reference. Check high, then med, then low
+
+#if TTL_AVAILABLE
+ #define SET_GET_TTL(var, val) \
+ (var)->mGetTtl = sGetTtlEnabled && (val)
+#else
+ #define SET_GET_TTL(var, val)
+#endif
+
+ if (!PR_CLIST_IS_EMPTY(&mHighQ)) {
+ DeQueue (mHighQ, result);
+ SET_GET_TTL(*result, false);
+ return true;
+ }
+
+ if (mActiveAnyThreadCount < HighThreadThreshold) {
+ if (!PR_CLIST_IS_EMPTY(&mMediumQ)) {
+ DeQueue (mMediumQ, result);
+ mActiveAnyThreadCount++;
+ (*result)->usingAnyThread = true;
+ SET_GET_TTL(*result, true);
+ return true;
+ }
+
+ if (!PR_CLIST_IS_EMPTY(&mLowQ)) {
+ DeQueue (mLowQ, result);
+ mActiveAnyThreadCount++;
+ (*result)->usingAnyThread = 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
+
+ mNumIdleThreads++;
+ mIdleThreadCV.Wait(timeout);
+ mNumIdleThreads--;
+
+ now = PR_IntervalNow();
+
+ if ((PRIntervalTime)(now - epoch) >= timeout)
+ timedOut = true;
+ else {
+ // It is possible that PR_WaitCondVar() 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 -= (PRIntervalTime)(now - epoch);
+ epoch = now;
+ }
+ }
+
+ // tell thread to exit...
+ return false;
+}
+
+void
+nsHostResolver::PrepareRecordExpiration(nsHostRecord* rec) const
+{
+ MOZ_ASSERT(((bool)rec->addr_info) != rec->negative);
+ if (!rec->addr_info) {
+ rec->SetExpiration(TimeStamp::NowLoRes(),
+ NEGATIVE_RECORD_LIFETIME, 0);
+ LOG(("Caching host [%s%s%s] negative record for %u seconds.\n",
+ LOG_HOST(rec->host, rec->netInterface),
+ NEGATIVE_RECORD_LIFETIME));
+ return;
+ }
+
+ unsigned int lifetime = mDefaultCacheLifetime;
+ unsigned int grace = mDefaultGracePeriod;
+#if TTL_AVAILABLE
+ unsigned int ttl = mDefaultCacheLifetime;
+ if (sGetTtlEnabled) {
+ MutexAutoLock lock(rec->addr_info_lock);
+ if (rec->addr_info && rec->addr_info->ttl != AddrInfo::NO_TTL_DATA) {
+ ttl = rec->addr_info->ttl;
+ }
+ lifetime = ttl;
+ grace = 0;
+ }
+#endif
+
+ rec->SetExpiration(TimeStamp::NowLoRes(), lifetime, grace);
+ LOG(("Caching host [%s%s%s] record for %u seconds (grace %d).",
+ LOG_HOST(rec->host, rec->netInterface), lifetime, grace));
+}
+
+static bool
+different_rrset(AddrInfo *rrset1, AddrInfo *rrset2)
+{
+ if (!rrset1 || !rrset2) {
+ return true;
+ }
+
+ LOG(("different_rrset %s\n", rrset1->mHostName));
+ nsTArray<NetAddr> orderedSet1;
+ nsTArray<NetAddr> orderedSet2;
+
+ for (NetAddrElement *element = rrset1->mAddresses.getFirst();
+ element; element = element->getNext()) {
+ if (LOG_ENABLED()) {
+ char buf[128];
+ NetAddrToString(&element->mAddress, buf, 128);
+ LOG(("different_rrset add to set 1 %s\n", buf));
+ }
+ orderedSet1.InsertElementAt(orderedSet1.Length(), element->mAddress);
+ }
+
+ for (NetAddrElement *element = rrset2->mAddresses.getFirst();
+ element; element = element->getNext()) {
+ if (LOG_ENABLED()) {
+ char buf[128];
+ NetAddrToString(&element->mAddress, buf, 128);
+ LOG(("different_rrset add to set 2 %s\n", buf));
+ }
+ orderedSet2.InsertElementAt(orderedSet2.Length(), element->mAddress);
+ }
+
+ if (orderedSet1.Length() != orderedSet2.Length()) {
+ LOG(("different_rrset true due to length change\n"));
+ return true;
+ }
+ orderedSet1.Sort();
+ orderedSet2.Sort();
+
+ for (uint32_t i = 0; i < orderedSet1.Length(); ++i) {
+ if (!(orderedSet1[i] == orderedSet2[i])) {
+ LOG(("different_rrset true due to content change\n"));
+ return true;
+ }
+ }
+ LOG(("different_rrset false\n"));
+ return false;
+}
+
+//
+// OnLookupComplete() checks if the resolving should be redone and if so it
+// returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT.
+// takes ownership of AddrInfo parameter
+nsHostResolver::LookupStatus
+nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* newRRSet)
+{
+ // get the list of pending callbacks for this lookup, and notify
+ // them that the lookup is complete.
+ PRCList cbs;
+ PR_INIT_CLIST(&cbs);
+ {
+ MutexAutoLock lock(mLock);
+
+ if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) {
+ LOG(("nsHostResolver record %p resolve again due to flushcache\n", rec));
+ rec->mResolveAgain = false;
+ delete newRRSet;
+ return LOOKUP_RESOLVEAGAIN;
+ }
+
+ // grab list of callbacks to notify
+ MoveCList(rec->callbacks, cbs);
+
+ // update record fields. We might have a rec->addr_info already if a
+ // previous lookup result expired and we're reresolving it..
+ AddrInfo *old_addr_info;
+ {
+ MutexAutoLock lock(rec->addr_info_lock);
+ if (different_rrset(rec->addr_info, newRRSet)) {
+ LOG(("nsHostResolver record %p new gencnt\n", rec));
+ old_addr_info = rec->addr_info;
+ rec->addr_info = newRRSet;
+ rec->addr_info_gencnt++;
+ } else {
+ if (rec->addr_info && newRRSet) {
+ rec->addr_info->ttl = newRRSet->ttl;
+ }
+ old_addr_info = newRRSet;
+ }
+ }
+ delete old_addr_info;
+
+ rec->negative = !rec->addr_info;
+ PrepareRecordExpiration(rec);
+ rec->resolving = false;
+
+ if (rec->usingAnyThread) {
+ mActiveAnyThreadCount--;
+ rec->usingAnyThread = false;
+ }
+
+ if (!mShutdown) {
+ // add to mEvictionQ
+ PR_APPEND_LINK(rec, &mEvictionQ);
+ NS_ADDREF(rec);
+ if (mEvictionQSize < mMaxCacheEntries)
+ mEvictionQSize++;
+ else {
+ // remove first element on mEvictionQ
+ nsHostRecord *head =
+ static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
+ PR_REMOVE_AND_INIT_LINK(head);
+ mDB.Remove((nsHostKey *) head);
+
+ if (!head->negative) {
+ // record the age of the entry upon eviction.
+ TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart;
+ Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE,
+ static_cast<uint32_t>(age.ToSeconds() / 60));
+ }
+
+ // release reference to rec owned by mEvictionQ
+ NS_RELEASE(head);
+ }
+#if TTL_AVAILABLE
+ if (!rec->mGetTtl && !rec->resolving && sGetTtlEnabled) {
+ LOG(("Issuing second async lookup for TTL for host [%s%s%s].",
+ LOG_HOST(rec->host, rec->netInterface)));
+ rec->flags =
+ (rec->flags & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW;
+ DebugOnly<nsresult> rv = IssueLookup(rec);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "Could not issue second async lookup for TTL.");
+ }
+#endif
+ }
+ }
+
+ if (!PR_CLIST_IS_EMPTY(&cbs)) {
+ PRCList *node = cbs.next;
+ while (node != &cbs) {
+ nsResolveHostCallback *callback =
+ static_cast<nsResolveHostCallback *>(node);
+ node = node->next;
+ callback->OnLookupComplete(this, rec, status);
+ // NOTE: callback must not be dereferenced after this point!!
+ }
+ }
+
+ NS_RELEASE(rec);
+
+ return LOOKUP_OK;
+}
+
+void
+nsHostResolver::CancelAsyncRequest(const char *host,
+ uint16_t flags,
+ uint16_t af,
+ const char *netInterface,
+ nsIDNSListener *aListener,
+ nsresult status)
+
+{
+ MutexAutoLock lock(mLock);
+
+ // Lookup the host record associated with host, flags & address family
+ nsHostKey key = { host, flags, af, netInterface };
+ auto he = static_cast<nsHostDBEnt*>(mDB.Search(&key));
+ if (he) {
+ nsHostRecord* recPtr = nullptr;
+ PRCList *node = he->rec->callbacks.next;
+ // Remove the first nsDNSAsyncRequest callback which matches the
+ // supplied listener object
+ while (node != &he->rec->callbacks) {
+ nsResolveHostCallback *callback
+ = static_cast<nsResolveHostCallback *>(node);
+ if (callback && (callback->EqualsAsyncListener(aListener))) {
+ // Remove from the list of callbacks
+ PR_REMOVE_LINK(callback);
+ recPtr = he->rec;
+ callback->OnLookupComplete(this, recPtr, status);
+ break;
+ }
+ node = node->next;
+ }
+
+ // If there are no more callbacks, remove the hash table entry
+ if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) {
+ mDB.Remove((nsHostKey *)recPtr);
+ // If record is on a Queue, remove it and then deref it
+ if (recPtr->next != recPtr) {
+ PR_REMOVE_LINK(recPtr);
+ NS_RELEASE(recPtr);
+ }
+ }
+ }
+}
+
+size_t
+nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
+{
+ MutexAutoLock lock(mLock);
+
+ size_t n = mallocSizeOf(this);
+
+ n += mDB.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (auto iter = mDB.ConstIter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<nsHostDBEnt*>(iter.Get());
+ n += entry->rec->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ // The following fields aren't measured.
+ // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to
+ // nsHostRecords that also pointed to by entries |mDB|, and measured when
+ // |mDB| is measured.
+
+ return n;
+}
+
+void
+nsHostResolver::ThreadFunc(void *arg)
+{
+ LOG(("DNS lookup thread - starting execution.\n"));
+
+ static nsThreadPoolNaming naming;
+ naming.SetThreadPoolName(NS_LITERAL_CSTRING("DNS Resolver"));
+
+#if defined(RES_RETRY_ON_FAILURE)
+ nsResState rs;
+#endif
+ nsHostResolver *resolver = (nsHostResolver *)arg;
+ nsHostRecord *rec = nullptr;
+ AddrInfo *ai = nullptr;
+
+ while (rec || resolver->GetHostToLookup(&rec)) {
+ LOG(("DNS lookup thread - Calling getaddrinfo for host [%s%s%s].\n",
+ LOG_HOST(rec->host, rec->netInterface)));
+
+ TimeStamp startTime = TimeStamp::Now();
+#if TTL_AVAILABLE
+ bool getTtl = rec->mGetTtl;
+#else
+ bool getTtl = false;
+#endif
+
+ nsresult status = GetAddrInfo(rec->host, rec->af, rec->flags, rec->netInterface,
+ &ai, getTtl);
+#if defined(RES_RETRY_ON_FAILURE)
+ if (NS_FAILED(status) && rs.Reset()) {
+ status = GetAddrInfo(rec->host, rec->af, rec->flags, rec->netInterface, &ai,
+ getTtl);
+ }
+#endif
+
+ { // obtain lock to check shutdown and manage inter-module telemetry
+ MutexAutoLock lock(resolver->mLock);
+
+ if (!resolver->mShutdown) {
+ TimeDuration elapsed = TimeStamp::Now() - startTime;
+ uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds());
+
+ if (NS_SUCCEEDED(status)) {
+ Telemetry::ID 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);
+ }
+ }
+ }
+
+ // OnLookupComplete may release "rec", long before we lose it.
+ LOG(("DNS lookup thread - lookup completed for host [%s%s%s]: %s.\n",
+ LOG_HOST(rec->host, rec->netInterface),
+ ai ? "success" : "failure: unknown host"));
+
+ if (LOOKUP_RESOLVEAGAIN == resolver->OnLookupComplete(rec, status, ai)) {
+ // leave 'rec' assigned and loop to make a renewed host resolve
+ LOG(("DNS lookup thread - Re-resolving host [%s%s%s].\n",
+ LOG_HOST(rec->host, rec->netInterface)));
+ } else {
+ rec = nullptr;
+ }
+ }
+ resolver->mThreadCount--;
+ NS_RELEASE(resolver);
+ LOG(("DNS lookup thread - queue empty, thread finished.\n"));
+}
+
+nsresult
+nsHostResolver::Create(uint32_t maxCacheEntries,
+ uint32_t defaultCacheEntryLifetime,
+ uint32_t defaultGracePeriod,
+ nsHostResolver **result)
+{
+ auto *res = new nsHostResolver(maxCacheEntries, defaultCacheEntryLifetime,
+ defaultGracePeriod);
+ NS_ADDREF(res);
+
+ nsresult rv = res->Init();
+ if (NS_FAILED(rv))
+ NS_RELEASE(res);
+
+ *result = res;
+ return rv;
+}
+
+void
+nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries> *args)
+{
+ for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) {
+ // We don't pay attention to address literals, only resolved domains.
+ // Also require a host.
+ auto entry = static_cast<nsHostDBEnt*>(iter.Get());
+ nsHostRecord* rec = entry->rec;
+ MOZ_ASSERT(rec, "rec should never be null here!");
+ if (!rec || !rec->addr_info || !rec->host) {
+ continue;
+ }
+
+ DNSCacheEntries info;
+ info.hostname = rec->host;
+ info.family = rec->af;
+ info.netInterface = rec->netInterface;
+ info.expiration =
+ (int64_t)(rec->mValidEnd - TimeStamp::NowLoRes()).ToSeconds();
+ if (info.expiration <= 0) {
+ // We only need valid DNS cache entries
+ continue;
+ }
+
+ {
+ MutexAutoLock lock(rec->addr_info_lock);
+
+ NetAddr *addr = nullptr;
+ NetAddrElement *addrElement = rec->addr_info->mAddresses.getFirst();
+ if (addrElement) {
+ addr = &addrElement->mAddress;
+ }
+ while (addr) {
+ char buf[kIPv6CStrBufSize];
+ if (NetAddrToString(addr, buf, sizeof(buf))) {
+ info.hostaddr.AppendElement(buf);
+ }
+ addr = nullptr;
+ addrElement = addrElement->getNext();
+ if (addrElement) {
+ addr = &addrElement->mAddress;
+ }
+ }
+ }
+
+ args->AppendElement(info);
+ }
+}
diff --git a/netwerk/dns/nsHostResolver.h b/netwerk/dns/nsHostResolver.h
new file mode 100644
index 0000000000..4c37ff0d3c
--- /dev/null
+++ b/netwerk/dns/nsHostResolver.h
@@ -0,0 +1,372 @@
+/* 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 nsHostResolver_h__
+#define nsHostResolver_h__
+
+#include "nscore.h"
+#include "prclist.h"
+#include "prnetdb.h"
+#include "PLDHashTable.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+#include "nsISupportsImpl.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "GetAddrInfo.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "mozilla/TimeStamp.h"
+
+class nsHostResolver;
+class nsHostRecord;
+class nsResolveHostCallback;
+
+#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 char *host;
+ uint16_t flags;
+ uint16_t af;
+ const char *netInterface;
+};
+
+/**
+ * nsHostRecord - ref counted object type stored in host resolver cache.
+ */
+class nsHostRecord : public PRCList, public nsHostKey
+{
+ typedef mozilla::Mutex Mutex;
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHostRecord)
+
+ /* instantiates a new host record */
+ static nsresult Create(const nsHostKey *key, nsHostRecord **record);
+
+ /* 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::OnLookupComplete);
+ * 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| */
+ mozilla::net::AddrInfo *addr_info;
+ mozilla::net::NetAddr *addr;
+ bool negative; /* 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. */
+
+ enum ExpirationStatus {
+ EXP_VALID,
+ EXP_GRACE,
+ EXP_EXPIRED,
+ };
+
+ ExpirationStatus CheckExpiration(const mozilla::TimeStamp& now) const;
+
+ // 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;
+
+ // 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;
+
+ // hold addr_info_lock when calling the blacklist functions
+ bool Blacklisted(mozilla::net::NetAddr *query);
+ void ResetBlacklist();
+ void ReportUnusable(mozilla::net::NetAddr *addr);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ enum DnsPriority {
+ DNS_PRIORITY_LOW,
+ DNS_PRIORITY_MEDIUM,
+ DNS_PRIORITY_HIGH,
+ };
+ static DnsPriority GetPriority(uint16_t aFlags);
+
+ bool RemoveOrRefresh(); // Mark records currently being resolved as needed
+ // to resolve again.
+
+private:
+ friend class nsHostResolver;
+
+
+ PRCList callbacks; /* list of callbacks */
+
+ bool resolving; /* true if this record is being resolved, which means
+ * that it is either on the pending queue or owned by
+ * one of the worker threads. */
+
+ bool onQueue; /* true if pending and on the queue (not yet given to getaddrinfo())*/
+ bool usingAnyThread; /* true if off queue and contributing to mActiveAnyThreadCount */
+ bool mDoomed; /* explicitly expired */
+
+#if TTL_AVAILABLE
+ bool mGetTtl;
+#endif
+
+ // The number of times ReportUnusable() has been called in the record's
+ // lifetime.
+ uint32_t mBlacklistedCount;
+
+ // when the results from this resolve is returned, it is not to be
+ // trusted, but instead a new resolve must be made!
+ bool mResolveAgain;
+
+ // 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> mBlacklistedItems;
+
+ explicit nsHostRecord(const nsHostKey *key); /* use Create() instead */
+ ~nsHostRecord();
+};
+
+/**
+ * ResolveHost callback object. It's PRCList members are used by
+ * the nsHostResolver and should not be used by anything else.
+ */
+class NS_NO_VTABLE nsResolveHostCallback : public PRCList
+{
+public:
+ /**
+ * OnLookupComplete
+ *
+ * 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 OnLookupComplete(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;
+};
+
+/**
+ * nsHostResolver - an asynchronous host name resolver.
+ */
+class nsHostResolver
+{
+ typedef mozilla::CondVar CondVar;
+ typedef mozilla::Mutex Mutex;
+
+public:
+ /**
+ * host resolver instances are reference counted.
+ */
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHostResolver)
+
+ /**
+ * 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 **resolver);
+
+ /**
+ * 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 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 char *hostname,
+ uint16_t flags,
+ uint16_t af,
+ const char *netInterface,
+ nsResolveHostCallback *callback);
+
+ /**
+ * removes the specified callback from the nsHostRecord for the given
+ * hostname, 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 char *hostname,
+ uint16_t flags,
+ uint16_t af,
+ const char *netInterface,
+ nsResolveHostCallback *callback,
+ nsresult status);
+
+ /**
+ * Cancels an async request associated with the hostname, 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 char *host,
+ uint16_t flags,
+ uint16_t af,
+ const char *netInterface,
+ 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
+ };
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ /**
+ * Flush the DNS cache.
+ */
+ void FlushCache();
+
+private:
+ explicit nsHostResolver(uint32_t maxCacheEntries,
+ uint32_t defaultCacheEntryLifetime,
+ uint32_t defaultGracePeriod);
+ ~nsHostResolver();
+
+ nsresult Init();
+ nsresult IssueLookup(nsHostRecord *);
+ bool GetHostToLookup(nsHostRecord **m);
+
+ enum LookupStatus {
+ LOOKUP_OK,
+ LOOKUP_RESOLVEAGAIN,
+ };
+
+ LookupStatus OnLookupComplete(nsHostRecord *, nsresult, mozilla::net::AddrInfo *);
+ void DeQueue(PRCList &aQ, nsHostRecord **aResult);
+ void ClearPendingQueue(PRCList *aPendingQueue);
+ 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 char *host);
+
+ static void MoveQueue(nsHostRecord *aRec, PRCList &aDestQ);
+
+ static void ThreadFunc(void *);
+
+ 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 mIdleThreadCV;
+ PLDHashTable mDB;
+ PRCList mHighQ;
+ PRCList mMediumQ;
+ PRCList mLowQ;
+ PRCList mEvictionQ;
+ uint32_t mEvictionQSize;
+ PRTime mCreationTime;
+ PRIntervalTime mLongIdleTimeout;
+ PRIntervalTime mShortIdleTimeout;
+
+ mozilla::Atomic<bool> mShutdown;
+ mozilla::Atomic<uint32_t> mNumIdleThreads;
+ mozilla::Atomic<uint32_t> mThreadCount;
+ mozilla::Atomic<uint32_t> mActiveAnyThreadCount;
+ mozilla::Atomic<uint32_t> mPendingCount;
+
+ // Set the expiration time stamps appropriately.
+ void PrepareRecordExpiration(nsHostRecord* 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..3e1ae7a731
--- /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/nsIDNSListener.idl b/netwerk/dns/nsIDNSListener.idl
new file mode 100644
index 0000000000..46c241005e
--- /dev/null
+++ b/netwerk/dns/nsIDNSListener.idl
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICancelable;
+interface nsIDNSRecord;
+
+/**
+ * nsIDNSListener
+ */
+[scriptable, function, 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.
+ * @param aStatus
+ * if the lookup failed, this parameter gives the reason.
+ */
+ void onLookupComplete(in nsICancelable aRequest,
+ in nsIDNSRecord aRecord,
+ in nsresult aStatus);
+};
+
+/**
+ * nsIDNSListenerProxy:
+ *
+ * Must be implemented by classes that wrap the original listener passed to
+ * nsIDNSService.AsyncResolve, so we have access to original listener for
+ * comparison purposes.
+ */
+[uuid(60eff0e4-6f7c-493c-add9-1cbea59063ad)]
+interface nsIDNSListenerProxy : nsISupports
+{
+ /*
+ * The original nsIDNSListener which requested hostname resolution.
+ */
+ readonly attribute nsIDNSListener originalListener;
+};
diff --git a/netwerk/dns/nsIDNSRecord.idl b/netwerk/dns/nsIDNSRecord.idl
new file mode 100644
index 0000000000..b3158bd3be
--- /dev/null
+++ b/netwerk/dns/nsIDNSRecord.idl
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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
+{
+ /**
+ * @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
+ */
+ 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);
+};
diff --git a/netwerk/dns/nsIDNSService.idl b/netwerk/dns/nsIDNSService.idl
new file mode 100644
index 0000000000..2704790dc3
--- /dev/null
+++ b/netwerk/dns/nsIDNSService.idl
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICancelable;
+interface nsIEventTarget;
+interface nsIDNSRecord;
+interface nsIDNSListener;
+
+%{C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla { namespace net {
+ struct DNSCacheEntries;
+} }
+%}
+
+[ptr] native EntriesArray(nsTArray<mozilla::net::DNSCacheEntries>);
+
+/**
+ * nsIDNSService
+ */
+[scriptable, uuid(de5642c6-61fc-4fcf-9a47-03226b0d4e21)]
+interface nsIDNSService : nsISupports
+{
+ /**
+ * kicks off an asynchronous host lookup.
+ *
+ * @param aHostName
+ * the hostname or IP-address-literal to resolve.
+ * @param aFlags
+ * a bitwise OR of the RESOLVE_ prefixed constants defined below.
+ * @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).
+ *
+ * @return An object that can be used to cancel the host lookup.
+ */
+ nsICancelable asyncResolve(in AUTF8String aHostName,
+ in unsigned long aFlags,
+ in nsIDNSListener aListener,
+ in nsIEventTarget aListenerTarget);
+
+ /**
+ * Attempts to cancel a previously requested async DNS lookup
+ *
+ * @param aHostName
+ * the hostname or IP-address-literal to resolve.
+ * @param aFlags
+ * a bitwise OR of the RESOLVE_ prefixed constants defined below.
+ * @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
+ *
+ * @return An object that can be used to cancel the host lookup.
+ */
+ void cancelAsyncResolve(in AUTF8String aHostName,
+ in unsigned long aFlags,
+ in nsIDNSListener aListener,
+ in nsresult aReason);
+
+ /**
+ * called to synchronously resolve a hostname. warning this method may
+ * block the calling thread for a long period of time. it is extremely
+ * unwise to call this function on the UI thread of an application.
+ *
+ * @param aHostName
+ * the hostname or IP-address-literal to resolve.
+ * @param aFlags
+ * a bitwise OR of the RESOLVE_ prefixed constants defined below.
+ *
+ * @return DNS record corresponding to the given hostname.
+ * @throws NS_ERROR_UNKNOWN_HOST if host could not be resolved.
+ */
+ nsIDNSRecord resolve(in AUTF8String aHostName,
+ in unsigned long aFlags);
+
+ /**
+ * kicks off an asynchronous host lookup.
+ *
+ * This function is identical to asyncResolve except an additional
+ * parameter aNetwortInterface. If parameter aNetworkInterface is an empty
+ * string function will return the same result as asyncResolve.
+ * Setting aNetworkInterface value make only sense for gonk,because it
+ * an per networking interface query is possible.
+ */
+ nsICancelable asyncResolveExtended(in AUTF8String aHostName,
+ in unsigned long aFlags,
+ in AUTF8String aNetworkInterface,
+ in nsIDNSListener aListener,
+ in nsIEventTarget aListenerTarget);
+
+ /**
+ * Attempts to cancel a previously requested async DNS lookup
+ * This is an extended versin with a additional parameter aNetworkInterface
+ */
+ void cancelAsyncResolveExtended(in AUTF8String aHostName,
+ in unsigned long aFlags,
+ in AUTF8String aNetworkInterface,
+ in nsIDNSListener aListener,
+ in nsresult aReason);
+
+ /**
+ * 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);
+
+ /**
+ * @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);
+};
diff --git a/netwerk/dns/nsIDNService.cpp b/netwerk/dns/nsIDNService.cpp
new file mode 100644
index 0000000000..d4f31027e6
--- /dev/null
+++ b/netwerk/dns/nsIDNService.cpp
@@ -0,0 +1,959 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDNService.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "nsUnicodeScriptCodes.h"
+#include "harfbuzz/hb.h"
+#include "nsIServiceManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIObserverService.h"
+#include "nsISupportsPrimitives.h"
+#include "punycode.h"
+
+#ifdef IDNA2008
+// 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"
+#endif
+
+using namespace mozilla::unicode;
+
+//-----------------------------------------------------------------------------
+// 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_IDNBLACKLIST "network.IDN.blacklist_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"
+
+inline bool isOnlySafeChars(const nsAFlatString& in,
+ const nsAFlatString& blacklist)
+{
+ return (blacklist.IsEmpty() ||
+ in.FindCharInSet(blacklist) == kNotFound);
+}
+
+//-----------------------------------------------------------------------------
+// nsIDNService
+//-----------------------------------------------------------------------------
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsIDNService,
+ nsIIDNService,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsresult nsIDNService::Init()
+{
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ prefs->GetBranch(NS_NET_PREF_IDNWHITELIST, getter_AddRefs(mIDNWhitelistPrefBranch));
+
+ nsCOMPtr<nsIPrefBranch> prefInternal(do_QueryInterface(prefs));
+ if (prefInternal) {
+ prefInternal->AddObserver(NS_NET_PREF_IDNBLACKLIST, this, true);
+ prefInternal->AddObserver(NS_NET_PREF_SHOWPUNYCODE, this, true);
+ prefInternal->AddObserver(NS_NET_PREF_IDNRESTRICTION, this, true);
+ prefInternal->AddObserver(NS_NET_PREF_IDNUSEWHITELIST, this, true);
+ prefsChanged(prefInternal, nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::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)
+ prefsChanged(prefBranch, aData);
+ }
+ return NS_OK;
+}
+
+void nsIDNService::prefsChanged(nsIPrefBranch *prefBranch, const char16_t *pref)
+{
+ if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNBLACKLIST).Equals(pref)) {
+ nsCOMPtr<nsISupportsString> blacklist;
+ nsresult rv = prefBranch->GetComplexValue(NS_NET_PREF_IDNBLACKLIST,
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(blacklist));
+ if (NS_SUCCEEDED(rv))
+ blacklist->ToString(getter_Copies(mIDNBlacklist));
+ else
+ mIDNBlacklist.Truncate();
+ }
+ if (!pref || NS_LITERAL_STRING(NS_NET_PREF_SHOWPUNYCODE).Equals(pref)) {
+ bool val;
+ if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_SHOWPUNYCODE, &val)))
+ mShowPunycode = val;
+ }
+ if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNUSEWHITELIST).Equals(pref)) {
+ bool val;
+ if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_IDNUSEWHITELIST,
+ &val)))
+ mIDNUseWhitelist = val;
+ }
+ if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNRESTRICTION).Equals(pref)) {
+ nsXPIDLCString profile;
+ if (NS_FAILED(prefBranch->GetCharPref(NS_NET_PREF_IDNRESTRICTION,
+ getter_Copies(profile)))) {
+ profile.Truncate();
+ }
+ if (profile.EqualsLiteral("moderate")) {
+ mRestrictionProfile = eModeratelyRestrictiveProfile;
+ } else if (profile.EqualsLiteral("high")) {
+ mRestrictionProfile = eHighlyRestrictiveProfile;
+ } else {
+ mRestrictionProfile = eASCIIOnlyProfile;
+ }
+ }
+}
+
+nsIDNService::nsIDNService()
+ : mShowPunycode(false)
+ , mIDNUseWhitelist(false)
+{
+#ifdef IDNA2008
+ 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);
+#else
+ if (idn_success != idn_nameprep_create(nullptr, &mNamePrepHandle))
+ mNamePrepHandle = nullptr;
+
+ mNormalizer = do_GetService(NS_UNICODE_NORMALIZER_CONTRACTID);
+ /* member initializers and constructor code */
+#endif
+}
+
+nsIDNService::~nsIDNService()
+{
+#ifdef IDNA2008
+ uidna_close(mIDNA);
+#else
+ idn_nameprep_destroy(mNamePrepHandle);
+#endif
+}
+
+#ifdef IDNA2008
+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
+ 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;
+}
+#endif
+
+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.
+
+ 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++ == '.') {
+ if (NS_FAILED(decodeACE(Substring(input, offset, len - 1), decodedBuf,
+ flag))) {
+ _retval.Assign(input);
+ return NS_OK;
+ }
+
+ _retval.Append(decodedBuf);
+ _retval.Append('.');
+ offset += len;
+ len = 0;
+ }
+ }
+ // decode the last node
+ if (len) {
+ if (NS_FAILED(decodeACE(Substring(input, offset, len), decodedBuf,
+ flag)))
+ _retval.Assign(input);
+ 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;
+}
+
+NS_IMETHODIMP nsIDNService::ConvertToDisplayIDN(const nsACString & input, bool * _isASCII, nsACString & _retval)
+{
+ // If host is ACE, then convert to UTF-8 if the host is in the IDN whitelist.
+ // Else, if host is already UTF-8, then make sure it is normalized per IDN.
+
+ nsresult rv = NS_OK;
+
+ // Even if the hostname is not ASCII, individual labels may still be ACE, so
+ // test IsACE before testing IsASCII
+ bool isACE;
+ IsACE(input, &isACE);
+
+ if (IsASCII(input)) {
+ // first, canonicalize the host to lowercase, for whitelist lookup
+ _retval = input;
+ ToLowerCase(_retval);
+
+ if (isACE && !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_HIGH_SURROGATE(curChar) &&
+ NS_IS_LOW_SURROGATE(*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;
+}
+
+#ifndef IDNA2008
+static void ucs4toUtf16(const uint32_t *in, nsAString& out)
+{
+ while (*in) {
+ if (!IS_IN_BMP(*in)) {
+ out.Append((char16_t) H_SURROGATE(*in));
+ out.Append((char16_t) L_SURROGATE(*in));
+ }
+ else
+ out.Append((char16_t) *in);
+ in++;
+ }
+}
+#endif
+
+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)
+{
+#ifdef IDNA2008
+ return IDNA2008StringPrep(in, out, flag);
+#else
+ if (!mNamePrepHandle || !mNormalizer)
+ return NS_ERROR_FAILURE;
+
+ uint32_t ucs4Buf[kMaxDNSNodeLen + 1];
+ uint32_t ucs4Len;
+ nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // map
+ idn_result_t idn_err;
+
+ uint32_t namePrepBuf[kMaxDNSNodeLen * 3]; // map up to three characters
+ idn_err = idn_nameprep_map(mNamePrepHandle, (const uint32_t *) ucs4Buf,
+ (uint32_t *) namePrepBuf, kMaxDNSNodeLen * 3);
+ NS_ENSURE_TRUE(idn_err == idn_success, NS_ERROR_MALFORMED_URI);
+
+ nsAutoString namePrepStr;
+ ucs4toUtf16(namePrepBuf, namePrepStr);
+ if (namePrepStr.Length() >= kMaxDNSNodeLen)
+ return NS_ERROR_MALFORMED_URI;
+
+ // normalize
+ nsAutoString normlizedStr;
+ rv = mNormalizer->NormalizeUnicodeNFKC(namePrepStr, normlizedStr);
+ if (normlizedStr.Length() >= kMaxDNSNodeLen)
+ return NS_ERROR_MALFORMED_URI;
+
+ // set the result string
+ out.Assign(normlizedStr);
+
+ if (flag == eStringPrepIgnoreErrors) {
+ return NS_OK;
+ }
+
+ // prohibit
+ const uint32_t *found = nullptr;
+ idn_err = idn_nameprep_isprohibited(mNamePrepHandle,
+ (const uint32_t *) ucs4Buf, &found);
+ if (idn_err != idn_success || found) {
+ rv = NS_ERROR_MALFORMED_URI;
+ } else {
+ // check bidi
+ idn_err = idn_nameprep_isvalidbidi(mNamePrepHandle,
+ (const uint32_t *) ucs4Buf, &found);
+ if (idn_err != idn_success || found) {
+ rv = NS_ERROR_MALFORMED_URI;
+ } else if (flag == eStringPrepForUI) {
+ // check unassigned code points
+ idn_err = idn_nameprep_isunassigned(mNamePrepHandle,
+ (const uint32_t *) ucs4Buf, &found);
+ if (idn_err != idn_success || found) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ }
+ }
+
+ if (flag == eStringPrepForDNS && NS_FAILED(rv)) {
+ out.Truncate();
+ }
+
+ return rv;
+#endif
+}
+
+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.Replace(index, 1, NS_LITERAL_STRING("."));
+ 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;
+#ifdef IDNA2008
+ nsresult result = IDNA2008ToUnicode(in, utf16);
+ NS_ENSURE_SUCCESS(result, result);
+#else
+ // RFC 3490 - 4.2 ToUnicode
+ // The ToUnicode output never contains more code points than its input.
+ punycode_uint output_length = in.Length() - kACEPrefixLen + 1;
+ auto *output = new punycode_uint[output_length];
+ NS_ENSURE_TRUE(output, NS_ERROR_OUT_OF_MEMORY);
+
+ enum punycode_status status = punycode_decode(in.Length() - kACEPrefixLen,
+ PromiseFlatCString(in).get() + kACEPrefixLen,
+ &output_length,
+ output,
+ nullptr);
+ if (status != punycode_success) {
+ delete [] output;
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // UCS4 -> UTF8
+ output[output_length] = 0;
+ ucs4toUtf16(output, utf16);
+ delete [] output;
+#endif
+ 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 (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 (!isOnlySafeChars(PromiseFlatString(label), mIDNBlacklist)) {
+ 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 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 (NS_IS_HIGH_SURROGATE(ch) && current != end &&
+ NS_IS_LOW_SURROGATE(*current)) {
+ ch = SURROGATE_TO_UCS4(ch, *current++);
+ }
+
+ // Check for restricted characters; aspirational scripts are NOT permitted,
+ // in anticipation of the category being merged into Limited-Use scripts
+ // in the upcoming (Unicode 10.0-based) revision of UAX #31.
+ XidmodType xm = GetIdentifierModification(ch);
+ if (xm != XIDMOD_RECOMMENDED &&
+ xm != XIDMOD_INCLUSION) {
+ return false;
+ }
+
+ // Check for mixed script
+ Script script = GetScriptCode(ch);
+ if (script != Script::COMMON &&
+ script != Script::INHERITED &&
+ script != lastScript) {
+ if (illegalScriptCombo(script, savedScript)) {
+ return false;
+ }
+ lastScript = script;
+ }
+
+ // Check for mixed numbering systems
+ if (GetGeneralCategory(ch) ==
+ 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;
+ }
+ }
+
+ // Check for consecutive non-spacing marks
+ if (previousChar != 0 &&
+ previousChar == ch &&
+ GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
+ return false;
+ }
+
+ // 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 = sizeof(scriptTable) / sizeof(int32_t);
+ 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 (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..19aa94da05
--- /dev/null
+++ b/netwerk/dns/nsIDNService.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIDNService_h__
+#define nsIDNService_h__
+
+#include "nsIIDNService.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsUnicodeScriptCodes.h"
+#include "nsWeakReference.h"
+
+#ifdef IDNA2008
+#include "unicode/uidna.h"
+#else
+#include "nsIUnicodeNormalizer.h"
+#include "nsIDNKitInterface.h"
+#endif
+
+#include "nsString.h"
+
+class nsIPrefBranch;
+
+//-----------------------------------------------------------------------------
+// nsIDNService
+//-----------------------------------------------------------------------------
+
+class nsIDNService final : public nsIIDNService,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIDNSERVICE
+ NS_DECL_NSIOBSERVER
+
+ 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(nsIPrefBranch *prefBranch, const char16_t *pref);
+
+ /**
+ * 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);
+
+#ifdef IDNA2008
+ /**
+ * 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;
+#else
+ idn_nameprep_t mNamePrepHandle;
+ nsCOMPtr<nsIUnicodeNormalizer> mNormalizer;
+#endif
+ nsXPIDLString mIDNBlacklist;
+
+ /**
+ * 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
+ */
+ 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
+ };
+ restrictionProfile mRestrictionProfile;
+ nsCOMPtr<nsIPrefBranch> mIDNWhitelistPrefBranch;
+ bool mIDNUseWhitelist;
+};
+
+#endif // nsIDNService_h__
diff --git a/netwerk/dns/nsIEffectiveTLDService.idl b/netwerk/dns/nsIEffectiveTLDService.idl
new file mode 100644
index 0000000000..adfbae8a02
--- /dev/null
+++ b/netwerk/dns/nsIEffectiveTLDService.idl
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+};
+
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/nsPIDNSService.idl b/netwerk/dns/nsPIDNSService.idl
new file mode 100644
index 0000000000..9bed0555c5
--- /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, 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..a97b20948c
--- /dev/null
+++ b/netwerk/dns/prepare_tlds.py
@@ -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/.
+
+import codecs
+import encodings.idna
+import re
+import sys
+
+"""
+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 in line:
+ 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)
+ entries.append(entry)
+
+ # Sort the entries so we can use binary search on them.
+ entries.sort(key=EffectiveTLDEntry.domain)
+
+ return entries
+
+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)
+ 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):
+ """
+ effective_tld_filename is the effective TLD file to parse.
+ A C++ array of { domain, exception, wild } entries representing the
+ eTLD file is then printed to output.
+ """
+
+ def boolStr(b):
+ if b:
+ return "true"
+ return "false"
+
+ for etld in getEffectiveTLDs(effective_tld_filename):
+ exception = boolStr(etld.exception())
+ wild = boolStr(etld.wild())
+ output.write('ETLD_ENTRY("%s", %s, %s)\n' % (etld.domain(), exception, wild))
+
+if __name__ == '__main__':
+ main(sys.stdout, sys.argv[1])
diff --git a/netwerk/dns/punycode.c b/netwerk/dns/punycode.c
new file mode 100644
index 0000000000..f905e5272a
--- /dev/null
+++ b/netwerk/dns/punycode.c
@@ -0,0 +1,289 @@
+/*
+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..459c6fd755
--- /dev/null
+++ b/netwerk/dns/punycode.h
@@ -0,0 +1,108 @@
+/*
+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/ipc/ChannelEventQueue.cpp b/netwerk/ipc/ChannelEventQueue.cpp
new file mode 100644
index 0000000000..a4dbae7d5b
--- /dev/null
+++ b/netwerk/ipc/ChannelEventQueue.cpp
@@ -0,0 +1,98 @@
+/* -*- 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 "nsISupports.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/Unused.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelEvent*
+ChannelEventQueue::TakeEvent()
+{
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mFlushing);
+
+ if (mSuspended || mEventQueue.IsEmpty()) {
+ return nullptr;
+ }
+
+ UniquePtr<ChannelEvent> event(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
+
+ // Prevent flushed events from flushing the queue recursively
+ {
+ MutexAutoLock lock(mMutex);
+ mFlushing = true;
+ }
+
+ while (true) {
+ UniquePtr<ChannelEvent> event(TakeEvent());
+ if (!event) {
+ break;
+ }
+
+ event->Run();
+ }
+
+ MutexAutoLock lock(mMutex);
+ mFlushing = false;
+}
+
+void
+ChannelEventQueue::Resume()
+{
+ MutexAutoLock lock(mMutex);
+
+ // Resuming w/o suspend: error in debug mode, ignore in build
+ MOZ_ASSERT(mSuspendCount > 0);
+ if (mSuspendCount <= 0) {
+ return;
+ }
+
+ if (!--mSuspendCount) {
+ RefPtr<Runnable> event =
+ NewRunnableMethod(this, &ChannelEventQueue::CompleteResume);
+ if (mTargetThread) {
+ mTargetThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ } else {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ Unused << NS_WARN_IF(NS_FAILED(NS_DispatchToCurrentThread(event.forget())));
+ }
+ }
+}
+
+nsresult
+ChannelEventQueue::RetargetDeliveryTo(nsIEventTarget* aTargetThread)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(!mTargetThread);
+ MOZ_RELEASE_ASSERT(aTargetThread);
+
+ mTargetThread = do_QueryInterface(aTargetThread);
+ MOZ_RELEASE_ASSERT(mTargetThread);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/ChannelEventQueue.h b/netwerk/ipc/ChannelEventQueue.h
new file mode 100644
index 0000000000..a843decabc
--- /dev/null
+++ b/netwerk/ipc/ChannelEventQueue.h
@@ -0,0 +1,237 @@
+/* -*- 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 "nsAutoPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
+
+class nsISupports;
+class nsIEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class ChannelEvent
+{
+ public:
+ ChannelEvent() { MOZ_COUNT_CTOR(ChannelEvent); }
+ virtual ~ChannelEvent() { MOZ_COUNT_DTOR(ChannelEvent); }
+ virtual void Run() = 0;
+};
+
+// 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)
+ , mForced(false)
+ , mFlushing(false)
+ , mOwner(owner)
+ , mMutex("ChannelEventQueue::mMutex")
+ {}
+
+ // 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);
+ inline nsresult 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.
+ inline 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();
+
+ // Retargets delivery of events to the target thread specified.
+ nsresult RetargetDeliveryTo(nsIEventTarget* aTargetThread);
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~ChannelEventQueue()
+ {
+ }
+
+ inline void MaybeFlushQueue();
+ void FlushQueue();
+ inline void CompleteResume();
+
+ ChannelEvent* TakeEvent();
+
+ nsTArray<UniquePtr<ChannelEvent>> mEventQueue;
+
+ uint32_t mSuspendCount;
+ bool mSuspended;
+ bool mForced;
+ bool mFlushing;
+
+ // Keep ptr to avoid refcount cycle: only grab ref during flushing.
+ nsISupports *mOwner;
+
+ Mutex mMutex;
+
+ // EventTarget for delivery of events to the correct thread.
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+
+ friend class AutoEventEnqueuer;
+};
+
+inline void
+ChannelEventQueue::RunOrEnqueue(ChannelEvent* aCallback,
+ bool aAssertionWhenNotQueued)
+{
+ MOZ_ASSERT(aCallback);
+
+ // To avoid leaks.
+ UniquePtr<ChannelEvent> event(aCallback);
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ bool enqueue = mForced || mSuspended || mFlushing;
+ MOZ_ASSERT(enqueue == true || mEventQueue.IsEmpty(),
+ "Should always enqueue if ChannelEventQueue not empty");
+
+ if (enqueue) {
+ mEventQueue.AppendElement(Move(event));
+ return;
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(!aAssertionWhenNotQueued);
+ event->Run();
+}
+
+inline void
+ChannelEventQueue::StartForcedQueueing()
+{
+ MutexAutoLock lock(mMutex);
+ mForced = true;
+}
+
+inline void
+ChannelEventQueue::EndForcedQueueing()
+{
+ {
+ MutexAutoLock lock(mMutex);
+ mForced = false;
+ }
+
+ MaybeFlushQueue();
+}
+
+inline nsresult
+ChannelEventQueue::PrependEvents(nsTArray<UniquePtr<ChannelEvent>>& aEvents)
+{
+ MutexAutoLock lock(mMutex);
+
+ UniquePtr<ChannelEvent>* newEvents =
+ mEventQueue.InsertElementsAt(0, aEvents.Length());
+ if (!newEvents) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < aEvents.Length(); i++) {
+ newEvents[i] = Move(aEvents[i]);
+ }
+
+ return NS_OK;
+}
+
+inline void
+ChannelEventQueue::Suspend()
+{
+ MutexAutoLock lock(mMutex);
+
+ mSuspended = true;
+ mSuspendCount++;
+}
+
+inline void
+ChannelEventQueue::CompleteResume()
+{
+ {
+ 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;
+ }
+ }
+
+ 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 = !mForced && !mFlushing && !mSuspended &&
+ !mEventQueue.IsEmpty();
+ }
+
+ 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) {
+ mEventQueue->StartForcedQueueing();
+ }
+ ~AutoEventEnqueuer() {
+ mEventQueue->EndForcedQueueing();
+ }
+ private:
+ RefPtr<ChannelEventQueue> mEventQueue;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh
new file mode 100644
index 0000000000..9365397d19
--- /dev/null
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -0,0 +1,196 @@
+/* -*- 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 PRtspChannel;
+include protocol PSendStream;
+include BlobTypes;
+include URIParams;
+include IPCStream;
+include InputStreamParams;
+include PBackgroundSharedTypes;
+
+using mozilla::NeckoOriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
+using RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h";
+using struct nsHttpAtom from "nsHttp.h";
+using class nsHttpResponseHead from "nsHttpResponseHead.h";
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// LoadInfo IPDL structs
+//-----------------------------------------------------------------------------
+
+struct LoadInfoArgs
+{
+ OptionalPrincipalInfo requestingPrincipalInfo;
+ PrincipalInfo triggeringPrincipalInfo;
+ OptionalPrincipalInfo principalToInheritInfo;
+ uint32_t securityFlags;
+ uint32_t contentPolicyType;
+ uint32_t tainting;
+ bool upgradeInsecureRequests;
+ bool verifySignedContent;
+ bool enforceSRI;
+ bool forceInheritPrincipalDropped;
+ uint64_t innerWindowID;
+ uint64_t outerWindowID;
+ uint64_t parentOuterWindowID;
+ uint64_t frameOuterWindowID;
+ bool enforceSecurity;
+ bool initialSecurityCheckDone;
+ bool isInThirdPartyContext;
+ NeckoOriginAttributes originAttributes;
+ PrincipalInfo[] redirectChainIncludingInternalRedirects;
+ PrincipalInfo[] redirectChain;
+ nsCString[] corsUnsafeHeaders;
+ bool forcePreflight;
+ bool isPreflight;
+ bool forceHSTSPriming;
+ bool mixedContentWouldBlock;
+};
+
+/**
+ * Not every channel necessarily has a loadInfo attached.
+ */
+union OptionalLoadInfoArgs
+{
+ void_t;
+ LoadInfoArgs;
+};
+
+//-----------------------------------------------------------------------------
+// HTTP IPDL structs
+//-----------------------------------------------------------------------------
+
+union OptionalHttpResponseHead
+{
+ void_t;
+ nsHttpResponseHead;
+};
+
+struct CorsPreflightArgs
+{
+ nsCString[] unsafeHeaders;
+};
+
+union OptionalCorsPreflightArgs
+{
+ void_t;
+ CorsPreflightArgs;
+};
+
+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?
+ OptionalURIParams original;
+ OptionalURIParams doc;
+ OptionalURIParams referrer;
+ uint32_t referrerPolicy;
+ OptionalURIParams apiRedirectTo;
+ OptionalURIParams topWindowURI;
+ uint32_t loadFlags;
+ RequestHeaderTuples requestHeaders;
+ nsCString requestMethod;
+ OptionalIPCStream uploadStream;
+ bool uploadStreamHasHeaders;
+ uint16_t priority;
+ uint32_t classOfService;
+ uint8_t redirectionLimit;
+ bool allowPipelining;
+ bool allowSTS;
+ uint32_t thirdPartyFlags;
+ bool resumeAt;
+ uint64_t startPos;
+ nsCString entityID;
+ bool chooseApplicationCache;
+ nsCString appCacheClientID;
+ bool allowSpdy;
+ bool allowAltSvc;
+ bool beConservative;
+ OptionalLoadInfoArgs loadInfo;
+ OptionalHttpResponseHead synthesizedResponseHead;
+ nsCString synthesizedSecurityInfoSerialization;
+ uint32_t cacheKey;
+ nsCString requestContextID;
+ OptionalCorsPreflightArgs preflightArgs;
+ uint32_t initialRwin;
+ bool blockAuthPrompt;
+ bool suspendAfterSynthesizeResponse;
+ bool allowStaleCacheContent;
+ nsCString contentTypeHint;
+ nsCString channelId;
+ uint64_t contentWindowId;
+ nsCString preferredAlternativeType;
+};
+
+struct HttpChannelConnectArgs
+{
+ uint32_t registrarId;
+ bool shouldIntercept;
+};
+
+union HttpChannelCreationArgs
+{
+ HttpChannelOpenArgs; // For AsyncOpen: the common case.
+ HttpChannelConnectArgs; // Used for redirected-to channels
+};
+
+//-----------------------------------------------------------------------------
+// FTP IPDL structs
+//-----------------------------------------------------------------------------
+
+struct FTPChannelOpenArgs
+{
+ URIParams uri;
+ uint64_t startPos;
+ nsCString entityID;
+ OptionalInputStreamParams uploadStream;
+ OptionalLoadInfoArgs loadInfo;
+};
+
+struct FTPChannelConnectArgs
+{
+ uint32_t channelId;
+};
+
+union FTPChannelCreationArgs
+{
+ FTPChannelOpenArgs; // For AsyncOpen: the common case.
+ FTPChannelConnectArgs; // Used for redirected-to channels
+};
+
+struct HttpChannelDiverterArgs
+{
+ PHttpChannel mChannel;
+ bool mApplyConversion;
+};
+
+union ChannelDiverterArgs
+{
+ HttpChannelDiverterArgs;
+ PFTPChannel;
+};
+
+//-----------------------------------------------------------------------------
+// RTSP IPDL structs
+//-----------------------------------------------------------------------------
+
+struct RtspChannelConnectArgs
+{
+ URIParams uri;
+ uint32_t channelId;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoChild.cpp b/netwerk/ipc/NeckoChild.cpp
new file mode 100644
index 0000000000..00827b5a08
--- /dev/null
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -0,0 +1,431 @@
+
+/* 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 "necko-config.h"
+#include "nsHttp.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/CookieServiceChild.h"
+#include "mozilla/net/WyciwygChannelChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+#include "mozilla/net/WebSocketChannelChild.h"
+#include "mozilla/net/WebSocketEventListenerChild.h"
+#include "mozilla/net/DNSRequestChild.h"
+#include "mozilla/net/ChannelDiverterChild.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"
+
+#ifdef NECKO_PROTOCOL_rtsp
+#include "mozilla/net/RtspControllerChild.h"
+#include "mozilla/net/RtspChannelChild.h"
+#endif
+#include "SerializedLoadContext.h"
+#include "nsIOService.h"
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsNetUtil.h"
+
+using mozilla::dom::TCPSocketChild;
+using mozilla::dom::TCPServerSocketChild;
+using mozilla::dom::UDPSocketChild;
+
+namespace mozilla {
+namespace net {
+
+PNeckoChild *gNeckoChild = nullptr;
+
+// C++ file contents
+NeckoChild::NeckoChild()
+{
+}
+
+NeckoChild::~NeckoChild()
+{
+ //Send__delete__(gNeckoChild);
+ gNeckoChild = nullptr;
+}
+
+void NeckoChild::InitNeckoChild()
+{
+ MOZ_ASSERT(IsNeckoChild(), "InitNeckoChild called by non-child!");
+
+ if (!gNeckoChild) {
+ mozilla::dom::ContentChild * cpc =
+ mozilla::dom::ContentChild::GetSingleton();
+ NS_ASSERTION(cpc, "Content Protocol is NULL!");
+ gNeckoChild = cpc->SendPNeckoConstructor();
+ NS_ASSERTION(gNeckoChild, "PNecko Protocol init failed!");
+ }
+}
+
+PHttpChannelChild*
+NeckoChild::AllocPHttpChannelChild(const PBrowserOrId& browser,
+ const SerializedLoadContext& loadContext,
+ const HttpChannelCreationArgs& aOpenArgs)
+{
+ // We don't allocate here: instead we always use IPDL constructor that takes
+ // an existing HttpChildChannel
+ NS_NOTREACHED("AllocPHttpChannelChild should not be called on child");
+ return nullptr;
+}
+
+bool
+NeckoChild::DeallocPHttpChannelChild(PHttpChannelChild* channel)
+{
+ MOZ_ASSERT(IsNeckoChild(), "DeallocPHttpChannelChild called by non-child!");
+
+ HttpChannelChild* child = static_cast<HttpChannelChild*>(channel);
+ child->ReleaseIPDLReference();
+ return true;
+}
+
+PAltDataOutputStreamChild*
+NeckoChild::AllocPAltDataOutputStreamChild(
+ const nsCString& type,
+ PHttpChannelChild* channel)
+{
+ AltDataOutputStreamChild* stream = new AltDataOutputStreamChild();
+ stream->AddIPDLReference();
+ return stream;
+}
+
+bool
+NeckoChild::DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor)
+{
+ AltDataOutputStreamChild* child = static_cast<AltDataOutputStreamChild*>(aActor);
+ child->ReleaseIPDLReference();
+ return true;
+}
+
+PFTPChannelChild*
+NeckoChild::AllocPFTPChannelChild(const PBrowserOrId& aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs)
+{
+ // We don't allocate here: see FTPChannelChild::AsyncOpen()
+ NS_RUNTIMEABORT("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 nsCookieService::GetSingleton()
+ NS_NOTREACHED("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;
+}
+
+PWyciwygChannelChild*
+NeckoChild::AllocPWyciwygChannelChild()
+{
+ WyciwygChannelChild *p = new WyciwygChannelChild();
+ p->AddIPDLReference();
+ return p;
+}
+
+bool
+NeckoChild::DeallocPWyciwygChannelChild(PWyciwygChannelChild* channel)
+{
+ MOZ_ASSERT(IsNeckoChild(), "DeallocPWyciwygChannelChild called by non-child!");
+
+ WyciwygChannelChild *p = static_cast<WyciwygChannelChild*>(channel);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PWebSocketChild*
+NeckoChild::AllocPWebSocketChild(const PBrowserOrId& browser,
+ const SerializedLoadContext& aSerialized,
+ const uint32_t& aSerial)
+{
+ NS_NOTREACHED("AllocPWebSocketChild should not be called");
+ return nullptr;
+}
+
+bool
+NeckoChild::DeallocPWebSocketChild(PWebSocketChild* child)
+{
+ WebSocketChannelChild* p = static_cast<WebSocketChannelChild*>(child);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PWebSocketEventListenerChild*
+NeckoChild::AllocPWebSocketEventListenerChild(const uint64_t& aInnerWindowID)
+{
+ RefPtr<WebSocketEventListenerChild> c =
+ new WebSocketEventListenerChild(aInnerWindowID);
+ return c.forget().take();
+}
+
+bool
+NeckoChild::DeallocPWebSocketEventListenerChild(PWebSocketEventListenerChild* aActor)
+{
+ RefPtr<WebSocketEventListenerChild> c =
+ dont_AddRef(static_cast<WebSocketEventListenerChild*>(aActor));
+ MOZ_ASSERT(c);
+ return true;
+}
+
+PDataChannelChild*
+NeckoChild::AllocPDataChannelChild(const uint32_t& channelId)
+{
+ MOZ_ASSERT_UNREACHABLE("Should never get here");
+ return nullptr;
+}
+
+bool
+NeckoChild::DeallocPDataChannelChild(PDataChannelChild* child)
+{
+ // NB: See DataChannelChild::ActorDestroy.
+ return true;
+}
+
+PRtspControllerChild*
+NeckoChild::AllocPRtspControllerChild()
+{
+ NS_NOTREACHED("AllocPRtspController should not be called");
+ return nullptr;
+}
+
+bool
+NeckoChild::DeallocPRtspControllerChild(PRtspControllerChild* child)
+{
+#ifdef NECKO_PROTOCOL_rtsp
+ RtspControllerChild* p = static_cast<RtspControllerChild*>(child);
+ p->ReleaseIPDLReference();
+#endif
+ return true;
+}
+
+PRtspChannelChild*
+NeckoChild::AllocPRtspChannelChild(const RtspChannelConnectArgs& aArgs)
+{
+ NS_NOTREACHED("AllocPRtspController should not be called");
+ return nullptr;
+}
+
+bool
+NeckoChild::DeallocPRtspChannelChild(PRtspChannelChild* child)
+{
+#ifdef NECKO_PROTOCOL_rtsp
+ RtspChannelChild* p = static_cast<RtspChannelChild*>(child);
+ p->ReleaseIPDLReference();
+#endif
+ return true;
+}
+
+PTCPSocketChild*
+NeckoChild::AllocPTCPSocketChild(const nsString& host,
+ const uint16_t& port)
+{
+ TCPSocketChild* p = new TCPSocketChild(host, port);
+ 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)
+{
+ NS_NOTREACHED("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(const Principal& aPrincipal,
+ const nsCString& aFilter)
+{
+ NS_NOTREACHED("AllocPUDPSocket should not be called");
+ return nullptr;
+}
+
+bool
+NeckoChild::DeallocPUDPSocketChild(PUDPSocketChild* child)
+{
+
+ UDPSocketChild* p = static_cast<UDPSocketChild*>(child);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PDNSRequestChild*
+NeckoChild::AllocPDNSRequestChild(const nsCString& aHost,
+ const uint32_t& aFlags,
+ const nsCString& aNetworkInterface)
+{
+ // We don't allocate here: instead we always use IPDL constructor that takes
+ // an existing object
+ NS_NOTREACHED("AllocPDNSRequestChild should not be called on child");
+ return nullptr;
+}
+
+bool
+NeckoChild::DeallocPDNSRequestChild(PDNSRequestChild* aChild)
+{
+ DNSRequestChild *p = static_cast<DNSRequestChild*>(aChild);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PChannelDiverterChild*
+NeckoChild::AllocPChannelDiverterChild(const ChannelDiverterArgs& channel)
+{
+ return new ChannelDiverterChild();;
+}
+
+bool
+NeckoChild::DeallocPChannelDiverterChild(PChannelDiverterChild* child)
+{
+ delete static_cast<ChannelDiverterChild*>(child);
+ 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;
+}
+
+bool
+NeckoChild::RecvAsyncAuthPromptForNestedFrame(const TabId& aNestedFrameId,
+ const nsCString& aUri,
+ const nsString& aRealm,
+ const uint64_t& aCallbackId)
+{
+ RefPtr<dom::TabChild> tabChild = dom::TabChild::FindTabChild(aNestedFrameId);
+ if (!tabChild) {
+ MOZ_CRASH();
+ return false;
+ }
+ tabChild->SendAsyncAuthPrompt(aUri, aRealm, aCallbackId);
+ return true;
+}
+
+/* Predictor Messages */
+bool
+NeckoChild::RecvPredOnPredictPrefetch(const URIParams& aURI,
+ const uint32_t& aHttpStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "PredictorChild::RecvOnPredictPrefetch "
+ "off main thread.");
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictorVerifier> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ predictor->OnPredictPrefetch(uri, aHttpStatus);
+ return true;
+}
+
+bool
+NeckoChild::RecvPredOnPredictPreconnect(const URIParams& aURI)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "PredictorChild::RecvOnPredictPreconnect "
+ "off main thread.");
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictorVerifier> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ predictor->OnPredictPreconnect(uri);
+ return true;
+}
+
+bool
+NeckoChild::RecvPredOnPredictDNS(const URIParams& aURI)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "PredictorChild::RecvOnPredictDNS off "
+ "main thread.");
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictorVerifier> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ predictor->OnPredictDNS(uri);
+ return true;
+}
+
+bool
+NeckoChild::RecvSpeculativeConnectRequest()
+{
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "speculative-connect-request",
+ nullptr);
+ }
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/ipc/NeckoChild.h b/netwerk/ipc/NeckoChild.h
new file mode 100644
index 0000000000..d1889d897b
--- /dev/null
+++ b/netwerk/ipc/NeckoChild.h
@@ -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/. */
+
+#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
+{
+public:
+ NeckoChild();
+ virtual ~NeckoChild();
+
+ static void InitNeckoChild();
+
+protected:
+ virtual PHttpChannelChild*
+ AllocPHttpChannelChild(const PBrowserOrId&, const SerializedLoadContext&,
+ const HttpChannelCreationArgs& aOpenArgs) override;
+ virtual bool DeallocPHttpChannelChild(PHttpChannelChild*) override;
+
+ virtual PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild(const nsCString& type, PHttpChannelChild* channel) override;
+ virtual bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor) override;
+
+ virtual PCookieServiceChild* AllocPCookieServiceChild() override;
+ virtual bool DeallocPCookieServiceChild(PCookieServiceChild*) override;
+ virtual PWyciwygChannelChild* AllocPWyciwygChannelChild() override;
+ virtual bool DeallocPWyciwygChannelChild(PWyciwygChannelChild*) override;
+ virtual PFTPChannelChild*
+ AllocPFTPChannelChild(const PBrowserOrId& aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs) override;
+ virtual bool DeallocPFTPChannelChild(PFTPChannelChild*) override;
+ virtual PWebSocketChild*
+ AllocPWebSocketChild(const PBrowserOrId&,
+ const SerializedLoadContext&,
+ const uint32_t&) override;
+ virtual bool DeallocPWebSocketChild(PWebSocketChild*) override;
+ virtual PTCPSocketChild* AllocPTCPSocketChild(const nsString& host,
+ const uint16_t& port) override;
+ virtual bool DeallocPTCPSocketChild(PTCPSocketChild*) override;
+ virtual PTCPServerSocketChild*
+ AllocPTCPServerSocketChild(const uint16_t& aLocalPort,
+ const uint16_t& aBacklog,
+ const bool& aUseArrayBuffers) override;
+ virtual bool DeallocPTCPServerSocketChild(PTCPServerSocketChild*) override;
+ virtual PUDPSocketChild* AllocPUDPSocketChild(const Principal& aPrincipal,
+ const nsCString& aFilter) override;
+ virtual bool DeallocPUDPSocketChild(PUDPSocketChild*) override;
+ virtual PDNSRequestChild* AllocPDNSRequestChild(const nsCString& aHost,
+ const uint32_t& aFlags,
+ const nsCString& aNetworkInterface) override;
+ virtual bool DeallocPDNSRequestChild(PDNSRequestChild*) override;
+ virtual PDataChannelChild* AllocPDataChannelChild(const uint32_t& channelId) override;
+ virtual bool DeallocPDataChannelChild(PDataChannelChild* child) override;
+ virtual PRtspControllerChild* AllocPRtspControllerChild() override;
+ virtual bool DeallocPRtspControllerChild(PRtspControllerChild*) override;
+ virtual PRtspChannelChild*
+ AllocPRtspChannelChild(const RtspChannelConnectArgs& aArgs)
+ override;
+ virtual bool DeallocPRtspChannelChild(PRtspChannelChild*) override;
+ virtual PChannelDiverterChild*
+ AllocPChannelDiverterChild(const ChannelDiverterArgs& channel) override;
+ virtual bool
+ DeallocPChannelDiverterChild(PChannelDiverterChild* actor) override;
+ virtual PTransportProviderChild*
+ AllocPTransportProviderChild() override;
+ virtual bool
+ DeallocPTransportProviderChild(PTransportProviderChild* aActor) override;
+ virtual bool RecvAsyncAuthPromptForNestedFrame(const TabId& aNestedFrameId,
+ const nsCString& aUri,
+ const nsString& aRealm,
+ const uint64_t& aCallbackId) override;
+ virtual PWebSocketEventListenerChild*
+ AllocPWebSocketEventListenerChild(const uint64_t& aInnerWindowID) override;
+ virtual bool DeallocPWebSocketEventListenerChild(PWebSocketEventListenerChild*) override;
+
+ /* Predictor Messsages */
+ virtual bool RecvPredOnPredictPrefetch(const URIParams& aURI,
+ const uint32_t& aHttpStatus) override;
+ virtual bool RecvPredOnPredictPreconnect(const URIParams& aURI) override;
+ virtual bool RecvPredOnPredictDNS(const URIParams& aURI) override;
+
+ virtual bool RecvSpeculativeConnectRequest() override;
+};
+
+/**
+ * Reference to the PNecko Child protocol.
+ * Null if this is not a content process.
+ */
+extern PNeckoChild *gNeckoChild;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NeckoChild_h
diff --git a/netwerk/ipc/NeckoCommon.cpp b/netwerk/ipc/NeckoCommon.cpp
new file mode 100644
index 0000000000..ea2dce5b24
--- /dev/null
+++ b/netwerk/ipc/NeckoCommon.cpp
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NeckoCommon.h"
+
+namespace mozilla {
+namespace net {
+
+namespace NeckoCommonInternal {
+ bool gSecurityDisabled = true;
+} // namespace NeckoCommonInternal
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoCommon.h b/netwerk/ipc/NeckoCommon.h
new file mode 100644
index 0000000000..ed92ac25ea
--- /dev/null
+++ b/netwerk/ipc/NeckoCommon.h
@@ -0,0 +1,130 @@
+/* -*- 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 TabChild;
+} // 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.Append(" (set NECKO_ERRORS_ARE_FATAL=0 in your environment to " \
+ "convert this error into a warning.)"); \
+ NS_RUNTIMEABORT(msg.get()); \
+ } else { \
+ msg.Append(" (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 (mIsPending || mWasOpened) { \
+ nsPrintfCString msg("'%s' called after AsyncOpen: %s +%d", \
+ __FUNCTION__, __FILE__, __LINE__); \
+ NECKO_MAYBE_ABORT(msg); \
+ } \
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); \
+ NS_ENSURE_TRUE(!mWasOpened, 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 (mRequestObserversCalled) { \
+ nsPrintfCString msg("'%s' called too late: %s +%d", \
+ __FUNCTION__, __FILE__, __LINE__); \
+ NECKO_MAYBE_ABORT(msg); \
+ if (mIsPending) \
+ return NS_ERROR_IN_PROGRESS; \
+ MOZ_ASSERT(mWasOpened); \
+ 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;
+}
+
+namespace NeckoCommonInternal {
+ extern bool gSecurityDisabled;
+ extern bool gRegisteredBool;
+} // namespace NeckoCommonInternal
+
+// This should always return true unless xpcshell tests are being used
+inline bool
+UsingNeckoIPCSecurity()
+{
+ return !NeckoCommonInternal::gSecurityDisabled;
+}
+
+inline bool
+MissingRequiredTabChild(mozilla::dom::TabChild* tabChild,
+ const char* context)
+{
+ if (UsingNeckoIPCSecurity()) {
+ if (!tabChild) {
+ printf_stderr("WARNING: child tried to open %s IPDL channel w/o "
+ "security info\n", context);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+} // 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..273f049a26
--- /dev/null
+++ b/netwerk/ipc/NeckoMessageUtils.h
@@ -0,0 +1,189 @@
+/* -*- 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/IPCMessageUtils.h"
+#include "nsStringGlue.h"
+#include "prio.h"
+#include "mozilla/net/DNS.h"
+#include "TimingStruct.h"
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#include "nsPrintfCString.h"
+#endif
+
+namespace IPC {
+
+// nsIPermissionManager utilities
+
+struct Permission
+{
+ nsCString origin, type;
+ uint32_t capability, expireType;
+ int64_t expireTime;
+
+ Permission() { }
+ Permission(const nsCString& aOrigin,
+ const nsCString& aType,
+ const uint32_t aCapability,
+ const uint32_t aExpireType,
+ const int64_t aExpireTime) : origin(aOrigin),
+ type(aType),
+ capability(aCapability),
+ expireType(aExpireType),
+ expireTime(aExpireTime)
+ {}
+};
+
+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...
+ NS_RUNTIMEABORT("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 {
+#ifdef MOZ_CRASHREPORTER
+ if (XRE_IsParentProcess()) {
+ nsPrintfCString msg("%d", aParam.raw.family);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Unknown NetAddr socket family"), msg);
+ }
+#endif
+ NS_RUNTIMEABORT("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<mozilla::net::ResourceTimingStruct>
+{
+ static void Write(Message* aMsg, const mozilla::net::ResourceTimingStruct& aParam)
+ {
+ WriteParam(aMsg, aParam.domainLookupStart);
+ WriteParam(aMsg, aParam.domainLookupEnd);
+ WriteParam(aMsg, aParam.connectStart);
+ WriteParam(aMsg, aParam.connectEnd);
+ WriteParam(aMsg, aParam.requestStart);
+ WriteParam(aMsg, aParam.responseStart);
+ WriteParam(aMsg, aParam.responseEnd);
+
+ WriteParam(aMsg, aParam.fetchStart);
+ WriteParam(aMsg, aParam.redirectStart);
+ WriteParam(aMsg, aParam.redirectEnd);
+
+ WriteParam(aMsg, aParam.transferSize);
+ WriteParam(aMsg, aParam.encodedBodySize);
+ WriteParam(aMsg, aParam.protocolVersion);
+
+ WriteParam(aMsg, aParam.cacheReadStart);
+ WriteParam(aMsg, aParam.cacheReadEnd);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, mozilla::net::ResourceTimingStruct* aResult)
+ {
+ return ReadParam(aMsg, aIter, &aResult->domainLookupStart) &&
+ ReadParam(aMsg, aIter, &aResult->domainLookupEnd) &&
+ ReadParam(aMsg, aIter, &aResult->connectStart) &&
+ ReadParam(aMsg, aIter, &aResult->connectEnd) &&
+ ReadParam(aMsg, aIter, &aResult->requestStart) &&
+ ReadParam(aMsg, aIter, &aResult->responseStart) &&
+ ReadParam(aMsg, aIter, &aResult->responseEnd) &&
+ ReadParam(aMsg, aIter, &aResult->fetchStart) &&
+ ReadParam(aMsg, aIter, &aResult->redirectStart) &&
+ ReadParam(aMsg, aIter, &aResult->redirectEnd) &&
+ ReadParam(aMsg, aIter, &aResult->transferSize) &&
+ ReadParam(aMsg, aIter, &aResult->encodedBodySize) &&
+ ReadParam(aMsg, aIter, &aResult->protocolVersion) &&
+ ReadParam(aMsg, aIter, &aResult->cacheReadStart) &&
+ ReadParam(aMsg, aIter, &aResult->cacheReadEnd);
+ }
+};
+
+} // 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..5913b59d9a
--- /dev/null
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -0,0 +1,907 @@
+/* -*- 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 "necko-config.h"
+#include "nsHttp.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/CookieServiceParent.h"
+#include "mozilla/net/WyciwygChannelParent.h"
+#include "mozilla/net/FTPChannelParent.h"
+#include "mozilla/net/WebSocketChannelParent.h"
+#include "mozilla/net/WebSocketEventListenerParent.h"
+#include "mozilla/net/DataChannelParent.h"
+#include "mozilla/net/AltDataOutputStreamParent.h"
+#include "mozilla/Unused.h"
+#ifdef NECKO_PROTOCOL_rtsp
+#include "mozilla/net/RtspControllerParent.h"
+#include "mozilla/net/RtspChannelParent.h"
+#endif
+#include "mozilla/net/DNSRequestParent.h"
+#include "mozilla/net/ChannelDiverterParent.h"
+#include "mozilla/net/IPCTransportProvider.h"
+#include "mozilla/dom/ChromeUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabContext.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/dom/network/TCPSocketParent.h"
+#include "mozilla/dom/network/TCPServerSocketParent.h"
+#include "mozilla/dom/network/UDPSocketParent.h"
+#include "mozilla/dom/workers/ServiceWorkerManager.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/AppProcessChecker.h"
+#include "nsPrintfCString.h"
+#include "nsHTMLDNSPrefetch.h"
+#include "nsIAppsService.h"
+#include "nsEscape.h"
+#include "SerializedLoadContext.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIAuthPromptCallback.h"
+#include "nsPrincipal.h"
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "nsISpeculativeConnect.h"
+
+using mozilla::DocShellOriginAttributes;
+using mozilla::NeckoOriginAttributes;
+using mozilla::dom::ChromeUtils;
+using mozilla::dom::ContentParent;
+using mozilla::dom::TabContext;
+using mozilla::dom::TabParent;
+using mozilla::net::PTCPSocketParent;
+using mozilla::dom::TCPSocketParent;
+using mozilla::net::PTCPServerSocketParent;
+using mozilla::dom::TCPServerSocketParent;
+using mozilla::net::PUDPSocketParent;
+using mozilla::dom::UDPSocketParent;
+using mozilla::dom::workers::ServiceWorkerManager;
+using mozilla::ipc::OptionalPrincipalInfo;
+using mozilla::ipc::PrincipalInfo;
+using IPC::SerializedLoadContext;
+
+namespace mozilla {
+namespace net {
+
+// C++ file contents
+NeckoParent::NeckoParent()
+{
+ // 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");
+
+ // only register once--we will have multiple NeckoParents if there are
+ // multiple child processes.
+ static bool registeredBool = false;
+ if (!registeredBool) {
+ Preferences::AddBoolVarCache(&NeckoCommonInternal::gSecurityDisabled,
+ "network.disable.ipc.security");
+ registeredBool = true;
+ }
+}
+
+NeckoParent::~NeckoParent()
+{
+}
+
+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 OptionalLoadInfoArgs aOptionalLoadInfoArgs)
+{
+ if (aOptionalLoadInfoArgs.type() != OptionalLoadInfoArgs::TLoadInfoArgs) {
+ return nullptr;
+ }
+
+ const LoadInfoArgs& loadInfoArgs = aOptionalLoadInfoArgs.get_LoadInfoArgs();
+ const OptionalPrincipalInfo& optionalPrincipalInfo =
+ loadInfoArgs.requestingPrincipalInfo();
+
+ if (optionalPrincipalInfo.type() != OptionalPrincipalInfo::TPrincipalInfo) {
+ return nullptr;
+ }
+
+ const PrincipalInfo& principalInfo =
+ optionalPrincipalInfo.get_PrincipalInfo();
+
+ return PrincipalInfoToPrincipal(principalInfo);
+}
+
+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());
+}
+
+// Bug 1289001 - If GetValidatedOriginAttributes returns an error string, that
+// usually leads to a content crash with very little info about the cause.
+// We prefer to crash on the parent, so we get the reason in the crash report.
+static MOZ_COLD
+void CrashWithReason(const char * reason)
+{
+#ifndef RELEASE_OR_BETA
+ MOZ_CRASH_UNSAFE_OOL(reason);
+#endif
+}
+
+const char*
+NeckoParent::GetValidatedOriginAttributes(const SerializedLoadContext& aSerialized,
+ PContentParent* aContent,
+ nsIPrincipal* aRequestingPrincipal,
+ DocShellOriginAttributes& aAttrs)
+{
+ if (!UsingNeckoIPCSecurity()) {
+ if (!aSerialized.IsNotNull()) {
+ // If serialized is null, we cannot validate anything. We have to assume
+ // that this requests comes from a SystemPrincipal.
+ aAttrs = DocShellOriginAttributes(NECKO_NO_APP_ID, false);
+ } else {
+ aAttrs = aSerialized.mOriginAttributes;
+ }
+ return nullptr;
+ }
+
+ if (!aSerialized.IsNotNull()) {
+ CrashWithReason("GetValidatedOriginAttributes | SerializedLoadContext from child is null");
+ return "SerializedLoadContext from child is null";
+ }
+
+ nsTArray<TabContext> contextArray =
+ static_cast<ContentParent*>(aContent)->GetManagedTabContext();
+
+ nsAutoCString serializedSuffix;
+ aSerialized.mOriginAttributes.CreateAnonymizedSuffix(serializedSuffix);
+
+ nsAutoCString debugString;
+ for (uint32_t i = 0; i < contextArray.Length(); i++) {
+ const TabContext& tabContext = contextArray[i];
+
+ if (!ChromeUtils::IsOriginAttributesEqual(aSerialized.mOriginAttributes,
+ tabContext.OriginAttributesRef())) {
+ debugString.Append("(");
+ debugString.Append(serializedSuffix);
+ debugString.Append(",");
+
+ nsAutoCString tabSuffix;
+ tabContext.OriginAttributesRef().CreateAnonymizedSuffix(tabSuffix);
+ debugString.Append(tabSuffix);
+
+ debugString.Append(")");
+ continue;
+ }
+
+ aAttrs = aSerialized.mOriginAttributes;
+ return nullptr;
+ }
+
+ // This may be a ServiceWorker: when a push notification is received, FF wakes
+ // up the corrisponding service worker so that it can manage the PushEvent. At
+ // that time we probably don't have any valid tabcontext, but still, we want
+ // to support http channel requests coming from that ServiceWorker.
+ if (aRequestingPrincipal) {
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm &&
+ swm->MayHaveActiveServiceWorkerInstance(static_cast<ContentParent*>(aContent),
+ aRequestingPrincipal)) {
+ aAttrs = aSerialized.mOriginAttributes;
+ return nullptr;
+ }
+ }
+
+ nsAutoCString errorString;
+ errorString.Append("GetValidatedOriginAttributes | App does not have permission -");
+ errorString.Append(debugString);
+
+ // Leak the buffer on the heap to make sure that it lives long enough, as
+ // MOZ_CRASH_ANNOTATE expects the pointer passed to it to live to the end of
+ // the program.
+ char * error = strdup(errorString.BeginReading());
+ CrashWithReason(error);
+ return "App does not have permission";
+}
+
+const char *
+NeckoParent::CreateChannelLoadContext(const PBrowserOrId& aBrowser,
+ PContentParent* aContent,
+ const SerializedLoadContext& aSerialized,
+ nsIPrincipal* aRequestingPrincipal,
+ nsCOMPtr<nsILoadContext> &aResult)
+{
+ DocShellOriginAttributes 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);
+ switch (aBrowser.type()) {
+ case PBrowserOrId::TPBrowserParent:
+ {
+ RefPtr<TabParent> tabParent =
+ TabParent::GetFrom(aBrowser.get_PBrowserParent());
+ dom::Element* topFrameElement = nullptr;
+ if (tabParent) {
+ topFrameElement = tabParent->GetOwnerElement();
+ }
+ aResult = new LoadContext(aSerialized, topFrameElement, attrs);
+ break;
+ }
+ case PBrowserOrId::TTabId:
+ {
+ aResult = new LoadContext(aSerialized, aBrowser.get_TabId(), attrs);
+ break;
+ }
+ default:
+ MOZ_CRASH();
+ }
+ }
+
+ return nullptr;
+}
+
+void
+NeckoParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ // Nothing needed here. Called right before destructor since this is a
+ // non-refcounted class.
+}
+
+PHttpChannelParent*
+NeckoParent::AllocPHttpChannelParent(const PBrowserOrId& 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);
+ HttpChannelParent *p = new HttpChannelParent(aBrowser, loadContext, overrideStatus);
+ p->AddRef();
+ return p;
+}
+
+bool
+NeckoParent::DeallocPHttpChannelParent(PHttpChannelParent* channel)
+{
+ HttpChannelParent *p = static_cast<HttpChannelParent *>(channel);
+ p->Release();
+ return true;
+}
+
+bool
+NeckoParent::RecvPHttpChannelConstructor(
+ PHttpChannelParent* aActor,
+ const PBrowserOrId& aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const HttpChannelCreationArgs& aOpenArgs)
+{
+ HttpChannelParent* p = static_cast<HttpChannelParent*>(aActor);
+ return p->Init(aOpenArgs);
+}
+
+PAltDataOutputStreamParent*
+NeckoParent::AllocPAltDataOutputStreamParent(
+ const nsCString& type,
+ PHttpChannelParent* channel)
+{
+ HttpChannelParent* chan = static_cast<HttpChannelParent*>(channel);
+ nsCOMPtr<nsIOutputStream> stream;
+ nsresult rv = chan->OpenAlternativeOutputStream(type, 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(const PBrowserOrId& 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(aBrowser, loadContext, overrideStatus);
+ p->AddRef();
+ return p;
+}
+
+bool
+NeckoParent::DeallocPFTPChannelParent(PFTPChannelParent* channel)
+{
+ FTPChannelParent *p = static_cast<FTPChannelParent *>(channel);
+ p->Release();
+ return true;
+}
+
+bool
+NeckoParent::RecvPFTPChannelConstructor(
+ PFTPChannelParent* aActor,
+ const PBrowserOrId& aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs)
+{
+ FTPChannelParent* p = static_cast<FTPChannelParent*>(aActor);
+ return p->Init(aOpenArgs);
+}
+
+PCookieServiceParent*
+NeckoParent::AllocPCookieServiceParent()
+{
+ return new CookieServiceParent();
+}
+
+bool
+NeckoParent::DeallocPCookieServiceParent(PCookieServiceParent* cs)
+{
+ delete cs;
+ return true;
+}
+
+PWyciwygChannelParent*
+NeckoParent::AllocPWyciwygChannelParent()
+{
+ WyciwygChannelParent *p = new WyciwygChannelParent();
+ p->AddRef();
+ return p;
+}
+
+bool
+NeckoParent::DeallocPWyciwygChannelParent(PWyciwygChannelParent* channel)
+{
+ WyciwygChannelParent *p = static_cast<WyciwygChannelParent *>(channel);
+ p->Release();
+ return true;
+}
+
+PWebSocketParent*
+NeckoParent::AllocPWebSocketParent(const PBrowserOrId& 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<TabParent> tabParent = TabParent::GetFrom(browser.get_PBrowserParent());
+ PBOverrideStatus overrideStatus = PBOverrideStatusFromLoadContext(serialized);
+ WebSocketChannelParent* p = new WebSocketChannelParent(tabParent, 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;
+}
+
+PDataChannelParent*
+NeckoParent::AllocPDataChannelParent(const uint32_t &channelId)
+{
+ RefPtr<DataChannelParent> p = new DataChannelParent();
+ return p.forget().take();
+}
+
+bool
+NeckoParent::DeallocPDataChannelParent(PDataChannelParent* actor)
+{
+ RefPtr<DataChannelParent> p = dont_AddRef(static_cast<DataChannelParent*>(actor));
+ return true;
+}
+
+bool
+NeckoParent::RecvPDataChannelConstructor(PDataChannelParent* actor,
+ const uint32_t& channelId)
+{
+ DataChannelParent* p = static_cast<DataChannelParent*>(actor);
+ DebugOnly<bool> rv = p->Init(channelId);
+ MOZ_ASSERT(rv);
+ return true;
+}
+
+PRtspControllerParent*
+NeckoParent::AllocPRtspControllerParent()
+{
+#ifdef NECKO_PROTOCOL_rtsp
+ RtspControllerParent* p = new RtspControllerParent();
+ p->AddRef();
+ return p;
+#else
+ return nullptr;
+#endif
+}
+
+bool
+NeckoParent::DeallocPRtspControllerParent(PRtspControllerParent* actor)
+{
+#ifdef NECKO_PROTOCOL_rtsp
+ RtspControllerParent* p = static_cast<RtspControllerParent*>(actor);
+ p->Release();
+#endif
+ return true;
+}
+
+PRtspChannelParent*
+NeckoParent::AllocPRtspChannelParent(const RtspChannelConnectArgs& aArgs)
+{
+#ifdef NECKO_PROTOCOL_rtsp
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aArgs.uri());
+ RtspChannelParent *p = new RtspChannelParent(uri);
+ p->AddRef();
+ return p;
+#else
+ return nullptr;
+#endif
+}
+
+bool
+NeckoParent::RecvPRtspChannelConstructor(
+ PRtspChannelParent* aActor,
+ const RtspChannelConnectArgs& aConnectArgs)
+{
+#ifdef NECKO_PROTOCOL_rtsp
+ RtspChannelParent* p = static_cast<RtspChannelParent*>(aActor);
+ return p->Init(aConnectArgs);
+#else
+ return false;
+#endif
+}
+
+bool
+NeckoParent::DeallocPRtspChannelParent(PRtspChannelParent* actor)
+{
+#ifdef NECKO_PROTOCOL_rtsp
+ RtspChannelParent* p = static_cast<RtspChannelParent*>(actor);
+ p->Release();
+#endif
+ return true;
+}
+
+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;
+}
+
+bool
+NeckoParent::RecvPTCPServerSocketConstructor(PTCPServerSocketParent* aActor,
+ const uint16_t& aLocalPort,
+ const uint16_t& aBacklog,
+ const bool& aUseArrayBuffers)
+{
+ static_cast<TCPServerSocketParent*>(aActor)->Init();
+ return true;
+}
+
+bool
+NeckoParent::DeallocPTCPServerSocketParent(PTCPServerSocketParent* actor)
+{
+ TCPServerSocketParent* p = static_cast<TCPServerSocketParent*>(actor);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+PUDPSocketParent*
+NeckoParent::AllocPUDPSocketParent(const Principal& /* unused */,
+ const nsCString& /* unused */)
+{
+ RefPtr<UDPSocketParent> p = new UDPSocketParent(this);
+
+ return p.forget().take();
+}
+
+bool
+NeckoParent::RecvPUDPSocketConstructor(PUDPSocketParent* aActor,
+ const Principal& aPrincipal,
+ const nsCString& aFilter)
+{
+ return static_cast<UDPSocketParent*>(aActor)->Init(aPrincipal, aFilter);
+}
+
+bool
+NeckoParent::DeallocPUDPSocketParent(PUDPSocketParent* actor)
+{
+ UDPSocketParent* p = static_cast<UDPSocketParent*>(actor);
+ p->Release();
+ return true;
+}
+
+PDNSRequestParent*
+NeckoParent::AllocPDNSRequestParent(const nsCString& aHost,
+ const uint32_t& aFlags,
+ const nsCString& aNetworkInterface)
+{
+ DNSRequestParent *p = new DNSRequestParent();
+ p->AddRef();
+ return p;
+}
+
+bool
+NeckoParent::RecvPDNSRequestConstructor(PDNSRequestParent* aActor,
+ const nsCString& aHost,
+ const uint32_t& aFlags,
+ const nsCString& aNetworkInterface)
+{
+ static_cast<DNSRequestParent*>(aActor)->DoAsyncResolve(aHost, aFlags,
+ aNetworkInterface);
+ return true;
+}
+
+bool
+NeckoParent::DeallocPDNSRequestParent(PDNSRequestParent* aParent)
+{
+ DNSRequestParent *p = static_cast<DNSRequestParent*>(aParent);
+ p->Release();
+ return true;
+}
+
+bool
+NeckoParent::RecvSpeculativeConnect(const URIParams& aURI,
+ const Principal& aPrincipal,
+ const bool& aAnonymous)
+{
+ nsCOMPtr<nsISpeculativeConnect> speculator(gIOService);
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ nsCOMPtr<nsIPrincipal> principal(aPrincipal);
+ if (uri && speculator) {
+ if (aAnonymous) {
+ speculator->SpeculativeAnonymousConnect2(uri, principal, nullptr);
+ } else {
+ speculator->SpeculativeConnect2(uri, principal, nullptr);
+ }
+
+ }
+ return true;
+}
+
+bool
+NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname,
+ const uint16_t& flags)
+{
+ nsHTMLDNSPrefetch::Prefetch(hostname, flags);
+ return true;
+}
+
+bool
+NeckoParent::RecvCancelHTMLDNSPrefetch(const nsString& hostname,
+ const uint16_t& flags,
+ const nsresult& reason)
+{
+ nsHTMLDNSPrefetch::CancelPrefetch(hostname, flags, reason);
+ return true;
+}
+
+PChannelDiverterParent*
+NeckoParent::AllocPChannelDiverterParent(const ChannelDiverterArgs& channel)
+{
+ return new ChannelDiverterParent();
+}
+
+bool
+NeckoParent::RecvPChannelDiverterConstructor(PChannelDiverterParent* actor,
+ const ChannelDiverterArgs& channel)
+{
+ auto parent = static_cast<ChannelDiverterParent*>(actor);
+ parent->Init(channel);
+ return true;
+}
+
+bool
+NeckoParent::DeallocPChannelDiverterParent(PChannelDiverterParent* parent)
+{
+ delete static_cast<ChannelDiverterParent*>(parent);
+ return true;
+}
+
+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;
+}
+
+bool
+NeckoParent::RecvOnAuthAvailable(const uint64_t& aCallbackId,
+ const nsString& aUser,
+ const nsString& aPassword,
+ const nsString& aDomain)
+{
+ nsCOMPtr<nsIAuthPromptCallback> callback = CallbackMap()[aCallbackId];
+ if (!callback) {
+ return true;
+ }
+ CallbackMap().erase(aCallbackId);
+
+ RefPtr<nsAuthInformationHolder> holder =
+ new nsAuthInformationHolder(0, EmptyString(), EmptyCString());
+ holder->SetUsername(aUser);
+ holder->SetPassword(aPassword);
+ holder->SetDomain(aDomain);
+
+ callback->OnAuthAvailable(nullptr, holder);
+ return true;
+}
+
+bool
+NeckoParent::RecvOnAuthCancelled(const uint64_t& aCallbackId,
+ const bool& aUserCancel)
+{
+ nsCOMPtr<nsIAuthPromptCallback> callback = CallbackMap()[aCallbackId];
+ if (!callback) {
+ return true;
+ }
+ CallbackMap().erase(aCallbackId);
+ callback->OnAuthCancelled(nullptr, aUserCancel);
+ return true;
+}
+
+/* Predictor Messages */
+bool
+NeckoParent::RecvPredPredict(const ipc::OptionalURIParams& aTargetURI,
+ const ipc::OptionalURIParams& aSourceURI,
+ const uint32_t& aReason,
+ const SerializedLoadContext& aLoadContext,
+ const bool& hasVerifier)
+{
+ nsCOMPtr<nsIURI> targetURI = DeserializeURI(aTargetURI);
+ nsCOMPtr<nsIURI> sourceURI = DeserializeURI(aSourceURI);
+
+ // We only actually care about the loadContext.mPrivateBrowsing, so we'll just
+ // pass dummy params for nestFrameId, and originAttributes.
+ uint64_t nestedFrameId = 0;
+ DocShellOriginAttributes attrs(NECKO_UNKNOWN_APP_ID, false);
+ nsCOMPtr<nsILoadContext> loadContext;
+ if (aLoadContext.IsNotNull()) {
+ attrs.SyncAttributesWithPrivateBrowsing(aLoadContext.mOriginAttributes.mPrivateBrowsingId > 0);
+ loadContext = new LoadContext(aLoadContext, nestedFrameId, attrs);
+ }
+
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsINetworkPredictorVerifier> verifier;
+ if (hasVerifier) {
+ verifier = do_QueryInterface(predictor);
+ }
+ predictor->Predict(targetURI, sourceURI, aReason, loadContext, verifier);
+ return true;
+}
+
+bool
+NeckoParent::RecvPredLearn(const ipc::URIParams& aTargetURI,
+ const ipc::OptionalURIParams& aSourceURI,
+ const uint32_t& aReason,
+ const SerializedLoadContext& aLoadContext)
+{
+ nsCOMPtr<nsIURI> targetURI = DeserializeURI(aTargetURI);
+ nsCOMPtr<nsIURI> sourceURI = DeserializeURI(aSourceURI);
+
+ // We only actually care about the loadContext.mPrivateBrowsing, so we'll just
+ // pass dummy params for nestFrameId, and originAttributes;
+ uint64_t nestedFrameId = 0;
+ DocShellOriginAttributes attrs(NECKO_UNKNOWN_APP_ID, false);
+ nsCOMPtr<nsILoadContext> loadContext;
+ if (aLoadContext.IsNotNull()) {
+ attrs.SyncAttributesWithPrivateBrowsing(aLoadContext.mOriginAttributes.mPrivateBrowsingId > 0);
+ loadContext = new LoadContext(aLoadContext, nestedFrameId, attrs);
+ }
+
+ // Get the current predictor
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ predictor->Learn(targetURI, sourceURI, aReason, loadContext);
+ return true;
+}
+
+bool
+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, false);
+
+ predictor->Reset();
+ return true;
+}
+
+bool
+NeckoParent::RecvRemoveRequestContext(const nsCString& rcid)
+{
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ do_GetService("@mozilla.org/network/request-context-service;1");
+ if (!rcsvc) {
+ return true;
+ }
+
+ nsID id;
+ id.Parse(rcid.BeginReading());
+ rcsvc->RemoveRequestContext(id);
+
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h
new file mode 100644
index 0000000000..1264700e50
--- /dev/null
+++ b/netwerk/ipc/NeckoParent.h
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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
+{
+public:
+ NeckoParent();
+ virtual ~NeckoParent();
+
+ MOZ_MUST_USE
+ static const char *
+ GetValidatedOriginAttributes(const SerializedLoadContext& aSerialized,
+ PContentParent* aBrowser,
+ nsIPrincipal* aRequestingPrincipal,
+ mozilla::DocShellOriginAttributes& 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.
+ */
+ MOZ_MUST_USE
+ static const char*
+ CreateChannelLoadContext(const PBrowserOrId& aBrowser,
+ PContentParent* aContent,
+ const SerializedLoadContext& aSerialized,
+ nsIPrincipal* aRequestingPrincipal,
+ nsCOMPtr<nsILoadContext> &aResult);
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+ virtual PCookieServiceParent* AllocPCookieServiceParent() override;
+ virtual bool
+ 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
+ * TabParent 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:
+ virtual PHttpChannelParent*
+ AllocPHttpChannelParent(const PBrowserOrId&, const SerializedLoadContext&,
+ const HttpChannelCreationArgs& aOpenArgs) override;
+ virtual bool
+ RecvPHttpChannelConstructor(
+ PHttpChannelParent* aActor,
+ const PBrowserOrId& aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const HttpChannelCreationArgs& aOpenArgs) override;
+ virtual bool DeallocPHttpChannelParent(PHttpChannelParent*) override;
+
+ virtual PAltDataOutputStreamParent* AllocPAltDataOutputStreamParent(
+ const nsCString& type, PHttpChannelParent* channel) override;
+ virtual bool DeallocPAltDataOutputStreamParent(
+ PAltDataOutputStreamParent* aActor) override;
+
+ virtual bool DeallocPCookieServiceParent(PCookieServiceParent*) override;
+ virtual PWyciwygChannelParent* AllocPWyciwygChannelParent() override;
+ virtual bool DeallocPWyciwygChannelParent(PWyciwygChannelParent*) override;
+ virtual PFTPChannelParent*
+ AllocPFTPChannelParent(const PBrowserOrId& aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs) override;
+ virtual bool
+ RecvPFTPChannelConstructor(
+ PFTPChannelParent* aActor,
+ const PBrowserOrId& aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs) override;
+ virtual bool DeallocPFTPChannelParent(PFTPChannelParent*) override;
+ virtual PWebSocketParent*
+ AllocPWebSocketParent(const PBrowserOrId& browser,
+ const SerializedLoadContext& aSerialized,
+ const uint32_t& aSerial) override;
+ virtual bool DeallocPWebSocketParent(PWebSocketParent*) override;
+ virtual PTCPSocketParent* AllocPTCPSocketParent(const nsString& host,
+ const uint16_t& port) override;
+
+ virtual bool DeallocPTCPSocketParent(PTCPSocketParent*) override;
+ virtual PTCPServerSocketParent*
+ AllocPTCPServerSocketParent(const uint16_t& aLocalPort,
+ const uint16_t& aBacklog,
+ const bool& aUseArrayBuffers) override;
+ virtual bool RecvPTCPServerSocketConstructor(PTCPServerSocketParent*,
+ const uint16_t& aLocalPort,
+ const uint16_t& aBacklog,
+ const bool& aUseArrayBuffers) override;
+ virtual bool DeallocPTCPServerSocketParent(PTCPServerSocketParent*) override;
+ virtual PUDPSocketParent* AllocPUDPSocketParent(const Principal& aPrincipal,
+ const nsCString& aFilter) override;
+ virtual bool RecvPUDPSocketConstructor(PUDPSocketParent*,
+ const Principal& aPrincipal,
+ const nsCString& aFilter) override;
+ virtual bool DeallocPUDPSocketParent(PUDPSocketParent*) override;
+ virtual PDNSRequestParent* AllocPDNSRequestParent(const nsCString& aHost,
+ const uint32_t& aFlags,
+ const nsCString& aNetworkInterface) override;
+ virtual bool RecvPDNSRequestConstructor(PDNSRequestParent* actor,
+ const nsCString& hostName,
+ const uint32_t& flags,
+ const nsCString& aNetworkInterface) override;
+ virtual bool DeallocPDNSRequestParent(PDNSRequestParent*) override;
+ virtual bool RecvSpeculativeConnect(const URIParams& aURI,
+ const Principal& aPrincipal,
+ const bool& aAnonymous) override;
+ virtual bool RecvHTMLDNSPrefetch(const nsString& hostname,
+ const uint16_t& flags) override;
+ virtual bool RecvCancelHTMLDNSPrefetch(const nsString& hostname,
+ const uint16_t& flags,
+ const nsresult& reason) override;
+ virtual PWebSocketEventListenerParent*
+ AllocPWebSocketEventListenerParent(const uint64_t& aInnerWindowID) override;
+ virtual bool DeallocPWebSocketEventListenerParent(PWebSocketEventListenerParent*) override;
+
+ virtual PDataChannelParent*
+ AllocPDataChannelParent(const uint32_t& channelId) override;
+ virtual bool DeallocPDataChannelParent(PDataChannelParent* parent) override;
+
+ virtual bool RecvPDataChannelConstructor(PDataChannelParent* aActor,
+ const uint32_t& channelId) override;
+
+ virtual PRtspControllerParent* AllocPRtspControllerParent() override;
+ virtual bool DeallocPRtspControllerParent(PRtspControllerParent*) override;
+
+ virtual PRtspChannelParent*
+ AllocPRtspChannelParent(const RtspChannelConnectArgs& aArgs)
+ override;
+ virtual bool
+ RecvPRtspChannelConstructor(PRtspChannelParent* aActor,
+ const RtspChannelConnectArgs& aArgs)
+ override;
+ virtual bool DeallocPRtspChannelParent(PRtspChannelParent*) override;
+
+ virtual PChannelDiverterParent*
+ AllocPChannelDiverterParent(const ChannelDiverterArgs& channel) override;
+ virtual bool
+ RecvPChannelDiverterConstructor(PChannelDiverterParent* actor,
+ const ChannelDiverterArgs& channel) override;
+ virtual bool DeallocPChannelDiverterParent(PChannelDiverterParent* actor)
+ override;
+ virtual PTransportProviderParent*
+ AllocPTransportProviderParent() override;
+ virtual bool
+ DeallocPTransportProviderParent(PTransportProviderParent* aActor) override;
+
+ virtual bool RecvOnAuthAvailable(const uint64_t& aCallbackId,
+ const nsString& aUser,
+ const nsString& aPassword,
+ const nsString& aDomain) override;
+ virtual bool RecvOnAuthCancelled(const uint64_t& aCallbackId,
+ const bool& aUserCancel) override;
+
+ /* Predictor Messages */
+ virtual bool RecvPredPredict(const ipc::OptionalURIParams& aTargetURI,
+ const ipc::OptionalURIParams& aSourceURI,
+ const PredictorPredictReason& aReason,
+ const IPC::SerializedLoadContext& aLoadContext,
+ const bool& hasVerifier) override;
+
+ virtual bool RecvPredLearn(const ipc::URIParams& aTargetURI,
+ const ipc::OptionalURIParams& aSourceURI,
+ const PredictorPredictReason& aReason,
+ const IPC::SerializedLoadContext& aLoadContext) override;
+ virtual bool RecvPredReset() override;
+
+ virtual bool RecvRemoveRequestContext(const nsCString& rcid) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NeckoParent_h
diff --git a/netwerk/ipc/PChannelDiverter.ipdl b/netwerk/ipc/PChannelDiverter.ipdl
new file mode 100644
index 0000000000..12be1f09b4
--- /dev/null
+++ b/netwerk/ipc/PChannelDiverter.ipdl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 PFTPChannel;
+include protocol PHttpChannel;
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+// Used when diverting necko channels from child back to the parent.
+// See nsIDivertableChannel.
+async protocol PChannelDiverter
+{
+ manager PNecko;
+
+child:
+ async __delete__();
+};
+
+}// namespace net
+}// namespace mozilla
diff --git a/netwerk/ipc/PDataChannel.ipdl b/netwerk/ipc/PDataChannel.ipdl
new file mode 100644
index 0000000000..69eef43aed
--- /dev/null
+++ b/netwerk/ipc/PDataChannel.ipdl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include URIParams;
+
+namespace mozilla {
+namespace net {
+
+async protocol PDataChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PNecko.ipdl b/netwerk/ipc/PNecko.ipdl
new file mode 100644
index 0000000000..d9d88352cc
--- /dev/null
+++ b/netwerk/ipc/PNecko.ipdl
@@ -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 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 PWyciwygChannel;
+include protocol PFTPChannel;
+include protocol PWebSocket;
+include protocol PWebSocketEventListener;
+include protocol PTCPSocket;
+include protocol PTCPServerSocket;
+include protocol PUDPSocket;
+include protocol PDNSRequest;
+include protocol PChannelDiverter;
+include protocol PBlob; //FIXME: bug #792908
+include protocol PFileDescriptorSet;
+include protocol PDataChannel;
+include protocol PTransportProvider;
+include protocol PSendStream;
+
+include protocol PRtspController;
+include protocol PRtspChannel;
+include URIParams;
+include InputStreamParams;
+include NeckoChannelParams;
+include PBrowserOrId;
+include protocol PAltDataOutputStream;
+
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
+using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+nested(upto inside_cpow) sync protocol PNecko
+{
+ manager PContent;
+ manages PHttpChannel;
+ manages PCookieService;
+ manages PWyciwygChannel;
+ manages PFTPChannel;
+ manages PWebSocket;
+ manages PWebSocketEventListener;
+ manages PTCPSocket;
+ manages PTCPServerSocket;
+ manages PUDPSocket;
+ manages PDNSRequest;
+ manages PDataChannel;
+ manages PRtspController;
+ manages PRtspChannel;
+ manages PChannelDiverter;
+ manages PTransportProvider;
+ manages PAltDataOutputStream;
+
+parent:
+ async __delete__();
+
+ nested(inside_cpow) async PCookieService();
+ async PHttpChannel(PBrowserOrId browser,
+ SerializedLoadContext loadContext,
+ HttpChannelCreationArgs args);
+ async PWyciwygChannel();
+ async PFTPChannel(PBrowserOrId browser, SerializedLoadContext loadContext,
+ FTPChannelCreationArgs args);
+
+ async PWebSocket(PBrowserOrId browser, SerializedLoadContext loadContext,
+ uint32_t aSerialID);
+ async PTCPServerSocket(uint16_t localPort, uint16_t backlog, bool useArrayBuffers);
+ async PUDPSocket(Principal principal, nsCString filter);
+
+ async PDNSRequest(nsCString hostName, uint32_t flags, nsCString networkInterface);
+
+ async PWebSocketEventListener(uint64_t aInnerWindowID);
+
+ /* Predictor Methods */
+ async PredPredict(OptionalURIParams targetURI, OptionalURIParams sourceURI,
+ uint32_t reason, SerializedLoadContext loadContext,
+ bool hasVerifier);
+ async PredLearn(URIParams targetURI, OptionalURIParams sourceURI,
+ uint32_t reason, SerializedLoadContext loadContext);
+ async PredReset();
+
+ async SpeculativeConnect(URIParams uri, Principal principal, bool anonymous);
+ async HTMLDNSPrefetch(nsString hostname, uint16_t flags);
+ async CancelHTMLDNSPrefetch(nsString hostname, uint16_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 PRtspController();
+ async PRtspChannel(RtspChannelConnectArgs args);
+ async PChannelDiverter(ChannelDiverterArgs channel);
+
+ /**
+ * 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 RemoveRequestContext(nsCString rcid);
+
+ async PAltDataOutputStream(nsCString type, PHttpChannel channel);
+
+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(URIParams uri, uint32_t httpStatus);
+ async PredOnPredictPreconnect(URIParams uri);
+ async PredOnPredictDNS(URIParams uri);
+
+ async SpeculativeConnectRequest();
+
+ 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/PRtspChannel.ipdl b/netwerk/ipc/PRtspChannel.ipdl
new file mode 100644
index 0000000000..e884cae762
--- /dev/null
+++ b/netwerk/ipc/PRtspChannel.ipdl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include URIParams;
+
+namespace mozilla {
+namespace net {
+
+async protocol PRtspChannel
+{
+ 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/PRtspController.ipdl b/netwerk/ipc/PRtspController.ipdl
new file mode 100644
index 0000000000..656dbe0b9a
--- /dev/null
+++ b/netwerk/ipc/PRtspController.ipdl
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 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;
+
+namespace mozilla {
+namespace net {
+
+/**
+ * Defined meta data format.
+ */
+union RtspMetaValue
+{
+ bool;
+ uint8_t;
+ uint32_t;
+ uint64_t;
+ nsCString;
+};
+
+/**
+ * Key-value pair.
+ */
+struct RtspMetadataParam
+{
+ nsCString name;
+ RtspMetaValue value;
+};
+
+async protocol PRtspController
+{
+ manager PNecko;
+
+parent:
+ async AsyncOpen(URIParams aURI);
+ async Play();
+ async Pause();
+ async Resume();
+ async Suspend();
+ async Seek(uint64_t offset);
+ async Stop();
+ async PlaybackEnded();
+ async __delete__();
+
+child:
+ async OnMediaDataAvailable(uint8_t index,
+ nsCString data,
+ uint32_t length,
+ uint32_t offset,
+ RtspMetadataParam[] meta);
+ async OnConnected(uint8_t index,
+ RtspMetadataParam[] meta);
+ async OnDisconnected(uint8_t index,
+ nsresult reason);
+ async AsyncOpenFailed(nsresult reason);
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/ipc/moz.build b/netwerk/ipc/moz.build
new file mode 100644
index 0000000000..0740e6f6b7
--- /dev/null
+++ b/netwerk/ipc/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/.
+
+EXPORTS.mozilla.net += [
+ 'ChannelEventQueue.h',
+ 'NeckoChild.h',
+ 'NeckoCommon.h',
+ 'NeckoMessageUtils.h',
+ 'NeckoParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ChannelEventQueue.cpp',
+ 'NeckoChild.cpp',
+ 'NeckoCommon.cpp',
+ 'NeckoParent.cpp',
+]
+
+IPDL_SOURCES = [
+ 'NeckoChannelParams.ipdlh',
+ 'PChannelDiverter.ipdl',
+ 'PDataChannel.ipdl',
+ 'PNecko.ipdl',
+ 'PRtspChannel.ipdl',
+ 'PRtspController.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/caps',
+ '/modules/libjar',
+ '/netwerk/base',
+ '/netwerk/protocol/http',
+]
diff --git a/netwerk/locales/en-US/necko.properties b/netwerk/locales/en-US/necko.properties
new file mode 100644
index 0000000000..60607241df
--- /dev/null
+++ b/netwerk/locales/en-US/necko.properties
@@ -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/.
+
+#ResolvingHost=Looking up
+#ConnectedTo=Connected to
+#ConnectingTo=Connecting to
+#SendingRequestTo=Sending request to
+#TransferringDataFrom=Transferring data from
+
+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…
+
+27=Beginning FTP transaction…
+28=Finished FTP transaction
+
+UnsupportedFTPServer=The FTP server %1$S is currently unsupported.
+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:
+
+PhishingAuth=You are about to visit “%1$S”. This site may be attempting to trick you into thinking you are visiting a different site. Use extreme caution.
+PhishingAuthAccept=I understand and will be very careful
+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”.
+
+TrackingUriBlocked=The resource at “%1$S” was blocked because tracking protection is enabled.
+
+# LOCALIZATION NOTE (APIDeprecationWarning):
+# %1$S is the deprecated API; %2$S is the API function that should be used.
+APIDeprecationWarning=Warning: ‘%1$S’ deprecated, please use ‘%2$S’
+
+# LOCALIZATION NOTE (nsICookieManagerDeprecated): don't localize originAttributes.
+# %1$S is the deprecated API; %2$S is the interface suffix that the given deprecated API belongs to.
+nsICookieManagerAPIDeprecated=“%1$S” is changed. Update your code and pass the correct originAttributes. Read more on MDN: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager%2$S
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..eb4454d28f
--- /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'] \ No newline at end of file
diff --git a/netwerk/mime/moz.build b/netwerk/mime/moz.build
new file mode 100644
index 0000000000..11ea2c4ea6
--- /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['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/mime/nsIMIMEHeaderParam.idl b/netwerk/mime/nsIMIMEHeaderParam.idl
new file mode 100644
index 0000000000..ffe8403d60
--- /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..64172e14e9
--- /dev/null
+++ b/netwerk/mime/nsIMIMEInfo.idl
@@ -0,0 +1,365 @@
+/* -*- 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;
+
+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 aWindowContext
+ * 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 nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * 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);
+};
+
+/**
+ * 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 aWindowContext
+ *
+ * Currently only relevant to web-handler apps. If given, 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 nsIInterfaceRequestor aWindowContext);
+
+};
+
+/**
+ * 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..704269c3cd
--- /dev/null
+++ b/netwerk/mime/nsIMIMEService.idl
@@ -0,0 +1,75 @@
+/* -*- 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);
+};
diff --git a/netwerk/mime/nsMIMEHeaderParamImpl.cpp b/netwerk/mime/nsMIMEHeaderParamImpl.cpp
new file mode 100644
index 0000000000..8d668f64fd
--- /dev/null
+++ b/netwerk/mime/nsMIMEHeaderParamImpl.cpp
@@ -0,0 +1,1346 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 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 "prmem.h"
+#include "prprf.h"
+#include "plstr.h"
+#include "plbase64.h"
+#include "nsCRT.h"
+#include "nsMemory.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsEscape.h"
+#include "nsIUTF8ConverterService.h"
+#include "nsUConvCID.h"
+#include "nsIServiceManager.h"
+#include "nsMIMEHeaderParamImpl.h"
+#include "nsReadableUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsError.h"
+#include "nsIUnicodeDecoder.h"
+#include "mozilla/dom/EncodingUtils.h"
+
+using mozilla::dom::EncodingUtils;
+
+// 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 char *, nsACString &);
+static nsresult DecodeRFC2047Str(const char *, const char *, bool, nsACString&);
+static nsresult internalDecodeParameter(const nsACString&, const char*,
+ const char*, bool, bool, nsACString&);
+
+// 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);
+}
+
+// XXX : aTryLocaleCharset is not yet effective.
+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.)
+ nsXPIDLCString med;
+ nsXPIDLCString 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.get(), nullptr, false,
+ // was aDecoding == MIME_FIELD_ENCODING
+ // see bug 875615
+ true,
+ str1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aFallbackCharset.IsEmpty())
+ {
+ nsAutoCString charset;
+ EncodingUtils::FindEncodingForLabel(aFallbackCharset, charset);
+ nsAutoCString str2;
+ nsCOMPtr<nsIUTF8ConverterService>
+ cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
+ if (cvtUTF8 &&
+ NS_SUCCEEDED(cvtUTF8->ConvertStringToUTF8(str1,
+ PromiseFlatCString(aFallbackCharset).get(), false,
+ !charset.EqualsLiteral("UTF-8"),
+ 1, 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 = 0L;
+ 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
+ if (result) {
+ *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;
+ }
+ } else {
+ // Handle OOM
+ NS_WARNING("Out of memory\n");
+ }
+
+ 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(nsACString& aCharset, const char *aOctets)
+{
+ nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(do_GetService
+ (NS_UTF8CONVERTERSERVICE_CONTRACTID));
+ if (!cvtUTF8) {
+ NS_WARNING("Can't get UTF8ConverterService\n");
+ return false;
+ }
+
+ nsAutoCString tmpRaw;
+ tmpRaw.Assign(aOctets);
+ nsAutoCString tmpDecoded;
+
+ nsresult rv = cvtUTF8->ConvertStringToUTF8(tmpRaw,
+ PromiseFlatCString(aCharset).get(),
+ false, false, 1, 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);
+}
+
+
+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 *) nsMemory::Clone(start, (str - start) + 1);
+ NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
+ (*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 = str;
+ 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;
+ }
+ while (nsCRT::IsAsciiSpace(*str)) ++str;
+
+ if (*str != '"') {
+ // The value is a token, not a quoted string.
+ valueStart = str;
+ for (valueEnd = str;
+ *valueEnd && !nsCRT::IsAsciiSpace (*valueEnd) && *valueEnd != ';';
+ 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.StripChars("\r\n");
+ char *res = ToNewCString(tempStr);
+ 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 *) nsMemory::Clone(rawValStart, rawValLength + 1);
+ if (!tmpResult) {
+ goto increment_str;
+ }
+ *(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 *) nsMemory::Clone(lang.BeginReading(), len + 1);
+ if (*aLang) {
+ *(*aLang + len) = 0;
+ }
+ }
+ if (aCharset && !charset.IsEmpty()) {
+ uint32_t len = charset.Length();
+ *aCharset = (char *) nsMemory::Clone(charset.BeginReading(), len + 1);
+ if (*aCharset) {
+ *(*aCharset + len) = 0;
+ }
+ }
+ }
+
+ return *aResult ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+nsresult
+internalDecodeRFC2047Header(const char* aHeaderVal, const char* 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 && (!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.StripChars("\r\n");
+ aResult = temp;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEHeaderParamImpl::DecodeRFC2047Header(const char* aHeaderVal,
+ const char* aDefaultCharset,
+ bool aOverrideCharset,
+ bool aEatContinuations,
+ nsACString& aResult)
+{
+ return internalDecodeRFC2047Header(aHeaderVal, 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);
+ if (!c) {
+ return false;
+ }
+
+ 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
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIUTF8ConverterService> cvtUTF8 =
+ do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString utf8;
+ rv = cvtUTF8->ConvertStringToUTF8(value, charset.get(), true, false, 1, utf8);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF8toUTF16(utf8, aResult);
+ return NS_OK;
+}
+
+nsresult
+internalDecodeParameter(const nsACString& aParamValue, const char* aCharset,
+ const char* 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 && *aCharset)
+ {
+ nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
+ if (cvtUTF8)
+ return cvtUTF8->ConvertStringToUTF8(aParamValue, aCharset,
+ true, true, 1, aResult);
+ }
+
+ const nsAFlatCString& 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, aCharset, 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 = 0;
+
+ out = dest = (char *)PR_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:
+ PR_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 char *aDefaultCharset, nsACString &aOutput)
+{
+ int32_t c;
+
+ // If aDefaultCharset is not specified, make a blind copy.
+ if (!aDefaultCharset || !*aDefaultCharset) {
+ 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(aDefaultCharset);
+
+ // If not UTF-8, treat as default charset
+ nsCOMPtr<nsIUTF8ConverterService>
+ cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
+ nsAutoCString utf8Text;
+ if (cvtUTF8 &&
+ NS_SUCCEEDED(
+ cvtUTF8->ConvertStringToUTF8(Substring(aInput, aInput + aLen),
+ aDefaultCharset, skipCheck, true, 1,
+ 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 char *aCharset, nsACString &aResult)
+{
+ char *decodedText;
+ 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);
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!decodedText) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIUTF8ConverterService>
+ cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID, &rv));
+ nsAutoCString utf8Text;
+ if (NS_SUCCEEDED(rv)) {
+ // skip ASCIIness/UTF8ness test if aCharset is 7bit non-ascii charset.
+ rv = cvtUTF8->ConvertStringToUTF8(nsDependentCString(decodedText),
+ aCharset,
+ IS_7BIT_NON_ASCII_CHARSET(aCharset),
+ true, 1, utf8Text);
+ }
+ PR_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 char *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, "=?")) != 0) {
+ 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.get(), 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 = 0;
+ 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 && 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.get(), 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.get(), 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.get(), 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.get(), 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..8918ee3275
--- /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() {}
+private:
+ virtual ~nsMIMEHeaderParamImpl() {}
+ enum ParamDecoding {
+ MIME_FIELD_ENCODING = 1,
+ HTTP_FIELD_ENCODING
+ };
+
+ nsresult DoGetParameter(const nsACString& aHeaderVal,
+ const char *aParamName,
+ ParamDecoding aDecoding,
+ const nsACString& aFallbackCharset,
+ bool aTryLocaleCharset,
+ char **aLang,
+ nsAString& aResult);
+
+ 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..098499a148
--- /dev/null
+++ b/netwerk/mime/nsMimeTypes.h
@@ -0,0 +1,229 @@
+/* -*- 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 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_FLAC "audio/flac"
+
+#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"
+#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 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_XUL "application/vnd.mozilla.xul+xml"
+#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"
+
+/* 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 */
+#define APPLICATION_CACHED_XUL "mozilla.application/cached-xul"
+
+#endif /* nsMimeTypes_h_ */
diff --git a/netwerk/moz.build b/netwerk/moz.build
new file mode 100644
index 0000000000..877d6e528b
--- /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/.
+
+DIRS += [
+ 'base',
+ 'cookie',
+ 'dns',
+ 'socket',
+ 'mime',
+ 'streamconv',
+ 'cache',
+ 'cache2',
+ 'protocol',
+ 'system',
+ 'ipc',
+ 'standalone',
+]
+
+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']
+
+CONFIGURE_DEFINE_FILES += ['necko-config.h']
+EXPORTS += ['!necko-config.h']
diff --git a/netwerk/necko-config.h.in b/netwerk/necko-config.h.in
new file mode 100644
index 0000000000..f8b1b57822
--- /dev/null
+++ b/netwerk/necko-config.h.in
@@ -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 _NECKO_CONFIG_H_
+#define _NECKO_CONFIG_H_
+
+#undef NECKO_COOKIES
+
+#undef NECKO_WIFI
+
+#undef NECKO_PROTOCOL_about
+#undef NECKO_PROTOCOL_data
+#undef NECKO_PROTOCOL_device
+#undef NECKO_PROTOCOL_file
+#undef NECKO_PROTOCOL_ftp
+#undef NECKO_PROTOCOL_http
+#undef NECKO_PROTOCOL_res
+#undef NECKO_PROTOCOL_rtsp
+#undef NECKO_PROTOCOL_viewsource
+#undef NECKO_PROTOCOL_websocket
+#undef NECKO_PROTOCOL_wyciwyg
+
+#endif
diff --git a/netwerk/protocol/about/moz.build b/netwerk/protocol/about/moz.build
new file mode 100644
index 0000000000..60a10c9cc7
--- /dev/null
+++ b/netwerk/protocol/about/moz.build
@@ -0,0 +1,34 @@
+# -*- 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 += [
+ '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['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/protocol/about/nsAboutBlank.cpp b/netwerk/protocol/about/nsAboutBlank.cpp
new file mode 100644
index 0000000000..be10be9ac7
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutBlank.cpp
@@ -0,0 +1,59 @@
+/* -*- 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 "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), EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
+ aURI,
+ in,
+ NS_LITERAL_CSTRING("text/html"),
+ NS_LITERAL_CSTRING("utf-8"),
+ 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;
+}
+
+nsresult
+nsAboutBlank::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ nsAboutBlank* about = new nsAboutBlank();
+ if (about == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(about);
+ nsresult rv = about->QueryInterface(aIID, aResult);
+ NS_RELEASE(about);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/protocol/about/nsAboutBlank.h b/netwerk/protocol/about/nsAboutBlank.h
new file mode 100644
index 0000000000..aae6e072d6
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutBlank.h
@@ -0,0 +1,35 @@
+/* -*- 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 nsAboutBlank_h__
+#define nsAboutBlank_h__
+
+#include "nsIAboutModule.h"
+
+class nsAboutBlank : public nsIAboutModule
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutBlank() {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+private:
+ virtual ~nsAboutBlank() {}
+};
+
+#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..2eb5e3b42e
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.cpp
@@ -0,0 +1,585 @@
+/* -*- 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 "nsAboutCache.h"
+#include "nsIInputStream.h"
+#include "nsIStorageStream.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(NS_LITERAL_CSTRING("memory"));
+ mStorageList.AppendElement(NS_LITERAL_CSTRING("disk"));
+ mStorageList.AppendElement(NS_LITERAL_CSTRING("appcache"));
+ } 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,
+ NS_LITERAL_CSTRING("text/html"),
+ NS_LITERAL_CSTRING("utf-8"),
+ 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"
+ " <link rel=\"stylesheet\" href=\"chrome://global/skin/about.css\"/>\n"
+ " <link rel=\"stylesheet\" href=\"chrome://global/skin/aboutCache.css\"/>\n"
+ " <script src=\"chrome://global/content/aboutCache.js\"></script>"
+ "</head>\n"
+ "<body class=\"aboutPageWideContainer\">\n"
+ "<h1>Information about the Network Cache Storage Service</h1>\n");
+
+ // Add the context switch controls
+ mBuffer.AppendLiteral(
+ "<label><input id='priv' type='checkbox'/> Private</label>\n"
+ "<label><input id='anon' type='checkbox'/> Anonymous</label>\n"
+ );
+
+ if (CacheObserver::UseNewCache()) {
+ // Visit scoping by browser and appid is not implemented for
+ // the old cache, simply don't add these controls.
+ // The appid/inbrowser entries are already mixed in the default
+ // view anyway.
+ mBuffer.AppendLiteral(
+ "<label><input id='appid' type='text' size='6'/> AppID</label>\n"
+ "<label><input id='inbrowser' type='checkbox'/> In Browser Element</label>\n"
+ );
+ }
+
+ mBuffer.AppendLiteral(
+ "<label><input id='submit' type='button' value='Update' onclick='navigate()'/></label>\n"
+ );
+
+ if (!mOverview) {
+ mBuffer.AppendLiteral("<a href=\"about:cache?storage=&amp;context=");
+ char* escapedContext = nsEscapeHTML(mContextString.get());
+ mBuffer.Append(escapedContext);
+ free(escapedContext);
+ mBuffer.AppendLiteral("\">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, nsISupports *aContext)
+{
+ nsresult rv;
+
+ if (!mChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Kick the walk loop.
+ rv = VisitNextStorage();
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_ASSERT(!aContext, "asyncOpen2() does not take a context argument");
+ rv = NS_MaybeOpenChannelUsingAsyncOpen2(mChannel, aListener);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ return AsyncOpen(aListener, nullptr);
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::Open2(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->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ mContextString.Truncate();
+ mLoadInfo = CacheFileUtils::ParseKey(NS_LITERAL_CSTRING(""));
+ storage.Truncate();
+
+ nsACString::const_iterator start, valueStart, end;
+ path.BeginReading(start);
+ path.EndReading(end);
+
+ valueStart = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), start, valueStart)) {
+ return NS_OK;
+ }
+
+ nsACString::const_iterator storageNameBegin = valueStart;
+
+ start = valueStart;
+ valueStart = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("&context="), start, valueStart))
+ start = end;
+
+ nsACString::const_iterator storageNameEnd = start;
+
+ mContextString = Substring(valueStart, end);
+ mLoadInfo = CacheFileUtils::ParseKey(mContextString);
+ storage.Assign(Substring(storageNameBegin, storageNameEnd));
+
+ 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(this, &nsAboutCache::Channel::FireVisitStorage));
+}
+
+void
+nsAboutCache::Channel::FireVisitStorage()
+{
+ nsresult rv;
+
+ rv = VisitStorage(mStorageName);
+ if (NS_FAILED(rv)) {
+ if (mLoadInfo) {
+ char* escaped = nsEscapeHTML(mStorageName.get());
+ mBuffer.Append(
+ nsPrintfCString("<p>Unrecognized storage name '%s' in about:cache URL</p>",
+ escaped));
+ free(escaped);
+ } else {
+ char* escaped = nsEscapeHTML(mContextString.get());
+ mBuffer.Append(
+ nsPrintfCString("<p>Unrecognized context key '%s' in about:cache URL</p>",
+ escaped));
+ free(escaped);
+ }
+
+ 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, mLoadInfo, 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>");
+ mBuffer.Append(mStorageName);
+ 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=");
+ mBuffer.Append(mStorageName);
+ mBuffer.AppendLiteral("&amp;context=");
+ char* escapedContext = nsEscapeHTML(mContextString.get());
+ mBuffer.Append(escapedContext);
+ free(escapedContext);
+ 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)
+{
+ // 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=");
+ url.Append(mStorageName);
+
+ url.AppendLiteral("&amp;context=");
+ char* escapedContext = nsEscapeHTML(mContextString.get());
+ url += escapedContext;
+ free(escapedContext);
+
+ url.AppendLiteral("&amp;eid=");
+ char* escapedEID = nsEscapeHTML(aIdEnhance.BeginReading());
+ url += escapedEID;
+ free(escapedEID);
+
+ nsAutoCString cacheUriSpec;
+ aURI->GetAsciiSpec(cacheUriSpec);
+ char* escapedCacheURI = nsEscapeHTML(cacheUriSpec.get());
+ 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()) {
+ mBuffer.Append(aIdEnhance);
+ mBuffer.Append(':');
+ }
+ mBuffer.Append(escapedCacheURI);
+ mBuffer.AppendLiteral("</a></td>\n");
+
+ free(escapedCacheURI);
+
+ // 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>");
+ 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.Append(NS_LITERAL_CSTRING("Pinned"));
+ } else {
+ mBuffer.Append(NS_LITERAL_CSTRING("&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)
+{
+ nsAboutCache* about = new nsAboutCache();
+ if (about == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(about);
+ nsresult rv = about->QueryInterface(aIID, aResult);
+ NS_RELEASE(about);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/protocol/about/nsAboutCache.h b/netwerk/protocol/about/nsAboutCache.h
new file mode 100644
index 0000000000..c2d1af8505
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.h
@@ -0,0 +1,136 @@
+/* -*- 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 nsAboutCache_h__
+#define nsAboutCache_h__
+
+#include "nsIAboutModule.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsICacheStorage.h"
+
+#include "nsString.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); } \
+
+class nsAboutCache final : public nsIAboutModule
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutCache() {}
+
+ static MOZ_MUST_USE nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+ static MOZ_MUST_USE nsresult
+ GetStorage(nsACString const & storageName, nsILoadContextInfo* loadInfo,
+ nsICacheStorage **storage);
+
+protected:
+ virtual ~nsAboutCache() {}
+
+ 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, nsISupports *aContext) override;
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener) override;
+ NS_IMETHOD Open(nsIInputStream * *_retval) override;
+ NS_IMETHOD Open2(nsIInputStream * *_retval) override;
+
+ private:
+ virtual ~Channel() {}
+
+ public:
+ MOZ_MUST_USE nsresult Init(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+ MOZ_MUST_USE 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.
+ MOZ_MUST_USE 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.
+ MOZ_MUST_USE 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.
+ MOZ_MUST_USE 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 context we are working with.
+ nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+ nsCString mContextString;
+
+ // 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..183395976e
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp
@@ -0,0 +1,603 @@
+/* -*- 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 "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>
+#include "nsIPipe.h"
+
+using namespace mozilla::net;
+
+#define HEXDUMP_MAX_ROWS 16
+
+static void
+HexDump(uint32_t *state, const char *buf, int32_t n, nsCString &result)
+{
+ char temp[16];
+
+ const unsigned char *p;
+ while (n) {
+ SprintfLiteral(temp, "%08x: ", *state);
+ result.Append(temp);
+ *state += HEXDUMP_MAX_ROWS;
+
+ p = (const unsigned char *) buf;
+
+ int32_t i, row_max = std::min(HEXDUMP_MAX_ROWS, n);
+
+ // print hex codes:
+ for (i = 0; i < row_max; ++i) {
+ SprintfLiteral(temp, "%02x ", *p++);
+ result.Append(temp);
+ }
+ for (i = row_max; i < HEXDUMP_MAX_ROWS; ++i) {
+ result.AppendLiteral(" ");
+ }
+
+ // print ASCII glyphs if possible:
+ p = (const unsigned char *) buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ switch (*p) {
+ case '<':
+ result.AppendLiteral("&lt;");
+ break;
+ case '>':
+ result.AppendLiteral("&gt;");
+ break;
+ case '&':
+ result.AppendLiteral("&amp;");
+ break;
+ default:
+ if (*p < 0x7F && *p > 0x1F) {
+ result.Append(*p);
+ } else {
+ result.Append('.');
+ }
+ }
+ }
+
+ result.Append('\n');
+
+ buf += row_max;
+ n -= row_max;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::nsISupports
+
+NS_IMPL_ISUPPORTS(nsAboutCacheEntry,
+ nsIAboutModule)
+NS_IMPL_ISUPPORTS(nsAboutCacheEntry::Channel,
+ nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor,
+ nsIStreamListener,
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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,
+ NS_LITERAL_CSTRING("text/html"),
+ NS_LITERAL_CSTRING("utf-8"),
+ 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;
+
+ NS_NAMED_LITERAL_CSTRING(
+ buffer,
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\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");
+ 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;
+
+ if (!CacheObserver::UseNewCache() &&
+ mLoadInfo->IsPrivate() &&
+ mStorageName.EqualsLiteral("disk")) {
+ // The cache v1 is storing all private entries in the memory-only
+ // cache, so it would not be found in the v1 disk cache.
+ mStorageName = NS_LITERAL_CSTRING("memory");
+ }
+
+ 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->GetPath(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(NS_LITERAL_CSTRING("?storage="), keyBegin, keyEnd))
+ return NS_ERROR_FAILURE;
+
+ valBegin = keyEnd; // the value of the storage key starts after the key
+
+ keyBegin = keyEnd; keyEnd = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("&context="), 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(NS_LITERAL_CSTRING("&eid="), 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(NS_LITERAL_CSTRING("&uri="), 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 if (!CacheObserver::UseNewCache() &&
+ !mLoadInfo->IsPrivate() &&
+ mStorageName.EqualsLiteral("memory")) {
+ // If we were not able to find the entry in the memory storage
+ // try again in the disk storage.
+ // This is a workaround for cache v1: when an originally disk
+ // cache entry is recreated as memory-only, it's clientID doesn't
+ // change and we cannot find it in "HTTP-memory-only" session.
+ // "Disk" cache storage looks at "HTTP".
+ mStorageName = NS_LITERAL_CSTRING("disk");
+ rv = OpenCacheEntry();
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ } 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;
+ nsCString buffer;
+ uint32_t n;
+
+ nsAutoCString str;
+
+ rv = entry->GetKey(str);
+ if (NS_FAILED(rv)) return rv;
+
+ buffer.SetCapacity(4096);
+ 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;
+ bool isJS = false;
+ bool isData = false;
+
+ rv = NS_NewURI(getter_AddRefs(uri), str);
+ // 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", &isJS);
+ uri->SchemeIs("data", &isData);
+ }
+ char* escapedStr = nsEscapeHTML(str.get());
+ if (NS_SUCCEEDED(rv) && !(isJS || isData)) {
+ buffer.AppendLiteral("<a href=\"");
+ buffer.Append(escapedStr);
+ buffer.AppendLiteral("\">");
+ buffer.Append(escapedStr);
+ buffer.AppendLiteral("</a>");
+ uri = nullptr;
+ } else {
+ buffer.Append(escapedStr);
+ }
+ free(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);
+ 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, nullptr);
+ if (NS_FAILED(rv)) {
+ return NS_OK; // just ignore
+ }
+
+ mWaitingForData = true;
+ return NS_OK;
+}
+
+nsresult
+nsAboutCacheEntry::Channel::WriteCacheEntryUnavailable()
+{
+ uint32_t n;
+ NS_NAMED_LITERAL_CSTRING(buffer,
+ "The cache entry you selected is not available.");
+ 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>");
+ char* escapedValue = nsEscapeHTML(value);
+ mBuffer->Append(escapedValue);
+ free(escapedValue);
+ mBuffer->AppendLiteral("</td>\n"
+ " </tr>\n");
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIStreamListener implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnStartRequest(nsIRequest *request, nsISupports *ctx)
+{
+ mHexDumpState = 0;
+
+ NS_NAMED_LITERAL_CSTRING(buffer, "<hr/>\n<pre>");
+ uint32_t n;
+ return mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
+ 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, nsISupports *ctx,
+ nsresult result)
+{
+ NS_NAMED_LITERAL_CSTRING(buffer, "</pre>\n");
+ uint32_t n;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ CloseContent();
+
+ return NS_OK;
+}
+
+void
+nsAboutCacheEntry::Channel::CloseContent()
+{
+ NS_NAMED_LITERAL_CSTRING(buffer, "</body>\n</html>\n");
+ 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..44a78760be
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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 nsCString;
+
+class nsAboutCacheEntry final : public nsIAboutModule
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+private:
+ virtual ~nsAboutCacheEntry() {}
+
+ 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() {}
+
+ public:
+ MOZ_MUST_USE nsresult Init(nsIURI* uri, nsILoadInfo* aLoadInfo);
+
+ MOZ_MUST_USE nsresult GetContentStream(nsIURI *, nsIInputStream **);
+ MOZ_MUST_USE nsresult OpenCacheEntry(nsIURI *);
+ MOZ_MUST_USE nsresult OpenCacheEntry();
+ MOZ_MUST_USE nsresult WriteCacheEntryDescription(nsICacheEntry *);
+ MOZ_MUST_USE nsresult WriteCacheEntryUnavailable();
+ MOZ_MUST_USE nsresult ParseURI(nsIURI *uri, nsACString &storageName,
+ nsILoadContextInfo **loadInfo,
+ nsCString &enahnceID,
+ nsIURI **cacheUri);
+ void CloseContent();
+
+ static MOZ_MUST_USE 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..998fc71f95
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
@@ -0,0 +1,439 @@
+/* -*- 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 "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 "nsAutoPtr.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsIChannel.h"
+#include "nsIScriptError.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(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) && (flags & nsIAboutModule::MAKE_LINKABLE);
+}
+////////////////////////////////////////////////////////////////////////////////
+
+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_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
+ // 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;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignore charset info
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ *result = nullptr;
+ nsresult rv;
+
+ // Use a simple URI to parse out some stuff first
+ nsCOMPtr<nsIURI> url = do_CreateInstance(kSimpleURICID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->SetSpec(aSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Unfortunately, people create random about: URIs that don't correspond to
+ // about: modules... Since those URIs will never open a channel, might as
+ // well consider them unsafe for better perf, and just in case.
+ bool isSafe = false;
+
+ nsCOMPtr<nsIAboutModule> aboutMod;
+ rv = NS_GetAboutModule(url, getter_AddRefs(aboutMod));
+ if (NS_SUCCEEDED(rv)) {
+ isSafe = IsSafeToLinkForUntrustedContent(aboutMod, url);
+ }
+
+ if (isSafe) {
+ // 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->GetPath(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ spec.Insert("moz-safe-about:", 0);
+
+ nsCOMPtr<nsIURI> inner;
+ rv = NS_NewURI(getter_AddRefs(inner), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsSimpleNestedURI* outer = new nsNestedAboutURI(inner, aBaseURI);
+ NS_ENSURE_TRUE(outer, NS_ERROR_OUT_OF_MEMORY);
+
+ // Take a ref to it in the COMPtr we plan to return
+ url = outer;
+
+ rv = outer->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We don't want to allow mutation, since it would allow safe and
+ // unsafe URIs to change into each other...
+ NS_TryToSetImmutable(url);
+ url.swap(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::NewChannel2(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)->GetLoadInfo();
+ if (aLoadInfo != loadInfo) {
+ if (loadInfo) {
+ NS_ASSERTION(false,
+ "nsIAboutModule->newChannel(aURI, aLoadInfo) needs to set LoadInfo");
+ const char16_t* params[] = {
+ u"nsIAboutModule->newChannel(aURI)",
+ u"nsIAboutModule->newChannel(aURI, aLoadInfo)"
+ };
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, mozilla::ArrayLength(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(NS_LITERAL_STRING("baseURI"),
+ 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::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+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_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignore charset info
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> url = do_CreateInstance(kSimpleURICID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->SetSpec(aSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NS_TryToSetImmutable(url);
+
+ *result = nullptr;
+ url.swap(*result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ *result = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::NewChannel(nsIURI* uri, 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_INTERFACE_MAP_BEGIN(nsNestedAboutURI)
+ if (aIID.Equals(kNestedAboutURICID))
+ foundInterface = static_cast<nsIURI*>(this);
+ else
+NS_INTERFACE_MAP_END_INHERITING(nsSimpleNestedURI)
+
+// nsISerializable
+NS_IMETHODIMP
+nsNestedAboutURI::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv = nsSimpleNestedURI::Read(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;
+}
+
+// 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;
+ if (aRefHandlingMode == eHonorRef) {
+ rv = mInnerURI->Clone(getter_AddRefs(innerClone));
+ } else if (aRefHandlingMode == eReplaceRef) {
+ rv = mInnerURI->CloneWithNewRef(aNewRef, getter_AddRefs(innerClone));
+ } else {
+ rv = mInnerURI->CloneIgnoringRef(getter_AddRefs(innerClone));
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsNestedAboutURI* url = new nsNestedAboutURI(innerClone, mBaseURI);
+ SetRefOnClone(url, aRefHandlingMode, aNewRef);
+ url->SetMutable(false);
+
+ return url;
+}
+
+// nsIClassInfo
+NS_IMETHODIMP
+nsNestedAboutURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kNestedAboutURICID;
+ 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..72c7e8e668
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.h
@@ -0,0 +1,90 @@
+/* -*- 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 nsAboutProtocolHandler_h___
+#define nsAboutProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsSimpleNestedURI.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.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() {}
+
+private:
+ virtual ~nsAboutProtocolHandler() {}
+};
+
+class nsSafeAboutProtocolHandler final : public nsIProtocolHandler
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsSafeAboutProtocolHandler methods:
+ nsSafeAboutProtocolHandler() {}
+
+private:
+ ~nsSafeAboutProtocolHandler() {}
+};
+
+
+// Class to allow us to propagate the base URI to about:blank correctly
+class nsNestedAboutURI : public nsSimpleNestedURI {
+public:
+ nsNestedAboutURI(nsIURI* aInnerURI, nsIURI* aBaseURI)
+ : nsSimpleNestedURI(aInnerURI)
+ , mBaseURI(aBaseURI)
+ {}
+
+ // For use only from deserialization
+ nsNestedAboutURI() : nsSimpleNestedURI() {}
+
+ virtual ~nsNestedAboutURI() {}
+
+ // Override QI so we can QI to our CID as needed
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
+
+ // Override StartClone(), the nsISerializable methods, and
+ // GetClassIDNoAlloc; this last is needed to make our nsISerializable impl
+ // work right.
+ virtual nsSimpleURI* StartClone(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef);
+ NS_IMETHOD Read(nsIObjectInputStream* aStream);
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream);
+ NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc);
+
+ nsIURI* GetBaseURI() const {
+ return mBaseURI;
+ }
+
+protected:
+ nsCOMPtr<nsIURI> mBaseURI;
+};
+
+} // 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..c5946412b0
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolUtils.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 nsAboutProtocolUtils_h
+#define nsAboutProtocolUtils_h
+
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIAboutModule.h"
+#include "nsServiceManagerUtils.h"
+#include "prtime.h"
+
+inline MOZ_MUST_USE nsresult
+NS_GetAboutModuleName(nsIURI *aAboutURI, nsCString& aModule)
+{
+#ifdef DEBUG
+ {
+ bool isAbout;
+ NS_ASSERTION(NS_SUCCEEDED(aAboutURI->SchemeIs("about", &isAbout)) &&
+ isAbout,
+ "should be used only on about: URIs");
+ }
+#endif
+
+ nsresult rv = aAboutURI->GetPath(aModule);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t f = aModule.FindCharInSet(NS_LITERAL_CSTRING("#?"));
+ 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)
+{
+ NS_PRECONDITION(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.Insert(NS_LITERAL_CSTRING(NS_ABOUT_MODULE_CONTRACTID_PREFIX), 0);
+
+ return CallGetService(contractID.get(), aModule);
+}
+
+inline PRTime SecondsToPRTime(uint32_t t_sec)
+{
+ PRTime t_usec, usec_per_sec;
+ t_usec = t_sec;
+ usec_per_sec = PR_USEC_PER_SEC;
+ return t_usec *= 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..230cd6c056
--- /dev/null
+++ b/netwerk/protocol/about/nsIAboutModule.idl
@@ -0,0 +1,88 @@
+/* -*- 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.
+ */
+ const unsigned long MAKE_LINKABLE = (1 << 7);
+
+ /**
+ * 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);
+};
+
+%{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..137eb74b6f
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelChild.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=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 "DataChannelChild.h"
+
+#include "mozilla/Unused.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)
+{
+}
+
+DataChannelChild::~DataChannelChild()
+{
+}
+
+NS_IMETHODIMP
+DataChannelChild::ConnectParent(uint32_t aId)
+{
+ if (!gNeckoChild->SendPDataChannelConstructor(this, aId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // IPC now has a ref to us.
+ AddIPDLReference();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelChild::CompleteRedirectSetup(nsIStreamListener *aListener,
+ nsISupports *aContext)
+{
+ nsresult rv;
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!aContext, "aContext should be null!");
+ rv = AsyncOpen2(aListener);
+ }
+ else {
+ rv = AsyncOpen(aListener, aContext);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mIPCOpen) {
+ Unused << Send__delete__(this);
+ }
+ return NS_OK;
+}
+
+void
+DataChannelChild::AddIPDLReference()
+{
+ AddRef();
+ mIPCOpen = true;
+}
+
+void
+DataChannelChild::ActorDestroy(ActorDestroyReason why)
+{
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ Release();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/data/DataChannelChild.h b/netwerk/protocol/data/DataChannelChild.h
new file mode 100644
index 0000000000..8fa42177a0
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelChild.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=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/. */
+
+#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();
+
+ void AddIPDLReference();
+
+ 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..e1db0ab369
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelParent.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=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 "DataChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(DataChannelParent, nsIParentChannel, nsIStreamListener)
+
+DataChannelParent::~DataChannelParent()
+{
+}
+
+bool
+DataChannelParent::Init(const uint32_t &channelId)
+{
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+DataChannelParent::SetParentListener(HttpChannelParentListener* aListener)
+{
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::NotifyTrackingProtectionDisabled()
+{
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::Delete()
+{
+ // Nothing to do.
+ return NS_OK;
+}
+
+void
+DataChannelParent::ActorDestroy(ActorDestroyReason why)
+{
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ // 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,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ 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..415672a444
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelParent.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=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/. */
+
+#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
+
+ MOZ_MUST_USE bool Init(const uint32_t& aArgs);
+
+private:
+ ~DataChannelParent();
+
+ 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..0958118fa9
--- /dev/null
+++ b/netwerk/protocol/data/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/.
+
+EXPORTS.mozilla.net += [
+ 'DataChannelParent.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..608a6c6e0e
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.cpp
@@ -0,0 +1,91 @@
+/* -*- 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/. */
+
+// data implementation
+
+#include "nsDataChannel.h"
+
+#include "mozilla/Base64.h"
+#include "nsIOService.h"
+#include "nsDataHandler.h"
+#include "nsIPipe.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsEscape.h"
+
+using namespace mozilla;
+
+nsresult
+nsDataChannel::OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel)
+{
+ NS_ENSURE_TRUE(URI(), NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+
+ nsAutoCString spec;
+ rv = URI()->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString contentType, contentCharset, dataBuffer;
+ bool lBase64;
+ rv = nsDataHandler::ParseURI(spec, contentType, &contentCharset,
+ lBase64, &dataBuffer);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_UnescapeURL(dataBuffer);
+
+ if (lBase64) {
+ // Don't allow spaces in base64-encoded content. This is only
+ // relevant for escaped spaces; other spaces are stripped in
+ // NewURI.
+ dataBuffer.StripWhitespace();
+ }
+
+ nsCOMPtr<nsIInputStream> bufInStream;
+ nsCOMPtr<nsIOutputStream> bufOutStream;
+
+ // create an unbounded pipe.
+ rv = NS_NewPipe(getter_AddRefs(bufInStream),
+ getter_AddRefs(bufOutStream),
+ nsIOService::gDefaultSegmentSize,
+ UINT32_MAX,
+ async, true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t contentLen;
+ if (lBase64) {
+ const uint32_t dataLen = dataBuffer.Length();
+ int32_t resultLen = 0;
+ if (dataLen >= 1 && dataBuffer[dataLen-1] == '=') {
+ if (dataLen >= 2 && dataBuffer[dataLen-2] == '=')
+ resultLen = dataLen-2;
+ else
+ resultLen = dataLen-1;
+ } else {
+ resultLen = dataLen;
+ }
+ resultLen = ((resultLen * 3) / 4);
+
+ nsAutoCString decodedData;
+ rv = Base64Decode(dataBuffer, decodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bufOutStream->Write(decodedData.get(), resultLen, &contentLen);
+ } else {
+ rv = bufOutStream->Write(dataBuffer.get(), dataBuffer.Length(), &contentLen);
+ }
+ 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..c986fba1e9
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.h
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+
+// 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:
+ virtual MOZ_MUST_USE nsresult OpenContentStream(bool async,
+ nsIInputStream **result,
+ nsIChannel** channel);
+};
+
+#endif /* nsDataChannel_h___ */
diff --git a/netwerk/protocol/data/nsDataHandler.cpp b/netwerk/protocol/data/nsDataHandler.cpp
new file mode 100644
index 0000000000..b84b50d5bd
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.cpp
@@ -0,0 +1,250 @@
+/* -*- 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 "nsDataChannel.h"
+#include "nsDataHandler.h"
+#include "nsNetCID.h"
+#include "nsError.h"
+#include "DataChannelChild.h"
+#include "plstr.h"
+
+static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsDataHandler::nsDataHandler() {
+}
+
+nsDataHandler::~nsDataHandler() {
+}
+
+NS_IMPL_ISUPPORTS(nsDataHandler, nsIProtocolHandler, nsISupportsWeakReference)
+
+nsresult
+nsDataHandler::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult) {
+
+ nsDataHandler* ph = new nsDataHandler();
+ if (ph == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(ph);
+ nsresult rv = ph->QueryInterface(aIID, aResult);
+ NS_RELEASE(ph);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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;
+}
+
+NS_IMETHODIMP
+nsDataHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignore charset info
+ nsIURI *aBaseURI,
+ nsIURI **result) {
+ nsresult rv;
+ RefPtr<nsIURI> uri;
+
+ nsCString spec(aSpec);
+
+ 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 = aBaseURI->Clone(getter_AddRefs(uri));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = uri->SetRef(spec);
+ } 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;
+ }
+ }
+
+ uri = do_CreateInstance(kSimpleURICID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = uri->SetSpec(spec);
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ uri.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDataHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ nsDataChannel* channel;
+ if (XRE_IsParentProcess()) {
+ channel = new nsDataChannel(uri);
+ } else {
+ channel = new mozilla::net::DataChannelChild(uri);
+ }
+ NS_ADDREF(channel);
+
+ nsresult rv = channel->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ *result = channel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsDataHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+#define BASE64_EXTENSION ";base64"
+
+nsresult
+nsDataHandler::ParseURI(nsCString& spec,
+ nsCString& contentType,
+ nsCString* contentCharset,
+ bool& isBase64,
+ nsCString* dataBuffer)
+{
+ isBase64 = false;
+
+ // move past "data:"
+ const char* roBuffer = (const char*) PL_strcasestr(spec.get(), "data:");
+ if (!roBuffer) {
+ // malformed uri
+ return NS_ERROR_MALFORMED_URI;
+ }
+ roBuffer += sizeof("data:") - 1;
+
+ // First, find the start of the data
+ const char* roComma = strchr(roBuffer, ',');
+ const char* roHash = strchr(roBuffer, '#');
+ if (!roComma || (roHash && roHash < roComma)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (roComma == roBuffer) {
+ // nothing but data
+ contentType.AssignLiteral("text/plain");
+ if (contentCharset) {
+ contentCharset->AssignLiteral("US-ASCII");
+ }
+ } else {
+ // Make a copy of the non-data part so we can null out parts of it as
+ // we go. This copy will be a small number of chars, in contrast to the
+ // data which may be large.
+ char* buffer = PL_strndup(roBuffer, roComma - roBuffer);
+
+ // determine if the data is base64 encoded.
+ char* base64 = PL_strcasestr(buffer, BASE64_EXTENSION);
+ if (base64) {
+ char *beyond = base64 + sizeof(BASE64_EXTENSION) - 1;
+ // 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 (*beyond == '\0' || *beyond == ';') {
+ isBase64 = true;
+ *base64 = '\0';
+ }
+ }
+
+ // everything else is content type
+ char *semiColon = (char *) strchr(buffer, ';');
+ if (semiColon)
+ *semiColon = '\0';
+
+ if (semiColon == buffer || base64 == buffer) {
+ // there is no content type, but there are other parameters
+ contentType.AssignLiteral("text/plain");
+ } else {
+ contentType.Assign(buffer);
+ ToLowerCase(contentType);
+ if (!contentType.StripWhitespace(mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ if (semiColon && contentCharset) {
+ char *charset = PL_strcasestr(semiColon + 1, "charset=");
+ if (charset) {
+ contentCharset->Assign(charset + sizeof("charset=") - 1);
+ if (!contentCharset->StripWhitespace(mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ free(buffer);
+ }
+
+ if (dataBuffer) {
+ // Split encoded data from terminal "#ref" (if present)
+ const char* roData = roComma + 1;
+ bool ok = !roHash
+ ? dataBuffer->Assign(roData, mozilla::fallible)
+ : dataBuffer->Assign(roData, roHash - roData, mozilla::fallible);
+ if (!ok) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/protocol/data/nsDataHandler.h b/netwerk/protocol/data/nsDataHandler.h
new file mode 100644
index 0000000000..75f873e174
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDataHandler_h___
+#define nsDataHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsDataHandler : public nsIProtocolHandler
+ , public nsSupportsWeakReference
+{
+ virtual ~nsDataHandler();
+
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsDataHandler methods:
+ nsDataHandler();
+
+ // Define a Create method to be used with a factory:
+ static MOZ_MUST_USE 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.
+ static MOZ_MUST_USE nsresult ParseURI(nsCString& spec,
+ nsCString& contentType,
+ nsCString* contentCharset,
+ bool& isBase64,
+ nsCString* dataBuffer);
+};
+
+#endif /* nsDataHandler_h___ */
diff --git a/netwerk/protocol/data/nsDataModule.cpp b/netwerk/protocol/data/nsDataModule.cpp
new file mode 100644
index 0000000000..f96db8c2e3
--- /dev/null
+++ b/netwerk/protocol/data/nsDataModule.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIModule.h"
+#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/device/AndroidCaptureProvider.cpp b/netwerk/protocol/device/AndroidCaptureProvider.cpp
new file mode 100644
index 0000000000..e697660850
--- /dev/null
+++ b/netwerk/protocol/device/AndroidCaptureProvider.cpp
@@ -0,0 +1,301 @@
+/* -*- 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 "base/basictypes.h"
+#include "AndroidCaptureProvider.h"
+#include "nsXULAppAPI.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsMemory.h"
+#include "RawStructs.h"
+
+// The maximum number of frames we keep in our queue. Don't live in the past.
+#define MAX_FRAMES_QUEUED 10
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(AndroidCameraInputStream, nsIInputStream, nsIAsyncInputStream)
+
+AndroidCameraInputStream::AndroidCameraInputStream() :
+ mWidth(0), mHeight(0), mCamera(0), mHeaderSent(false), mClosed(true), mFrameSize(0),
+ mMonitor("AndroidCamera.Monitor")
+{
+ mAvailable = sizeof(RawVideoHeader);
+ mFrameQueue = new nsDeque();
+}
+
+AndroidCameraInputStream::~AndroidCameraInputStream() {
+ // clear the frame queue
+ while (mFrameQueue->GetSize() > 0) {
+ free(mFrameQueue->PopFront());
+ }
+ delete mFrameQueue;
+}
+
+NS_IMETHODIMP
+AndroidCameraInputStream::Init(nsACString& aContentType, nsCaptureParams* aParams)
+{
+ if (!XRE_IsParentProcess())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ mContentType = aContentType;
+ mWidth = aParams->width;
+ mHeight = aParams->height;
+ mCamera = aParams->camera;
+
+ CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0);
+ if (!impl)
+ return NS_ERROR_OUT_OF_MEMORY;
+ if (impl->Init(mContentType, mCamera, mWidth, mHeight, this)) {
+ mWidth = impl->GetWidth();
+ mHeight = impl->GetHeight();
+ mClosed = false;
+ }
+ return NS_OK;
+}
+
+void AndroidCameraInputStream::ReceiveFrame(char* frame, uint32_t length) {
+ {
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+ if (mFrameQueue->GetSize() > MAX_FRAMES_QUEUED) {
+ free(mFrameQueue->PopFront());
+ mAvailable -= mFrameSize;
+ }
+ }
+
+ mFrameSize = sizeof(RawPacketHeader) + length;
+
+ char* fullFrame = (char*)moz_xmalloc(mFrameSize);
+
+ if (!fullFrame)
+ return;
+
+ RawPacketHeader* header = (RawPacketHeader*) fullFrame;
+ header->packetID = 0xFF;
+ header->codecID = 0x595556; // "YUV"
+
+ // we copy the Y plane, and de-interlace the CrCb
+
+ uint32_t yFrameSize = mWidth * mHeight;
+ uint32_t uvFrameSize = yFrameSize / 4;
+
+ memcpy(fullFrame + sizeof(RawPacketHeader), frame, yFrameSize);
+
+ char* uFrame = fullFrame + yFrameSize;
+ char* vFrame = fullFrame + yFrameSize + uvFrameSize;
+ char* yFrame = frame + yFrameSize;
+ for (uint32_t i = 0; i < uvFrameSize; i++) {
+ uFrame[i] = yFrame[2 * i + 1];
+ vFrame[i] = yFrame[2 * i];
+ }
+
+ {
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+ mAvailable += mFrameSize;
+ mFrameQueue->Push((void*)fullFrame);
+ }
+
+ NotifyListeners();
+}
+
+NS_IMETHODIMP
+AndroidCameraInputStream::Available(uint64_t *aAvailable)
+{
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+
+ *aAvailable = mAvailable;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::IsNonBlocking(bool *aNonBlock) {
+ *aNonBlock = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::Read(char *aBuffer, uint32_t aCount, uint32_t *aRead) {
+ return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aRead);
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount, uint32_t *aRead) {
+ *aRead = 0;
+
+ nsresult rv;
+
+ if (mAvailable == 0)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+
+ if (aCount > mAvailable)
+ aCount = mAvailable;
+
+ if (!mHeaderSent) {
+ CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0);
+ RawVideoHeader header;
+ header.headerPacketID = 0;
+ header.codecID = 0x595556; // "YUV"
+ header.majorVersion = 0;
+ header.minorVersion = 1;
+ header.options = 1 | 1 << 1; // color, 4:2:2
+
+ header.alphaChannelBpp = 0;
+ header.lumaChannelBpp = 8;
+ header.chromaChannelBpp = 4;
+ header.colorspace = 1;
+
+ header.frameWidth = mWidth;
+ header.frameHeight = mHeight;
+ header.aspectNumerator = 1;
+ header.aspectDenominator = 1;
+
+ header.framerateNumerator = impl->GetFps();
+ header.framerateDenominator = 1;
+
+ rv = aWriter(this, aClosure, (const char*)&header, 0, sizeof(RawVideoHeader), aRead);
+
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ mHeaderSent = true;
+ aCount -= sizeof(RawVideoHeader);
+ mAvailable -= sizeof(RawVideoHeader);
+ }
+
+ {
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+ while ((mAvailable > 0) && (aCount >= mFrameSize)) {
+ uint32_t readThisTime = 0;
+
+ char* frame = (char*)mFrameQueue->PopFront();
+ rv = aWriter(this, aClosure, (const char*)frame, *aRead, mFrameSize, &readThisTime);
+
+ if (readThisTime != mFrameSize) {
+ mFrameQueue->PushFront((void*)frame);
+ return NS_OK;
+ }
+
+ // RawReader does a copy when calling VideoData::Create()
+ free(frame);
+
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ aCount -= readThisTime;
+ mAvailable -= readThisTime;
+ *aRead += readThisTime;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::Close() {
+ return CloseWithStatus(NS_OK);
+}
+
+
+/**
+ * must be called on the main (java) thread
+ */
+void AndroidCameraInputStream::doClose() {
+ NS_ASSERTION(!mClosed, "Camera is already closed");
+
+ CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0);
+ impl->Close();
+ mClosed = true;
+}
+
+
+void AndroidCameraInputStream::NotifyListeners() {
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+
+ if (mCallback && (mAvailable > sizeof(RawVideoHeader))) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ if (mCallbackTarget) {
+ callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
+ } else {
+ callback = mCallback;
+ }
+
+ NS_ASSERTION(callback, "Shouldn't fail to make the callback!");
+
+ // Null the callback first because OnInputStreamReady may reenter AsyncWait
+ mCallback = nullptr;
+ mCallbackTarget = nullptr;
+
+ callback->OnInputStreamReady(this);
+ }
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::AsyncWait(nsIInputStreamCallback *aCallback, uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget *aTarget)
+{
+ if (aFlags != 0)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (mCallback || mCallbackTarget)
+ return NS_ERROR_UNEXPECTED;
+
+ mCallbackTarget = aTarget;
+ mCallback = aCallback;
+
+ // What we are being asked for may be present already
+ NotifyListeners();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP AndroidCameraInputStream::CloseWithStatus(nsresult status)
+{
+ AndroidCameraInputStream::doClose();
+ return NS_OK;
+}
+
+/**
+ * AndroidCaptureProvider implementation
+ */
+
+NS_IMPL_ISUPPORTS0(AndroidCaptureProvider)
+
+AndroidCaptureProvider* AndroidCaptureProvider::sInstance = nullptr;
+
+AndroidCaptureProvider::AndroidCaptureProvider() {
+}
+
+AndroidCaptureProvider::~AndroidCaptureProvider() {
+ AndroidCaptureProvider::sInstance = nullptr;
+}
+
+nsresult AndroidCaptureProvider::Init(nsACString& aContentType,
+ nsCaptureParams* aParams,
+ nsIInputStream** aStream) {
+
+ NS_ENSURE_ARG_POINTER(aParams);
+
+ NS_ASSERTION(aParams->frameLimit == 0 || aParams->timeLimit == 0,
+ "Cannot set both a frame limit and a time limit!");
+
+ RefPtr<AndroidCameraInputStream> stream;
+
+ if (aContentType.EqualsLiteral("video/x-raw-yuv")) {
+ stream = new AndroidCameraInputStream();
+ if (stream) {
+ nsresult rv = stream->Init(aContentType, aParams);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else
+ return NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ NS_NOTREACHED("Should not have asked Android for this type!");
+ }
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+already_AddRefed<AndroidCaptureProvider> GetAndroidCaptureProvider() {
+ if (!AndroidCaptureProvider::sInstance) {
+ AndroidCaptureProvider::sInstance = new AndroidCaptureProvider();
+ }
+ RefPtr<AndroidCaptureProvider> ret = AndroidCaptureProvider::sInstance;
+ return ret.forget();
+}
diff --git a/netwerk/protocol/device/AndroidCaptureProvider.h b/netwerk/protocol/device/AndroidCaptureProvider.h
new file mode 100644
index 0000000000..dd99ea5411
--- /dev/null
+++ b/netwerk/protocol/device/AndroidCaptureProvider.h
@@ -0,0 +1,68 @@
+/* -*- 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 AndroidDeviceCaptureProvide_h_
+#define AndroidDeviceCaptureProvide_h_
+
+#include "nsDeviceCaptureProvider.h"
+#include "nsIAsyncInputStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "mozilla/net/CameraStreamImpl.h"
+#include "nsIEventTarget.h"
+#include "nsDeque.h"
+#include "mozilla/ReentrantMonitor.h"
+
+class AndroidCaptureProvider final : public nsDeviceCaptureProvider {
+ private:
+ ~AndroidCaptureProvider();
+
+ public:
+ AndroidCaptureProvider();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ MOZ_MUST_USE nsresult Init(nsACString& aContentType, nsCaptureParams* aParams, nsIInputStream** aStream) override;
+ static AndroidCaptureProvider* sInstance;
+};
+
+class AndroidCameraInputStream final : public nsIAsyncInputStream, mozilla::net::CameraStreamImpl::FrameCallback {
+ private:
+ ~AndroidCameraInputStream();
+
+ public:
+ AndroidCameraInputStream();
+
+ NS_IMETHODIMP Init(nsACString& aContentType, nsCaptureParams* aParams);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ void ReceiveFrame(char* frame, uint32_t length) override;
+
+ protected:
+ void NotifyListeners();
+ void doClose();
+
+ uint32_t mAvailable;
+ nsCString mContentType;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mCamera;
+ bool mHeaderSent;
+ bool mClosed;
+ nsDeque *mFrameQueue;
+ uint32_t mFrameSize;
+ mozilla::ReentrantMonitor mMonitor;
+
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+};
+
+already_AddRefed<AndroidCaptureProvider> GetAndroidCaptureProvider();
+
+#endif
diff --git a/netwerk/protocol/device/CameraStreamImpl.cpp b/netwerk/protocol/device/CameraStreamImpl.cpp
new file mode 100644
index 0000000000..f4a2cf4a4f
--- /dev/null
+++ b/netwerk/protocol/device/CameraStreamImpl.cpp
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CameraStreamImpl.h"
+#include "GeneratedJNINatives.h"
+#include "nsCRTGlue.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Monitor.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace net {
+
+static CameraStreamImpl* mCamera0 = nullptr;
+static CameraStreamImpl* mCamera1 = nullptr;
+
+class CameraStreamImpl::Callback
+ : public java::GeckoAppShell::CameraCallback::Natives<Callback>
+{
+public:
+ static void OnFrameData(int32_t aCamera, jni::ByteArray::Param aData)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ CameraStreamImpl* impl = GetInstance(uint32_t(aCamera));
+ if (impl) {
+ impl->TransmitFrame(aData);
+ }
+ }
+};
+
+/**
+ * CameraStreamImpl
+ */
+
+void CameraStreamImpl::TransmitFrame(jni::ByteArray::Param aData) {
+ if (!mCallback) {
+ return;
+ }
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ const size_t length = size_t(env->GetArrayLength(aData.Get()));
+
+ if (!length) {
+ return;
+ }
+
+ jbyte* const data = env->GetByteArrayElements(aData.Get(), nullptr);
+ mCallback->ReceiveFrame(reinterpret_cast<char*>(data), length);
+ env->ReleaseByteArrayElements(aData.Get(), data, JNI_ABORT);
+}
+
+CameraStreamImpl* CameraStreamImpl::GetInstance(uint32_t aCamera) {
+ CameraStreamImpl* res = nullptr;
+ switch(aCamera) {
+ case 0:
+ if (mCamera0)
+ res = mCamera0;
+ else
+ res = mCamera0 = new CameraStreamImpl(aCamera);
+ break;
+ case 1:
+ if (mCamera1)
+ res = mCamera1;
+ else
+ res = mCamera1 = new CameraStreamImpl(aCamera);
+ break;
+ }
+ return res;
+}
+
+
+CameraStreamImpl::CameraStreamImpl(uint32_t aCamera) :
+ mInit(false), mCamera(aCamera)
+{
+ NS_WARNING("CameraStreamImpl::CameraStreamImpl()");
+ mWidth = 0;
+ mHeight = 0;
+ mFps = 0;
+}
+
+CameraStreamImpl::~CameraStreamImpl()
+{
+ NS_WARNING("CameraStreamImpl::~CameraStreamImpl()");
+}
+
+bool CameraStreamImpl::Init(const nsCString& contentType, const uint32_t& camera, const uint32_t& width, const uint32_t& height, FrameCallback* aCallback)
+{
+ mCallback = aCallback;
+ mWidth = width;
+ mHeight = height;
+
+ Callback::Init();
+ jni::IntArray::LocalRef retArray = java::GeckoAppShell::InitCamera(
+ contentType, int32_t(camera), int32_t(width), int32_t(height));
+ nsTArray<int32_t> ret = retArray->GetElements();
+
+ mWidth = uint32_t(ret[1]);
+ mHeight = uint32_t(ret[2]);
+ mFps = uint32_t(ret[3]);
+
+ return !!ret[0];
+}
+
+void CameraStreamImpl::Close() {
+ java::GeckoAppShell::CloseCamera();
+ mCallback = nullptr;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/device/CameraStreamImpl.h b/netwerk/protocol/device/CameraStreamImpl.h
new file mode 100644
index 0000000000..93037caf66
--- /dev/null
+++ b/netwerk/protocol/device/CameraStreamImpl.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 __CAMERASTREAMIMPL_H__
+#define __CAMERASTREAMIMPL_H__
+
+#include "mozilla/jni/Refs.h"
+
+#include "nsString.h"
+
+/**
+ * This singleton class handles communication with the Android camera
+ * through JNI. It is used by the IPDL parent or directly from the chrome process
+ */
+
+namespace mozilla {
+namespace net {
+
+class CameraStreamImpl {
+public:
+ class FrameCallback {
+ public:
+ virtual void ReceiveFrame(char* frame, uint32_t length) = 0;
+ };
+
+ /**
+ * instance bound to a given camera
+ */
+ static CameraStreamImpl* GetInstance(uint32_t aCamera);
+
+ bool initNeeded() {
+ return !mInit;
+ }
+
+ FrameCallback* GetFrameCallback() {
+ return mCallback;
+ }
+
+ MOZ_MUST_USE bool Init(const nsCString& contentType, const uint32_t& camera, const uint32_t& width, const uint32_t& height, FrameCallback* callback);
+ void Close();
+
+ uint32_t GetWidth() { return mWidth; }
+ uint32_t GetHeight() { return mHeight; }
+ uint32_t GetFps() { return mFps; }
+
+ void takePicture(const nsAString& aFileName);
+
+private:
+ class Callback;
+
+ CameraStreamImpl(uint32_t aCamera);
+ CameraStreamImpl(const CameraStreamImpl&);
+ CameraStreamImpl& operator=(const CameraStreamImpl&);
+
+ ~CameraStreamImpl();
+
+ void TransmitFrame(jni::ByteArray::Param aData);
+
+ bool mInit;
+ uint32_t mCamera;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mFps;
+ FrameCallback* mCallback;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/device/RawStructs.h b/netwerk/protocol/device/RawStructs.h
new file mode 100644
index 0000000000..61777877ea
--- /dev/null
+++ b/netwerk/protocol/device/RawStructs.h
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(RawStructs_h_)
+#define RawStructs_h_
+
+static const uint32_t RAW_ID = 0x595556;
+
+struct nsRawVideo_PRUint24 {
+ operator uint32_t() const { return value[2] << 16 | value[1] << 8 | value[0]; }
+ nsRawVideo_PRUint24& operator= (const uint32_t& rhs)
+ { value[2] = (rhs & 0x00FF0000) >> 16;
+ value[1] = (rhs & 0x0000FF00) >> 8;
+ value[0] = (rhs & 0x000000FF);
+ return *this; }
+private:
+ uint8_t value[3];
+};
+
+struct RawPacketHeader {
+ typedef nsRawVideo_PRUint24 PRUint24;
+ uint8_t packetID;
+ PRUint24 codecID;
+};
+
+// This is Arc's draft from wiki.xiph.org/OggYUV
+struct RawVideoHeader {
+ typedef nsRawVideo_PRUint24 PRUint24;
+ uint8_t headerPacketID; // Header Packet ID (always 0)
+ PRUint24 codecID; // Codec identifier (always "YUV")
+ uint8_t majorVersion; // Version Major (breaks backwards compat)
+ uint8_t minorVersion; // Version Minor (preserves backwards compat)
+ uint16_t options; // Bit 1: Color (false = B/W)
+ // Bits 2-4: Chroma Pixel Shape
+ // Bit 5: 50% horizontal offset for Cr samples
+ // Bit 6: 50% vertical ...
+ // Bits 7-8: Chroma Blending
+ // Bit 9: Packed (false = Planar)
+ // Bit 10: Cr Staggered Horizontally
+ // Bit 11: Cr Staggered Vertically
+ // Bit 12: Unused (format is always little endian)
+ // Bit 13: Interlaced (false = Progressive)
+ // Bits 14-16: Interlace options (undefined)
+
+ uint8_t alphaChannelBpp;
+ uint8_t lumaChannelBpp;
+ uint8_t chromaChannelBpp;
+ uint8_t colorspace;
+
+ PRUint24 frameWidth;
+ PRUint24 frameHeight;
+ PRUint24 aspectNumerator;
+ PRUint24 aspectDenominator;
+
+ uint32_t framerateNumerator;
+ uint32_t framerateDenominator;
+};
+
+#endif // RawStructs_h_
diff --git a/netwerk/protocol/device/moz.build b/netwerk/protocol/device/moz.build
new file mode 100644
index 0000000000..a186722204
--- /dev/null
+++ b/netwerk/protocol/device/moz.build
@@ -0,0 +1,27 @@
+# -*- 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'] == 'android':
+ EXPORTS.mozilla.net += [
+ 'CameraStreamImpl.h',
+ ]
+ UNIFIED_SOURCES += [
+ 'AndroidCaptureProvider.cpp',
+ 'CameraStreamImpl.cpp',
+ ]
+
+UNIFIED_SOURCES += [
+ 'nsDeviceChannel.cpp',
+ 'nsDeviceProtocolHandler.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base/',
+]
diff --git a/netwerk/protocol/device/nsDeviceCaptureProvider.h b/netwerk/protocol/device/nsDeviceCaptureProvider.h
new file mode 100644
index 0000000000..024f6689d6
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceCaptureProvider.h
@@ -0,0 +1,31 @@
+/* -*- 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 nsDeviceCaptureProvider_h_
+#define nsDeviceCaptureProvider_h_
+
+#include "nsIInputStream.h"
+
+struct nsCaptureParams {
+ bool captureAudio;
+ bool captureVideo;
+ uint32_t frameRate;
+ uint32_t frameLimit;
+ uint32_t timeLimit;
+ uint32_t width;
+ uint32_t height;
+ uint32_t bpp;
+ uint32_t camera;
+};
+
+class nsDeviceCaptureProvider : public nsISupports
+{
+public:
+ virtual MOZ_MUST_USE nsresult Init(nsACString& aContentType,
+ nsCaptureParams* aParams,
+ nsIInputStream** aStream) = 0;
+};
+
+#endif
diff --git a/netwerk/protocol/device/nsDeviceChannel.cpp b/netwerk/protocol/device/nsDeviceChannel.cpp
new file mode 100644
index 0000000000..6c5788a568
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceChannel.cpp
@@ -0,0 +1,154 @@
+/* -*- 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 "plstr.h"
+#include "nsDeviceChannel.h"
+#include "nsDeviceCaptureProvider.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "mozilla/Preferences.h"
+#include "AndroidCaptureProvider.h"
+#endif
+
+using namespace mozilla;
+
+// Copied from image/decoders/icon/nsIconURI.cpp
+// takes a string like ?size=32&contentType=text/html and returns a new string
+// containing just the attribute values. i.e you could pass in this string with
+// an attribute name of "size=", this will return 32
+// Assumption: attribute pairs are separated by &
+void extractAttributeValue(const char* searchString, const char* attributeName, nsCString& result)
+{
+ result.Truncate();
+
+ if (!searchString || !attributeName)
+ return;
+
+ uint32_t attributeNameSize = strlen(attributeName);
+ const char *startOfAttribute = PL_strcasestr(searchString, attributeName);
+ if (!startOfAttribute ||
+ !( *(startOfAttribute-1) == '?' || *(startOfAttribute-1) == '&') )
+ return;
+
+ startOfAttribute += attributeNameSize; // Skip the attributeName
+ if (!*startOfAttribute)
+ return;
+
+ const char *endOfAttribute = strchr(startOfAttribute, '&');
+ if (endOfAttribute) {
+ result.Assign(Substring(startOfAttribute, endOfAttribute));
+ } else {
+ result.Assign(startOfAttribute);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsDeviceChannel,
+ nsBaseChannel,
+ nsIChannel)
+
+// nsDeviceChannel methods
+nsDeviceChannel::nsDeviceChannel()
+{
+ SetContentType(NS_LITERAL_CSTRING("image/png"));
+}
+
+nsDeviceChannel::~nsDeviceChannel()
+{
+}
+
+nsresult
+nsDeviceChannel::Init(nsIURI* aUri)
+{
+ nsBaseChannel::Init();
+ nsBaseChannel::SetURI(aUri);
+ return NS_OK;
+}
+
+nsresult
+nsDeviceChannel::OpenContentStream(bool aAsync,
+ nsIInputStream** aStream,
+ nsIChannel** aChannel)
+{
+ if (!aAsync)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsCOMPtr<nsIURI> uri = nsBaseChannel::URI();
+ *aStream = nullptr;
+ *aChannel = nullptr;
+ NS_NAMED_LITERAL_CSTRING(width, "width=");
+ NS_NAMED_LITERAL_CSTRING(height, "height=");
+
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString type;
+
+ RefPtr<nsDeviceCaptureProvider> capture;
+ nsCaptureParams captureParams;
+ captureParams.camera = 0;
+ if (kNotFound != spec.Find(NS_LITERAL_CSTRING("type=image/png"),
+ true,
+ 0,
+ -1)) {
+ type.AssignLiteral("image/png");
+ SetContentType(type);
+ captureParams.captureAudio = false;
+ captureParams.captureVideo = true;
+ captureParams.timeLimit = 0;
+ captureParams.frameLimit = 1;
+ nsAutoCString buffer;
+ extractAttributeValue(spec.get(), "width=", buffer);
+ nsresult err;
+ captureParams.width = buffer.ToInteger(&err);
+ if (!captureParams.width)
+ captureParams.width = 640;
+ extractAttributeValue(spec.get(), "height=", buffer);
+ captureParams.height = buffer.ToInteger(&err);
+ if (!captureParams.height)
+ captureParams.height = 480;
+ extractAttributeValue(spec.get(), "camera=", buffer);
+ captureParams.camera = buffer.ToInteger(&err);
+ captureParams.bpp = 32;
+#ifdef MOZ_WIDGET_ANDROID
+ capture = GetAndroidCaptureProvider();
+#endif
+ } else if (kNotFound != spec.Find(NS_LITERAL_CSTRING("type=video/x-raw-yuv"),
+ true,
+ 0,
+ -1)) {
+ type.AssignLiteral("video/x-raw-yuv");
+ SetContentType(type);
+ captureParams.captureAudio = false;
+ captureParams.captureVideo = true;
+ nsAutoCString buffer;
+ extractAttributeValue(spec.get(), "width=", buffer);
+ nsresult err;
+ captureParams.width = buffer.ToInteger(&err);
+ if (!captureParams.width)
+ captureParams.width = 640;
+ extractAttributeValue(spec.get(), "height=", buffer);
+ captureParams.height = buffer.ToInteger(&err);
+ if (!captureParams.height)
+ captureParams.height = 480;
+ extractAttributeValue(spec.get(), "camera=", buffer);
+ captureParams.camera = buffer.ToInteger(&err);
+ captureParams.bpp = 32;
+ captureParams.timeLimit = 0;
+ captureParams.frameLimit = 60000;
+#ifdef MOZ_WIDGET_ANDROID
+ // only enable if "device.camera.enabled" is true.
+ if (Preferences::GetBool("device.camera.enabled", false) == true)
+ capture = GetAndroidCaptureProvider();
+#endif
+ } else {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!capture)
+ return NS_ERROR_FAILURE;
+
+ return capture->Init(type, &captureParams, aStream);
+}
diff --git a/netwerk/protocol/device/nsDeviceChannel.h b/netwerk/protocol/device/nsDeviceChannel.h
new file mode 100644
index 0000000000..8c3e447931
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceChannel.h
@@ -0,0 +1,26 @@
+/* -*- 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 nsDeviceChannel_h_
+#define nsDeviceChannel_h_
+
+#include "nsBaseChannel.h"
+
+class nsDeviceChannel : public nsBaseChannel
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsDeviceChannel();
+
+ MOZ_MUST_USE nsresult Init(nsIURI* uri);
+ MOZ_MUST_USE nsresult OpenContentStream(bool aAsync,
+ nsIInputStream **aStream,
+ nsIChannel **aChannel) override;
+
+protected:
+ ~nsDeviceChannel();
+};
+#endif
diff --git a/netwerk/protocol/device/nsDeviceProtocolHandler.cpp b/netwerk/protocol/device/nsDeviceProtocolHandler.cpp
new file mode 100644
index 0000000000..26c5f33dff
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceProtocolHandler.cpp
@@ -0,0 +1,93 @@
+/* -*- 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 "nsDeviceProtocolHandler.h"
+#include "nsDeviceChannel.h"
+#include "nsAutoPtr.h"
+#include "nsSimpleURI.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(nsDeviceProtocolHandler,
+ nsIProtocolHandler)
+
+nsresult
+nsDeviceProtocolHandler::Init(){
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::GetScheme(nsACString &aResult)
+{
+ aResult.AssignLiteral("moz-device");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::GetDefaultPort(int32_t *aResult)
+{
+ *aResult = -1; // no port for moz_device: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::GetProtocolFlags(uint32_t *aResult)
+{
+ *aResult = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::NewURI(const nsACString &spec,
+ const char *originCharset,
+ nsIURI *baseURI,
+ nsIURI **result)
+{
+ RefPtr<nsSimpleURI> uri = new nsSimpleURI();
+
+ nsresult rv = uri->SetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aResult)
+{
+ RefPtr<nsDeviceChannel> channel = new nsDeviceChannel();
+ nsresult rv = channel->Init(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::NewChannel(nsIURI* aURI, nsIChannel **aResult)
+{
+ return NewChannel2(aURI, nullptr, aResult);
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::AllowPort(int32_t port,
+ const char *scheme,
+ bool *aResult)
+{
+ // don't override anything.
+ *aResult = false;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/device/nsDeviceProtocolHandler.h b/netwerk/protocol/device/nsDeviceProtocolHandler.h
new file mode 100644
index 0000000000..dee9b4f8f0
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceProtocolHandler.h
@@ -0,0 +1,34 @@
+/* -*- 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 nsDeviceProtocolHandler_h_
+#define nsDeviceProtocolHandler_h_
+
+#include "nsIProtocolHandler.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace net {
+
+// {6b0ffe9e-d114-486b-aeb7-da62e7273ed5}
+#define NS_DEVICEPROTOCOLHANDLER_CID \
+{ 0x60ffe9e, 0xd114, 0x486b, \
+ {0xae, 0xb7, 0xda, 0x62, 0xe7, 0x27, 0x3e, 0xd5} }
+
+class nsDeviceProtocolHandler final : public nsIProtocolHandler {
+ ~nsDeviceProtocolHandler() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ nsDeviceProtocolHandler() {}
+
+ MOZ_MUST_USE nsresult Init();
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/protocol/file/moz.build b/netwerk/protocol/file/moz.build
new file mode 100644
index 0000000000..223ff2f2bd
--- /dev/null
+++ b/netwerk/protocol/file/moz.build
@@ -0,0 +1,30 @@
+# -*- 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 += [
+ 'nsFileProtocolHandler.h',
+]
+
+XPIDL_SOURCES += [
+ 'nsIFileChannel.idl',
+ 'nsIFileProtocolHandler.idl',
+]
+
+XPIDL_MODULE = 'necko_file'
+
+UNIFIED_SOURCES += [
+ 'nsFileChannel.cpp',
+ 'nsFileProtocolHandler.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/protocol/file/nsFileChannel.cpp b/netwerk/protocol/file/nsFileChannel.cpp
new file mode 100644
index 0000000000..0400aaec01
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -0,0 +1,486 @@
+/* -*- 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 "nsAutoPtr.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+
+#include "nsIFileURL.h"
+#include "nsIFile.h"
+#include "nsIMIMEService.h"
+#include "prio.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+//-----------------------------------------------------------------------------
+
+class nsFileCopyEvent : public Runnable {
+public:
+ nsFileCopyEvent(nsIOutputStream *dest, nsIInputStream *source, int64_t len)
+ : 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(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_DECL_ISUPPORTS_INHERITED
+
+ 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() {}
+
+ void OnCopyComplete();
+
+ RefPtr<nsFileCopyEvent> mCopyEvent;
+ nsCOMPtr<nsITransportEventSink> mSink;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsFileUploadContentStream,
+ nsBaseContentStream)
+
+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(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)
+{
+ // 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;
+ nsAutoCString fileTarget;
+ nsCOMPtr<nsIFile> resolvedFile;
+ bool symLink;
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
+ if (fileURL &&
+ NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
+ NS_SUCCEEDED(file->IsSymlink(&symLink)) &&
+ symLink &&
+ NS_SUCCEEDED(file->GetNativeTarget(fileTarget)) &&
+ NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget, PR_TRUE,
+ getter_AddRefs(resolvedFile))) &&
+ NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(targetURI),
+ resolvedFile, nullptr))) {
+ // Make an effort to match up the query strings.
+ nsCOMPtr<nsIURL> origURL = do_QueryInterface(uri);
+ nsCOMPtr<nsIURL> targetURL = do_QueryInterface(targetURI);
+ nsAutoCString queryString;
+ if (origURL && targetURL && NS_SUCCEEDED(origURL->GetQuery(queryString))) {
+ targetURL->SetQuery(queryString);
+ }
+
+ SetURI(targetURI);
+ SetOriginalURI(uri);
+ nsLoadFlags loadFlags = 0;
+ GetLoadFlags(&loadFlags);
+ SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
+ } else {
+ SetURI(uri);
+ }
+}
+
+nsFileChannel::~nsFileChannel()
+{
+}
+
+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;
+ rv = fileHandler->ReadURLFile(file, getter_AddRefs(newURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannel(getter_AddRefs(newChannel),
+ newURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_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 = uploadStream.forget();
+
+ 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(NS_LITERAL_CSTRING(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
+ if (mContentLength < 0) {
+ 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;
+ }
+ if (!contentType.IsEmpty())
+ SetContentType(contentType);
+ }
+
+ *result = nullptr;
+ stream.swap(*result);
+ 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..2f1f71ba3f
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.h
@@ -0,0 +1,45 @@
+/* -*- 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);
+
+protected:
+ ~nsFileChannel();
+
+ // 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.
+ MOZ_MUST_USE nsresult MakeFileInputStream(nsIFile *file,
+ nsCOMPtr<nsIInputStream> &stream,
+ nsCString &contentType, bool async);
+
+ virtual MOZ_MUST_USE nsresult OpenContentStream(bool async,
+ nsIInputStream **result,
+ nsIChannel** channel) override;
+
+private:
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ int64_t mUploadLength;
+};
+
+#endif // !nsFileChannel_h__
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.cpp b/netwerk/protocol/file/nsFileProtocolHandler.cpp
new file mode 100644
index 0000000000..e55cb9d474
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim: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 "nsIFile.h"
+#include "nsFileProtocolHandler.h"
+#include "nsFileChannel.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+
+#include "nsNetUtil.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
+
+//-----------------------------------------------------------------------------
+
+nsFileProtocolHandler::nsFileProtocolHandler()
+{
+}
+
+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, NS_LITERAL_CSTRING(".desktop")))
+ 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::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;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewURI(const nsACString &spec,
+ const char *charset,
+ nsIURI *baseURI,
+ nsIURI **result)
+{
+ nsCOMPtr<nsIStandardURL> url = new nsStandardURL(true);
+ if (!url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ const nsACString *specPtr = &spec;
+
+#if defined(XP_WIN)
+ nsAutoCString buf;
+ if (net_NormalizeFileURL(spec, buf))
+ specPtr = &buf;
+#endif
+
+ nsresult rv = url->Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1,
+ *specPtr, charset, baseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(url, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsFileChannel *chan = new nsFileChannel(uri);
+ if (!chan)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(chan);
+
+ nsresult rv = chan->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(chan);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = chan->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(chan);
+ return rv;
+ }
+
+ *result = chan;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewChannel(nsIURI *uri, nsIChannel **result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+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 *file, nsIURI **result)
+{
+ NS_ENSURE_ARG_POINTER(file);
+ nsresult rv;
+
+ nsCOMPtr<nsIFileURL> url = new nsStandardURL(true);
+ if (!url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // NOTE: the origin charset is assigned the value of the platform
+ // charset by the SetFile method.
+ rv = url->SetFile(file);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(url, result);
+}
+
+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..211eb2873e
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.h
@@ -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/. */
+
+#ifndef nsFileProtocolHandler_h__
+#define nsFileProtocolHandler_h__
+
+#include "nsIFileProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsFileProtocolHandler : public nsIFileProtocolHandler
+ , public nsSupportsWeakReference
+{
+ virtual ~nsFileProtocolHandler() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIFILEPROTOCOLHANDLER
+
+ nsFileProtocolHandler();
+
+ MOZ_MUST_USE 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..c39d54f518
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileProtocolHandler.idl
@@ -0,0 +1,65 @@
+/* -*- 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;
+
+[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);
+
+ /**
+ * 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);
+};
diff --git a/netwerk/protocol/ftp/FTPChannelChild.cpp b/netwerk/protocol/ftp/FTPChannelChild.cpp
new file mode 100644
index 0000000000..f8284aae3c
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -0,0 +1,932 @@
+/* -*- 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/ChannelDiverterChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "nsFtpProtocolHandler.h"
+#include "nsITabChild.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "base/compiler_specific.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsIPrompt.h"
+
+using namespace mozilla::ipc;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+namespace net {
+
+FTPChannelChild::FTPChannelChild(nsIURI* uri)
+: mIPCOpen(false)
+, mUnknownDecoderInvolved(false)
+, mCanceled(false)
+, mSuspendCount(0)
+, mIsPending(false)
+, mLastModifiedTime(0)
+, mStartPos(0)
+, mDivertingToParent(false)
+, mFlushedForDiversion(false)
+, mSuspendSent(false)
+{
+ LOG(("Creating FTPChannelChild @%x\n", this));
+ // grab a reference to the handler to ensure that it doesn't go away.
+ NS_ADDREF(gFtpHandler);
+ SetURI(uri);
+ mEventQ = new ChannelEventQueue(static_cast<nsIFTPChannel*>(this));
+
+ // 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 @%x\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,
+ nsIDivertableChannel)
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelChild::GetLastModifiedTime(PRTime* lastModifiedTime)
+{
+ *lastModifiedTime = mLastModifiedTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::SetLastModifiedTime(PRTime lastModifiedTime)
+{
+ 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& entityID)
+{
+ entityID = mEntityID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo)
+{
+ DROP_DEAD();
+}
+
+NS_IMETHODIMP
+FTPChannelChild::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentType,
+ int64_t contentLength)
+{
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ mUploadStream = stream;
+ // NOTE: contentLength is intentionally ignored here.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetUploadStream(nsIInputStream** stream)
+{
+ NS_ENSURE_ARG_POINTER(stream);
+ *stream = mUploadStream;
+ NS_IF_ADDREF(*stream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::AsyncOpen(::nsIStreamListener* listener, nsISupports* aContext)
+{
+ LOG(("FTPChannelChild::AsyncOpen [this=%p]\n", this));
+
+ 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.
+ nsresult rv;
+ 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::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsITabChild),
+ getter_AddRefs(iTabChild));
+ GetCallback(iTabChild);
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+ if (MissingRequiredTabChild(tabChild, "ftp")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mListener = listener;
+ mListenerContext = aContext;
+
+ // add ourselves to the load group.
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ OptionalInputStreamParams uploadStream;
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ SerializeInputStream(mUploadStream, uploadStream, fds);
+
+ MOZ_ASSERT(fds.IsEmpty());
+
+ FTPChannelOpenArgs openArgs;
+ SerializeURI(nsBaseChannel::URI(), openArgs.uri());
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.uploadStream() = uploadStream;
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ GetLoadInfo(getter_AddRefs(loadInfo));
+ rv = mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gNeckoChild->
+ SendPFTPChannelConstructor(this, tabChild, 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* result)
+{
+ *result = mIsPending;
+ return NS_OK;
+}
+
+nsresult
+FTPChannelChild::OpenContentStream(bool async,
+ nsIInputStream** stream,
+ nsIChannel** channel)
+{
+ NS_RUNTIMEABORT("FTPChannel*Child* should never have OpenContentStream called!");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::PFTPChannelChild
+//-----------------------------------------------------------------------------
+
+class FTPStartRequestEvent : public ChannelEvent
+{
+public:
+ FTPStartRequestEvent(FTPChannelChild* aChild,
+ const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI)
+ : mChild(aChild)
+ , mChannelStatus(aChannelStatus)
+ , mContentLength(aContentLength)
+ , mContentType(aContentType)
+ , mLastModified(aLastModified)
+ , mEntityID(aEntityID)
+ , mURI(aURI)
+ {
+ }
+ void Run()
+ {
+ mChild->DoOnStartRequest(mChannelStatus, mContentLength, mContentType,
+ mLastModified, mEntityID, mURI);
+ }
+
+private:
+ FTPChannelChild* mChild;
+ nsresult mChannelStatus;
+ int64_t mContentLength;
+ nsCString mContentType;
+ PRTime mLastModified;
+ nsCString mEntityID;
+ URIParams mURI;
+};
+
+bool
+FTPChannelChild::RecvOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI)
+{
+ // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+ // stage, as they are set in the listener's OnStartRequest.
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "mFlushedForDiversion should be unset before OnStartRequest!");
+ MOZ_RELEASE_ASSERT(!mDivertingToParent,
+ "mDivertingToParent should be unset before OnStartRequest!");
+
+ LOG(("FTPChannelChild::RecvOnStartRequest [this=%p]\n", this));
+
+ mEventQ->RunOrEnqueue(new FTPStartRequestEvent(this, aChannelStatus,
+ aContentLength, aContentType,
+ aLastModified, aEntityID,
+ aURI));
+ return true;
+}
+
+void
+FTPChannelChild::DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI)
+{
+ LOG(("FTPChannelChild::DoOnStartRequest [this=%p]\n", this));
+
+ // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+ // stage, as they are set in the listener's OnStartRequest.
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "mFlushedForDiversion should be unset before OnStartRequest!");
+ MOZ_RELEASE_ASSERT(!mDivertingToParent,
+ "mDivertingToParent should be unset before OnStartRequest!");
+
+ 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)) {
+ rv = nsBaseChannel::URI()->SetSpec(spec);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ } else {
+ Cancel(rv);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnStartRequest(this, mListenerContext);
+ if (NS_FAILED(rv))
+ Cancel(rv);
+
+ if (mDivertingToParent) {
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+ }
+}
+
+class FTPDataAvailableEvent : public ChannelEvent
+{
+public:
+ FTPDataAvailableEvent(FTPChannelChild* aChild,
+ const nsresult& aChannelStatus,
+ const nsCString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount)
+ : mChild(aChild)
+ , mChannelStatus(aChannelStatus)
+ , mData(aData)
+ , mOffset(aOffset)
+ , mCount(aCount)
+ {
+ }
+ void Run()
+ {
+ mChild->DoOnDataAvailable(mChannelStatus, mData, mOffset, mCount);
+ }
+
+private:
+ FTPChannelChild* mChild;
+ nsresult mChannelStatus;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+bool
+FTPChannelChild::RecvOnDataAvailable(const nsresult& channelStatus,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be receiving any more callbacks from parent!");
+
+ LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this));
+
+ mEventQ->RunOrEnqueue(new FTPDataAvailableEvent(this, channelStatus, data,
+ offset, count),
+ mDivertingToParent);
+
+ return true;
+}
+
+class MaybeDivertOnDataFTPEvent : public ChannelEvent
+{
+ public:
+ MaybeDivertOnDataFTPEvent(FTPChannelChild* child,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mChild(child)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count) {}
+
+ void Run()
+ {
+ mChild->MaybeDivertOnData(mData, mOffset, mCount);
+ }
+
+ private:
+ FTPChannelChild* mChild;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+void
+FTPChannelChild::MaybeDivertOnData(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ if (mDivertingToParent) {
+ SendDivertOnDataAvailable(data, offset, count);
+ }
+}
+
+void
+FTPChannelChild::DoOnDataAvailable(const nsresult& channelStatus,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("FTPChannelChild::DoOnDataAvailable [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = channelStatus;
+ }
+
+ if (mDivertingToParent) {
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be processing any more callbacks from parent!");
+
+ SendDivertOnDataAvailable(data, offset, count);
+ return;
+ }
+
+ if (mCanceled)
+ return;
+
+ if (mUnknownDecoderInvolved) {
+ mUnknownDecoderEventQ.AppendElement(
+ MakeUnique<MaybeDivertOnDataFTPEvent>(this, data, offset, count));
+ }
+
+ // 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),
+ data.get(),
+ count,
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnDataAvailable(this, mListenerContext,
+ stringStream, offset, count);
+ if (NS_FAILED(rv))
+ Cancel(rv);
+ stringStream->Close();
+}
+
+class FTPStopRequestEvent : public ChannelEvent
+{
+public:
+ FTPStopRequestEvent(FTPChannelChild* aChild,
+ const nsresult& aChannelStatus,
+ const nsCString &aErrorMsg,
+ bool aUseUTF8)
+ : mChild(aChild)
+ , mChannelStatus(aChannelStatus)
+ , mErrorMsg(aErrorMsg)
+ , mUseUTF8(aUseUTF8)
+ {
+ }
+ void Run()
+ {
+ mChild->DoOnStopRequest(mChannelStatus, mErrorMsg, mUseUTF8);
+ }
+
+private:
+ FTPChannelChild* mChild;
+ nsresult mChannelStatus;
+ nsCString mErrorMsg;
+ bool mUseUTF8;
+};
+
+bool
+FTPChannelChild::RecvOnStopRequest(const nsresult& aChannelStatus,
+ const nsCString &aErrorMsg,
+ const bool &aUseUTF8)
+{
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be receiving any more callbacks from parent!");
+
+ LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%x]\n",
+ this, aChannelStatus));
+
+ mEventQ->RunOrEnqueue(new FTPStopRequestEvent(this, aChannelStatus, aErrorMsg,
+ aUseUTF8));
+ return true;
+}
+
+class nsFtpChildAsyncAlert : public Runnable
+{
+public:
+ nsFtpChildAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg)
+ : mPrompter(aPrompter)
+ , mResponseMsg(aResponseMsg)
+ {
+ MOZ_COUNT_CTOR(nsFtpChildAsyncAlert);
+ }
+protected:
+ virtual ~nsFtpChildAsyncAlert()
+ {
+ MOZ_COUNT_DTOR(nsFtpChildAsyncAlert);
+ }
+public:
+ NS_IMETHOD Run() override
+ {
+ if (mPrompter) {
+ mPrompter->Alert(nullptr, mResponseMsg.get());
+ }
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIPrompt> mPrompter;
+ nsString mResponseMsg;
+};
+
+class MaybeDivertOnStopFTPEvent : public ChannelEvent
+{
+ public:
+ MaybeDivertOnStopFTPEvent(FTPChannelChild* child,
+ const nsresult& aChannelStatus)
+ : mChild(child)
+ , mChannelStatus(aChannelStatus) {}
+
+ void Run()
+ {
+ mChild->MaybeDivertOnStop(mChannelStatus);
+ }
+
+ private:
+ FTPChannelChild* mChild;
+ nsresult mChannelStatus;
+};
+
+void
+FTPChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
+{
+ if (mDivertingToParent) {
+ SendDivertOnStopRequest(aChannelStatus);
+ }
+}
+
+void
+FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus,
+ const nsCString &aErrorMsg,
+ bool aUseUTF8)
+{
+ LOG(("FTPChannelChild::DoOnStopRequest [this=%p status=%x]\n",
+ this, aChannelStatus));
+
+ if (mDivertingToParent) {
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be processing any more callbacks from parent!");
+
+ SendDivertOnStopRequest(aChannelStatus);
+ return;
+ }
+
+ if (!mCanceled)
+ mStatus = aChannelStatus;
+
+ if (mUnknownDecoderInvolved) {
+ mUnknownDecoderEventQ.AppendElement(
+ MakeUnique<MaybeDivertOnStopFTPEvent>(this, aChannelStatus));
+ }
+
+ { // Ensure that all queued ipdl events are dispatched before
+ // we initiate protocol deletion below.
+ mIsPending = false;
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ (void)mListener->OnStopRequest(this, mListenerContext, aChannelStatus);
+
+ if (NS_FAILED(aChannelStatus) && !aErrorMsg.IsEmpty()) {
+ nsCOMPtr<nsIPrompt> prompter;
+ GetCallback(prompter);
+ if (prompter) {
+ nsCOMPtr<nsIRunnable> alertEvent;
+ if (aUseUTF8) {
+ alertEvent = new nsFtpChildAsyncAlert(prompter,
+ NS_ConvertUTF8toUTF16(aErrorMsg));
+ } else {
+ alertEvent = new nsFtpChildAsyncAlert(prompter,
+ NS_ConvertASCIItoUTF16(aErrorMsg));
+ }
+ NS_DispatchToMainThread(alertEvent);
+ }
+ }
+
+ mListener = nullptr;
+ mListenerContext = 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);
+}
+
+class FTPFailedAsyncOpenEvent : public ChannelEvent
+{
+ public:
+ FTPFailedAsyncOpenEvent(FTPChannelChild* aChild, nsresult aStatus)
+ : mChild(aChild), mStatus(aStatus) {}
+ void Run() { mChild->DoFailedAsyncOpen(mStatus); }
+ private:
+ FTPChannelChild* mChild;
+ nsresult mStatus;
+};
+
+bool
+FTPChannelChild::RecvFailedAsyncOpen(const nsresult& statusCode)
+{
+ LOG(("FTPChannelChild::RecvFailedAsyncOpen [this=%p status=%x]\n",
+ this, statusCode));
+ mEventQ->RunOrEnqueue(new FTPFailedAsyncOpenEvent(this, statusCode));
+ return true;
+}
+
+void
+FTPChannelChild::DoFailedAsyncOpen(const nsresult& statusCode)
+{
+ LOG(("FTPChannelChild::DoFailedAsyncOpen [this=%p status=%x]\n",
+ this, statusCode));
+ mStatus = statusCode;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, statusCode);
+
+ if (mListener) {
+ mListener->OnStartRequest(this, mListenerContext);
+ mIsPending = false;
+ mListener->OnStopRequest(this, mListenerContext, statusCode);
+ } else {
+ mIsPending = false;
+ }
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mIPCOpen)
+ Send__delete__(this);
+}
+
+class FTPFlushedForDiversionEvent : public ChannelEvent
+{
+ public:
+ explicit FTPFlushedForDiversionEvent(FTPChannelChild* aChild)
+ : mChild(aChild)
+ {
+ MOZ_RELEASE_ASSERT(aChild);
+ }
+
+ void Run()
+ {
+ mChild->FlushedForDiversion();
+ }
+ private:
+ FTPChannelChild* mChild;
+};
+
+bool
+FTPChannelChild::RecvFlushedForDiversion()
+{
+ LOG(("FTPChannelChild::RecvFlushedForDiversion [this=%p]\n", this));
+ MOZ_ASSERT(mDivertingToParent);
+
+ mEventQ->RunOrEnqueue(new FTPFlushedForDiversionEvent(this), true);
+ return true;
+}
+
+void
+FTPChannelChild::FlushedForDiversion()
+{
+ LOG(("FTPChannelChild::FlushedForDiversion [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+
+ // Once this is set, it should not be unset before FTPChannelChild is taken
+ // down. After it is set, no OnStart/OnData/OnStop callbacks should be
+ // received from the parent channel, nor dequeued from the ChannelEventQueue.
+ mFlushedForDiversion = true;
+
+ SendDivertComplete();
+}
+
+bool
+FTPChannelChild::RecvDivertMessages()
+{
+ LOG(("FTPChannelChild::RecvDivertMessages [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+ MOZ_RELEASE_ASSERT(mSuspendCount > 0);
+
+ // DivertTo() has been called on parent, so we can now start sending queued
+ // IPDL messages back to parent listener.
+ if (NS_WARN_IF(NS_FAILED(Resume()))) {
+ return false;
+ }
+ return true;
+}
+
+class FTPDeleteSelfEvent : public ChannelEvent
+{
+ public:
+ explicit FTPDeleteSelfEvent(FTPChannelChild* aChild)
+ : mChild(aChild) {}
+ void Run() { mChild->DoDeleteSelf(); }
+ private:
+ FTPChannelChild* mChild;
+};
+
+bool
+FTPChannelChild::RecvDeleteSelf()
+{
+ mEventQ->RunOrEnqueue(new FTPDeleteSelfEvent(this));
+ return true;
+}
+
+void
+FTPChannelChild::DoDeleteSelf()
+{
+ if (mIPCOpen)
+ Send__delete__(this);
+}
+
+NS_IMETHODIMP
+FTPChannelChild::Cancel(nsresult status)
+{
+ LOG(("FTPChannelChild::Cancel [this=%p]\n", this));
+ if (mCanceled)
+ return NS_OK;
+
+ mCanceled = true;
+ mStatus = status;
+ if (mIPCOpen)
+ SendCancel(status);
+ 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.
+ // 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++ && !mDivertingToParent) {
+ 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.
+ // 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 && (!mDivertingToParent || mSuspendSent)) {
+ SendResume();
+ }
+ mEventQ->Resume();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelChild::ConnectParent(uint32_t id)
+{
+ LOG(("FTPChannelChild::ConnectParent [this=%p]\n", this));
+
+ mozilla::dom::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsITabChild),
+ getter_AddRefs(iTabChild));
+ GetCallback(iTabChild);
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+
+ // 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(id);
+
+ if (!gNeckoChild->SendPFTPChannelConstructor(this, tabChild,
+ IPC::SerializedLoadContext(this),
+ connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::CompleteRedirectSetup(nsIStreamListener *listener,
+ nsISupports *aContext)
+{
+ 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 = listener;
+ mListenerContext = aContext;
+
+ // 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;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::nsIDivertableChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+FTPChannelChild::DivertToParent(ChannelDiverterChild **aChild)
+{
+ MOZ_RELEASE_ASSERT(aChild);
+ MOZ_RELEASE_ASSERT(gNeckoChild);
+ MOZ_RELEASE_ASSERT(!mDivertingToParent);
+
+ LOG(("FTPChannelChild::DivertToParent [this=%p]\n", this));
+
+ // We must fail DivertToParent() if there's no parent end of the channel (and
+ // won't be!) due to early failure.
+ if (NS_FAILED(mStatus) && !mIPCOpen) {
+ return mStatus;
+ }
+
+ nsresult rv = Suspend();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Once this is set, it should not be unset before the child is taken down.
+ mDivertingToParent = true;
+
+ PChannelDiverterChild* diverter =
+ gNeckoChild->SendPChannelDiverterConstructor(this);
+ MOZ_RELEASE_ASSERT(diverter);
+
+ *aChild = static_cast<ChannelDiverterChild*>(diverter);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::UnknownDecoderInvolvedKeepData()
+{
+ mUnknownDecoderInvolved = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::UnknownDecoderInvolvedOnStartRequestCalled()
+{
+ mUnknownDecoderInvolved = false;
+
+ nsresult rv = NS_OK;
+
+ if (mDivertingToParent) {
+ rv = mEventQ->PrependEvents(mUnknownDecoderEventQ);
+ }
+ mUnknownDecoderEventQ.Clear();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetDivertingToParent(bool* aDiverting)
+{
+ NS_ENSURE_ARG_POINTER(aDiverting);
+ *aDiverting = mDivertingToParent;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/ftp/FTPChannelChild.h b/netwerk/protocol/ftp/FTPChannelChild.h
new file mode 100644
index 0000000000..f68b85a72d
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelChild.h
@@ -0,0 +1,164 @@
+/* -*- 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 "nsIDivertableChannel.h"
+
+#include "nsIStreamListener.h"
+#include "PrivateBrowsingChannel.h"
+
+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 nsIDivertableChannel
+{
+public:
+ typedef ::nsIStreamListener nsIStreamListener;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFTPCHANNEL
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIRESUMABLECHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIDIVERTABLECHANNEL
+
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+
+ explicit FTPChannelChild(nsIURI* uri);
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ NS_IMETHOD AsyncOpen(nsIStreamListener* listener, nsISupports* aContext) override;
+
+ // Note that we handle this ourselves, overriding the nsBaseChannel
+ // default behavior, in order to be e10s-friendly.
+ NS_IMETHOD IsPending(bool* result) override;
+
+ nsresult OpenContentStream(bool async,
+ nsIInputStream** stream,
+ nsIChannel** channel) override;
+
+ bool IsSuspended();
+
+ void FlushedForDiversion();
+
+protected:
+ virtual ~FTPChannelChild();
+
+ bool RecvOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI) override;
+ bool RecvOnDataAvailable(const nsresult& channelStatus,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count) override;
+ bool RecvOnStopRequest(const nsresult& channelStatus,
+ const nsCString &aErrorMsg,
+ const bool &aUseUTF8) override;
+ bool RecvFailedAsyncOpen(const nsresult& statusCode) override;
+ bool RecvFlushedForDiversion() override;
+ bool RecvDivertMessages() override;
+ bool 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& channelStatus,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void MaybeDivertOnData(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void MaybeDivertOnStop(const nsresult& statusCode);
+ void DoOnStopRequest(const nsresult& statusCode,
+ const nsCString &aErrorMsg,
+ bool aUseUTF8);
+ void DoFailedAsyncOpen(const nsresult& statusCode);
+ void DoDeleteSelf();
+
+ friend class FTPStartRequestEvent;
+ friend class FTPDataAvailableEvent;
+ friend class MaybeDivertOnDataFTPEvent;
+ friend class FTPStopRequestEvent;
+ friend class MaybeDivertOnStopFTPEvent;
+ friend class FTPFailedAsyncOpenEvent;
+ friend class FTPDeleteSelfEvent;
+
+private:
+ nsCOMPtr<nsIInputStream> mUploadStream;
+
+ bool mIPCOpen;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ // If nsUnknownDecoder is involved we queue onDataAvailable (and possibly
+ // OnStopRequest) so that we can divert them if needed when the listener's
+ // OnStartRequest is finally called
+ nsTArray<UniquePtr<ChannelEvent>> mUnknownDecoderEventQ;
+ bool mUnknownDecoderInvolved;
+
+ bool mCanceled;
+ uint32_t mSuspendCount;
+ bool mIsPending;
+
+ PRTime mLastModifiedTime;
+ uint64_t mStartPos;
+ nsCString mEntityID;
+
+ // Once set, OnData and possibly OnStop will be diverted to the parent.
+ bool mDivertingToParent;
+ // Once set, no OnStart/OnData/OnStop callbacks should be received from the
+ // parent channel, nor dequeued from the ChannelEventQueue.
+ bool mFlushedForDiversion;
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ bool mSuspendSent;
+};
+
+inline bool
+FTPChannelChild::IsSuspended()
+{
+ 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..cdd334971b
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -0,0 +1,896 @@
+/* -*- 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/TabParent.h"
+#include "nsFTPChannel.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsFtpProtocolHandler.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIEncodedChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIForcePendingChannel.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Unused.h"
+#include "SerializedLoadContext.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/LoadInfo.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(const PBrowserOrId& aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mIPCClosed(false)
+ , mLoadContext(aLoadContext)
+ , mPBOverride(aOverrideStatus)
+ , mStatus(NS_OK)
+ , mDivertingFromChild(false)
+ , mDivertedOnStartRequest(false)
+ , mSuspendedForDiversion(false)
+ , mUseUTF8(false)
+{
+ nsIProtocolHandler* handler;
+ CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
+ MOZ_ASSERT(handler, "no ftp handler");
+
+ if (aIframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
+ mTabParent = static_cast<dom::TabParent*>(aIframeEmbedding.get_PBrowserParent());
+ }
+
+ 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());
+ }
+ case FTPChannelCreationArgs::TFTPChannelConnectArgs:
+ {
+ const FTPChannelConnectArgs& cArgs = aArgs.get_FTPChannelConnectArgs();
+ return ConnectChannel(cArgs.channelId());
+ }
+ default:
+ NS_NOTREACHED("unknown open type");
+ return false;
+ }
+}
+
+bool
+FTPChannelParent::DoAsyncOpen(const URIParams& aURI,
+ const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const OptionalInputStreamParams& aUploadStream,
+ const OptionalLoadInfoArgs& aLoadInfoArgs)
+{
+ 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);
+ }
+
+ NeckoOriginAttributes 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,
+ nsIRequest::LOAD_NORMAL, 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);
+
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ nsCOMPtr<nsIInputStream> upload = DeserializeInputStream(aUploadStream, fds);
+ if (upload) {
+ // contentType and contentLength are ignored
+ rv = ftpChan->SetUploadStream(upload, EmptyCString(), 0);
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+ }
+
+ rv = ftpChan->ResumeAt(aStartPos, aEntityID);
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ rv = ftpChan->AsyncOpen2(this);
+ }
+ else {
+ rv = ftpChan->AsyncOpen(this, nullptr);
+ }
+
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ return true;
+}
+
+bool
+FTPChannelParent::ConnectChannel(const uint32_t& channelId)
+{
+ nsresult rv;
+
+ LOG(("Looking for a registered channel [this=%p, id=%d]", this, channelId));
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel));
+ if (NS_SUCCEEDED(rv))
+ mChannel = channel;
+
+ LOG((" found channel %p, rv=%08x", mChannel.get(), rv));
+
+ return true;
+}
+
+bool
+FTPChannelParent::RecvCancel(const nsresult& status)
+{
+ if (mChannel)
+ mChannel->Cancel(status);
+
+ return true;
+}
+
+bool
+FTPChannelParent::RecvSuspend()
+{
+ if (mChannel) {
+ SuspendChannel();
+ }
+ return true;
+}
+
+bool
+FTPChannelParent::RecvResume()
+{
+ if (mChannel) {
+ ResumeChannel();
+ }
+ return true;
+}
+
+class FTPDivertDataAvailableEvent : public ChannelEvent
+{
+public:
+ FTPDivertDataAvailableEvent(FTPChannelParent* aParent,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mParent(aParent)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count)
+ {
+ }
+
+ void Run()
+ {
+ mParent->DivertOnDataAvailable(mData, mOffset, mCount);
+ }
+
+private:
+ FTPChannelParent* mParent;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+bool
+FTPChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertOnDataAvailable if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ // Drop OnDataAvailables if the parent was canceled already.
+ if (NS_FAILED(mStatus)) {
+ return true;
+ }
+
+ mEventQ->RunOrEnqueue(new FTPDivertDataAvailableEvent(this, data, offset,
+ count));
+ return true;
+}
+
+void
+FTPChannelParent::DivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("FTPChannelParent::DivertOnDataAvailable [this=%p]\n", this));
+
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertOnDataAvailable if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Drop OnDataAvailables if the parent was canceled already.
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
+ count, NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ rv = OnDataAvailable(mChannel, nullptr, stringStream, offset, count);
+
+ stringStream->Close();
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ }
+}
+
+class FTPDivertStopRequestEvent : public ChannelEvent
+{
+public:
+ FTPDivertStopRequestEvent(FTPChannelParent* aParent,
+ const nsresult& statusCode)
+ : mParent(aParent)
+ , mStatusCode(statusCode)
+ {
+ }
+
+ void Run() {
+ mParent->DivertOnStopRequest(mStatusCode);
+ }
+
+private:
+ FTPChannelParent* mParent;
+ nsresult mStatusCode;
+};
+
+bool
+FTPChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
+{
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertOnStopRequest if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new FTPDivertStopRequestEvent(this, statusCode));
+ return true;
+}
+
+void
+FTPChannelParent::DivertOnStopRequest(const nsresult& statusCode)
+{
+ LOG(("FTPChannelParent::DivertOnStopRequest [this=%p]\n", this));
+
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertOnStopRequest if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Honor the channel's status even if the underlying transaction completed.
+ nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
+
+ // Reset fake pending status in case OnStopRequest has already been called.
+ if (mChannel) {
+ nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(false);
+ }
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ OnStopRequest(mChannel, nullptr, status);
+}
+
+class FTPDivertCompleteEvent : public ChannelEvent
+{
+public:
+ explicit FTPDivertCompleteEvent(FTPChannelParent* aParent)
+ : mParent(aParent)
+ {
+ }
+
+ void Run() {
+ mParent->DivertComplete();
+ }
+
+private:
+ FTPChannelParent* mParent;
+};
+
+bool
+FTPChannelParent::RecvDivertComplete()
+{
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertComplete if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new FTPDivertCompleteEvent(this));
+ return true;
+}
+
+void
+FTPChannelParent::DivertComplete()
+{
+ LOG(("FTPChannelParent::DivertComplete [this=%p]\n", this));
+
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertComplete if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ nsresult rv = ResumeForDiversion();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this));
+
+ if (mDivertingFromChild) {
+ MOZ_RELEASE_ASSERT(mDivertToListener,
+ "Cannot divert if listener is unset!");
+ return mDivertToListener->OnStartRequest(aRequest, aContext);
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest);
+ MOZ_ASSERT(chan);
+ NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
+
+ int64_t contentLength;
+ chan->GetContentLength(&contentLength);
+ nsCString contentType;
+ chan->GetContentType(contentType);
+
+ 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) {
+ httpChan->GetLastModifiedTime(&lastModified);
+ }
+
+ URIParams uriparam;
+ nsCOMPtr<nsIURI> uri;
+ chan->GetURI(getter_AddRefs(uri));
+ SerializeURI(uri, uriparam);
+
+ if (mIPCClosed || !SendOnStartRequest(mStatus, contentLength, contentType,
+ lastModified, entityID, uriparam)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatusCode)
+{
+ LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%ul]\n",
+ this, aStatusCode));
+
+ if (mDivertingFromChild) {
+ MOZ_RELEASE_ASSERT(mDivertToListener,
+ "Cannot divert if listener is unset!");
+ return mDivertToListener->OnStopRequest(aRequest, aContext, aStatusCode);
+ }
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode, mErrorMsg, mUseUTF8)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ LOG(("FTPChannelParent::OnDataAvailable [this=%p]\n", this));
+
+ if (mDivertingFromChild) {
+ MOZ_RELEASE_ASSERT(mDivertToListener,
+ "Cannot divert if listener is unset!");
+ return mDivertToListener->OnDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+ }
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mIPCClosed || !SendOnDataAvailable(mStatus, data, aOffset, aCount))
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::SetParentListener(HttpChannelParentListener* aListener)
+{
+ // Do not need ptr to HttpChannelParentListener.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::NotifyTrackingProtectionDisabled()
+{
+ // 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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 (mTabParent) {
+ return mTabParent->QueryInterface(uuid, result);
+ }
+ } else if (uuid.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ uuid.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsCOMPtr<nsIAuthPromptProvider> provider(do_QueryObject(mTabParent));
+ if (provider) {
+ return provider->GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL,
+ uuid,
+ result);
+ }
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(uuid, result);
+}
+
+nsresult
+FTPChannelParent::SuspendChannel()
+{
+ nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
+ do_QueryInterface(mChannel);
+ if (chan) {
+ return chan->SuspendInternal();
+ } else {
+ return mChannel->Suspend();
+ }
+}
+
+nsresult
+FTPChannelParent::ResumeChannel()
+{
+ nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
+ do_QueryInterface(mChannel);
+ if (chan) {
+ return chan->ResumeInternal();
+ } else {
+ return mChannel->Resume();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::ADivertableParentChannel
+//-----------------------------------------------------------------------------
+nsresult
+FTPChannelParent::SuspendForDiversion()
+{
+ MOZ_ASSERT(mChannel);
+ if (NS_WARN_IF(mDivertingFromChild)) {
+ MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Try suspending the channel. Allow it to fail, since OnStopRequest may have
+ // been called and thus the channel may not be pending.
+ nsresult rv = SuspendChannel();
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
+ mSuspendedForDiversion = NS_SUCCEEDED(rv);
+
+ // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent
+ // to the child.
+ mDivertingFromChild = true;
+
+ nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
+ do_QueryInterface(mChannel);
+ if (chan) {
+ chan->MessageDiversionStarted(this);
+ }
+
+ return NS_OK;
+}
+
+/* private, supporting function for ADivertableParentChannel */
+nsresult
+FTPChannelParent::ResumeForDiversion()
+{
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mDivertToListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot ResumeForDiversion if not diverting!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
+ do_QueryInterface(mChannel);
+ if (chan) {
+ chan->MessageDiversionStop();
+ }
+
+ if (mSuspendedForDiversion) {
+ nsresult rv = ResumeChannel();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailDiversion(NS_ERROR_UNEXPECTED, true);
+ return rv;
+ }
+ mSuspendedForDiversion = false;
+ }
+
+ // Delete() will tear down IPDL, but ref from underlying nsFTPChannel will
+ // keep us alive if there's more data to be delivered to listener.
+ if (NS_WARN_IF(NS_FAILED(Delete()))) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult
+FTPChannelParent::SuspendMessageDiversion()
+{
+ // This only need to suspend message queue.
+ mEventQ->Suspend();
+ return NS_OK;
+}
+
+nsresult
+FTPChannelParent::ResumeMessageDiversion()
+{
+ // This only need to resumes message queue.
+ mEventQ->Resume();
+ return NS_OK;
+}
+
+void
+FTPChannelParent::DivertTo(nsIStreamListener *aListener)
+{
+ MOZ_ASSERT(aListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertTo new listener if diverting is not set!");
+ return;
+ }
+
+ if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ mDivertToListener = aListener;
+
+ // Call OnStartRequest and SendDivertMessages asynchronously to avoid
+ // reentering client context.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod(this, &FTPChannelParent::StartDiversion));
+ return;
+}
+
+void
+FTPChannelParent::StartDiversion()
+{
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot StartDiversion if diverting is not set!");
+ return;
+ }
+
+ // Fake pending status in case OnStopRequest has already been called.
+ if (mChannel) {
+ nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(true);
+ }
+ }
+
+ {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ // Call OnStartRequest for the "DivertTo" listener.
+ nsresult rv = OnStartRequest(mChannel, nullptr);
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ return;
+ }
+ }
+
+ // After OnStartRequest has been called, tell FTPChannelChild to divert the
+ // OnDataAvailables and OnStopRequest to this FTPChannelParent.
+ if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+}
+
+class FTPFailDiversionEvent : public Runnable
+{
+public:
+ FTPFailDiversionEvent(FTPChannelParent *aChannelParent,
+ nsresult aErrorCode,
+ bool aSkipResume)
+ : mChannelParent(aChannelParent)
+ , mErrorCode(aErrorCode)
+ , mSkipResume(aSkipResume)
+ {
+ MOZ_RELEASE_ASSERT(aChannelParent);
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ }
+ NS_IMETHOD Run() override
+ {
+ mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume);
+ return NS_OK;
+ }
+private:
+ RefPtr<FTPChannelParent> mChannelParent;
+ nsresult mErrorCode;
+ bool mSkipResume;
+};
+
+void
+FTPChannelParent::FailDiversion(nsresult aErrorCode,
+ bool aSkipResume)
+{
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ MOZ_RELEASE_ASSERT(mDivertingFromChild);
+ MOZ_RELEASE_ASSERT(mDivertToListener);
+ MOZ_RELEASE_ASSERT(mChannel);
+
+ NS_DispatchToCurrentThread(
+ new FTPFailDiversionEvent(this, aErrorCode, aSkipResume));
+}
+
+void
+FTPChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
+ bool aSkipResume)
+{
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ MOZ_RELEASE_ASSERT(mDivertingFromChild);
+ MOZ_RELEASE_ASSERT(mDivertToListener);
+ MOZ_RELEASE_ASSERT(mChannel);
+
+ mChannel->Cancel(aErrorCode);
+ nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(false);
+ }
+
+ bool isPending = false;
+ nsresult rv = mChannel->IsPending(&isPending);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ // Resume only we suspended earlier.
+ if (mSuspendedForDiversion) {
+ ResumeChannel();
+ }
+ // Channel has already sent OnStartRequest to the child, so ensure that we
+ // call it here if it hasn't already been called.
+ if (!mDivertedOnStartRequest) {
+ nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(true);
+ }
+ mDivertToListener->OnStartRequest(mChannel, nullptr);
+
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(false);
+ }
+ }
+ // If the channel is pending, it will call OnStopRequest itself; otherwise, do
+ // it here.
+ if (!isPending) {
+ mDivertToListener->OnStopRequest(mChannel, nullptr, aErrorCode);
+ }
+ mDivertToListener = nullptr;
+ mChannel = nullptr;
+
+ if (!mIPCClosed) {
+ Unused << SendDeleteSelf();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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..210c996182
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelParent.h
@@ -0,0 +1,146 @@
+/* -*- 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 "ADivertableParentChannel.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 TabParent;
+class PBrowserOrId;
+} // namespace dom
+
+namespace net {
+class ChannelEventQueue;
+
+class FTPChannelParent final : public PFTPChannelParent
+ , public nsIParentChannel
+ , public nsIInterfaceRequestor
+ , public ADivertableParentChannel
+ , public nsIChannelEventSink
+ , public nsIFTPChannelParentInternal
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ FTPChannelParent(const dom::PBrowserOrId& aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus);
+
+ bool Init(const FTPChannelCreationArgs& aOpenArgs);
+
+ // ADivertableParentChannel functions.
+ void DivertTo(nsIStreamListener *aListener) override;
+ nsresult SuspendForDiversion() override;
+ nsresult SuspendMessageDiversion() override;
+ nsresult ResumeMessageDiversion() override;
+
+ // Calls OnStartRequest for "DivertTo" listener, then notifies child channel
+ // that it should divert OnDataAvailable and OnStopRequest calls to this
+ // parent channel.
+ void StartDiversion();
+
+ // Handles calling OnStart/Stop if there are errors during diversion.
+ // Called asynchronously from FailDiversion.
+ void NotifyDiversionFailed(nsresult aErrorCode, bool aSkipResume = true);
+
+ NS_IMETHOD SetErrorMsg(const char *aMsg, bool aUseUTF8) override;
+
+protected:
+ virtual ~FTPChannelParent();
+
+ // private, supporting function for ADivertableParentChannel.
+ nsresult ResumeForDiversion();
+
+ // Asynchronously calls NotifyDiversionFailed.
+ void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
+
+ bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const OptionalInputStreamParams& aUploadStream,
+ const OptionalLoadInfoArgs& aLoadInfoArgs);
+
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during HTTP->FTP redirects.
+ bool ConnectChannel(const uint32_t& channelId);
+
+ void DivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void DivertOnStopRequest(const nsresult& statusCode);
+ void DivertComplete();
+
+ friend class FTPDivertDataAvailableEvent;
+ friend class FTPDivertStopRequestEvent;
+ friend class FTPDivertCompleteEvent;
+
+ virtual bool RecvCancel(const nsresult& status) override;
+ virtual bool RecvSuspend() override;
+ virtual bool RecvResume() override;
+ virtual bool RecvDivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count) override;
+ virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) override;
+ virtual bool RecvDivertComplete() override;
+
+ nsresult SuspendChannel();
+ nsresult ResumeChannel();
+
+ 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;
+
+ // If OnStart/OnData/OnStop have been diverted from the child, forward them to
+ // this listener.
+ nsCOMPtr<nsIStreamListener> mDivertToListener;
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus;
+ // Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it
+ // must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are
+ // received from the child channel.
+ bool mDivertingFromChild;
+ // Set if OnStart|StopRequest was called during a diversion from the child.
+ bool mDivertedOnStartRequest;
+
+ // Set if we successfully suspended the nsHttpChannel for diversion. Unset
+ // when we call ResumeForDiversion.
+ bool mSuspendedForDiversion;
+ RefPtr<mozilla::dom::TabParent> mTabParent;
+
+ 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..55721bdbea
--- /dev/null
+++ b/netwerk/protocol/ftp/PFTPChannel.ipdl
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include InputStreamParams;
+include URIParams;
+
+//FIXME: bug #792908 (NeckoChannelParams already included by PNecko)
+include NeckoChannelParams;
+include protocol PBlob; //FIXME: bug #792908
+
+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();
+
+ // Divert OnDataAvailable to the parent.
+ async DivertOnDataAvailable(nsCString data,
+ uint64_t offset,
+ uint32_t count);
+
+ // Divert OnStopRequest to the parent.
+ async DivertOnStopRequest(nsresult statusCode);
+
+ // Child has no more events/messages to divert to the parent.
+ async DivertComplete();
+
+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);
+
+ // Parent has been suspended for diversion; no more events to be enqueued.
+ async FlushedForDiversion();
+
+ // Child should resume processing the ChannelEventQueue, i.e. diverting any
+ // OnDataAvailable and OnStopRequest messages in the queue back to the parent.
+ async DivertMessages();
+
+ async DeleteSelf();
+};
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/ftp/doc/rfc959.txt b/netwerk/protocol/ftp/doc/rfc959.txt
new file mode 100644
index 0000000000..5c9f11af5d
--- /dev/null
+++ b/netwerk/protocol/ftp/doc/rfc959.txt
@@ -0,0 +1,3933 @@
+
+
+Network Working Group J. Postel
+Request for Comments: 959 J. Reynolds
+ ISI
+Obsoletes RFC: 765 (IEN 149) October 1985
+
+ FILE TRANSFER PROTOCOL (FTP)
+
+
+Status of this Memo
+
+ This memo is the official specification of the File Transfer
+ Protocol (FTP). Distribution of this memo is unlimited.
+
+ The following new optional commands are included in this edition of
+ the specification:
+
+ CDUP (Change to Parent Directory), SMNT (Structure Mount), STOU
+ (Store Unique), RMD (Remove Directory), MKD (Make Directory), PWD
+ (Print Directory), and SYST (System).
+
+ Note that this specification is compatible with the previous edition.
+
+1. INTRODUCTION
+
+ The objectives of FTP are 1) to promote sharing of files (computer
+ programs and/or data), 2) to encourage indirect or implicit (via
+ programs) use of remote computers, 3) to shield a user from
+ variations in file storage systems among hosts, and 4) to transfer
+ data reliably and efficiently. FTP, though usable directly by a user
+ at a terminal, is designed mainly for use by programs.
+
+ The attempt in this specification is to satisfy the diverse needs of
+ users of maxi-hosts, mini-hosts, personal workstations, and TACs,
+ with a simple, and easily implemented protocol design.
+
+ This paper assumes knowledge of the Transmission Control Protocol
+ (TCP) [2] and the Telnet Protocol [3]. These documents are contained
+ in the ARPA-Internet protocol handbook [1].
+
+2. OVERVIEW
+
+ In this section, the history, the terminology, and the FTP model are
+ discussed. The terms defined in this section are only those that
+ have special significance in FTP. Some of the terminology is very
+ specific to the FTP model; some readers may wish to turn to the
+ section on the FTP model while reviewing the terminology.
+
+
+
+
+
+
+
+Postel & Reynolds [Page 1]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 2.1. HISTORY
+
+ FTP has had a long evolution over the years. Appendix III is a
+ chronological compilation of Request for Comments documents
+ relating to FTP. These include the first proposed file transfer
+ mechanisms in 1971 that were developed for implementation on hosts
+ at M.I.T. (RFC 114), plus comments and discussion in RFC 141.
+
+ RFC 172 provided a user-level oriented protocol for file transfer
+ between host computers (including terminal IMPs). A revision of
+ this as RFC 265, restated FTP for additional review, while RFC 281
+ suggested further changes. The use of a "Set Data Type"
+ transaction was proposed in RFC 294 in January 1982.
+
+ RFC 354 obsoleted RFCs 264 and 265. The File Transfer Protocol
+ was now defined as a protocol for file transfer between HOSTs on
+ the ARPANET, with the primary function of FTP defined as
+ transfering files efficiently and reliably among hosts and
+ allowing the convenient use of remote file storage capabilities.
+ RFC 385 further commented on errors, emphasis points, and
+ additions to the protocol, while RFC 414 provided a status report
+ on the working server and user FTPs. RFC 430, issued in 1973,
+ (among other RFCs too numerous to mention) presented further
+ comments on FTP. Finally, an "official" FTP document was
+ published as RFC 454.
+
+ By July 1973, considerable changes from the last versions of FTP
+ were made, but the general structure remained the same. RFC 542
+ was published as a new "official" specification to reflect these
+ changes. However, many implementations based on the older
+ specification were not updated.
+
+ In 1974, RFCs 607 and 614 continued comments on FTP. RFC 624
+ proposed further design changes and minor modifications. In 1975,
+ RFC 686 entitled, "Leaving Well Enough Alone", discussed the
+ differences between all of the early and later versions of FTP.
+ RFC 691 presented a minor revision of RFC 686, regarding the
+ subject of print files.
+
+ Motivated by the transition from the NCP to the TCP as the
+ underlying protocol, a phoenix was born out of all of the above
+ efforts in RFC 765 as the specification of FTP for use on TCP.
+
+ This current edition of the FTP specification is intended to
+ correct some minor documentation errors, to improve the
+ explanation of some protocol features, and to add some new
+ optional commands.
+
+
+Postel & Reynolds [Page 2]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ In particular, the following new optional commands are included in
+ this edition of the specification:
+
+ CDUP - Change to Parent Directory
+
+ SMNT - Structure Mount
+
+ STOU - Store Unique
+
+ RMD - Remove Directory
+
+ MKD - Make Directory
+
+ PWD - Print Directory
+
+ SYST - System
+
+ This specification is compatible with the previous edition. A
+ program implemented in conformance to the previous specification
+ should automatically be in conformance to this specification.
+
+ 2.2. TERMINOLOGY
+
+ ASCII
+
+ The ASCII character set is as defined in the ARPA-Internet
+ Protocol Handbook. In FTP, ASCII characters are defined to be
+ the lower half of an eight-bit code set (i.e., the most
+ significant bit is zero).
+
+ access controls
+
+ Access controls define users' access privileges to the use of a
+ system, and to the files in that system. Access controls are
+ necessary to prevent unauthorized or accidental use of files.
+ It is the prerogative of a server-FTP process to invoke access
+ controls.
+
+ byte size
+
+ There are two byte sizes of interest in FTP: the logical byte
+ size of the file, and the transfer byte size used for the
+ transmission of the data. The transfer byte size is always 8
+ bits. The transfer byte size is not necessarily the byte size
+ in which data is to be stored in a system, nor the logical byte
+ size for interpretation of the structure of the data.
+
+
+
+Postel & Reynolds [Page 3]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ control connection
+
+ The communication path between the USER-PI and SERVER-PI for
+ the exchange of commands and replies. This connection follows
+ the Telnet Protocol.
+
+ data connection
+
+ A full duplex connection over which data is transferred, in a
+ specified mode and type. The data transferred may be a part of
+ a file, an entire file or a number of files. The path may be
+ between a server-DTP and a user-DTP, or between two
+ server-DTPs.
+
+ data port
+
+ The passive data transfer process "listens" on the data port
+ for a connection from the active transfer process in order to
+ open the data connection.
+
+ DTP
+
+ The data transfer process establishes and manages the data
+ connection. The DTP can be passive or active.
+
+ End-of-Line
+
+ The end-of-line sequence defines the separation of printing
+ lines. The sequence is Carriage Return, followed by Line Feed.
+
+ EOF
+
+ The end-of-file condition that defines the end of a file being
+ transferred.
+
+ EOR
+
+ The end-of-record condition that defines the end of a record
+ being transferred.
+
+ error recovery
+
+ A procedure that allows a user to recover from certain errors
+ such as failure of either host system or transfer process. In
+ FTP, error recovery may involve restarting a file transfer at a
+ given checkpoint.
+
+
+
+Postel & Reynolds [Page 4]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ FTP commands
+
+ A set of commands that comprise the control information flowing
+ from the user-FTP to the server-FTP process.
+
+ file
+
+ An ordered set of computer data (including programs), of
+ arbitrary length, uniquely identified by a pathname.
+
+ mode
+
+ The mode in which data is to be transferred via the data
+ connection. The mode defines the data format during transfer
+ including EOR and EOF. The transfer modes defined in FTP are
+ described in the Section on Transmission Modes.
+
+ NVT
+
+ The Network Virtual Terminal as defined in the Telnet Protocol.
+
+ NVFS
+
+ The Network Virtual File System. A concept which defines a
+ standard network file system with standard commands and
+ pathname conventions.
+
+ page
+
+ A file may be structured as a set of independent parts called
+ pages. FTP supports the transmission of discontinuous files as
+ independent indexed pages.
+
+ pathname
+
+ Pathname is defined to be the character string which must be
+ input to a file system by a user in order to identify a file.
+ Pathname normally contains device and/or directory names, and
+ file name specification. FTP does not yet specify a standard
+ pathname convention. Each user must follow the file naming
+ conventions of the file systems involved in the transfer.
+
+ PI
+
+ The protocol interpreter. The user and server sides of the
+ protocol have distinct roles implemented in a user-PI and a
+ server-PI.
+
+
+Postel & Reynolds [Page 5]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ record
+
+ A sequential file may be structured as a number of contiguous
+ parts called records. Record structures are supported by FTP
+ but a file need not have record structure.
+
+ reply
+
+ A reply is an acknowledgment (positive or negative) sent from
+ server to user via the control connection in response to FTP
+ commands. The general form of a reply is a completion code
+ (including error codes) followed by a text string. The codes
+ are for use by programs and the text is usually intended for
+ human users.
+
+ server-DTP
+
+ The data transfer process, in its normal "active" state,
+ establishes the data connection with the "listening" data port.
+ It sets up parameters for transfer and storage, and transfers
+ data on command from its PI. The DTP can be placed in a
+ "passive" state to listen for, rather than initiate a
+ connection on the data port.
+
+ server-FTP process
+
+ A process or set of processes which perform the function of
+ file transfer in cooperation with a user-FTP process and,
+ possibly, another server. The functions consist of a protocol
+ interpreter (PI) and a data transfer process (DTP).
+
+ server-PI
+
+ The server protocol interpreter "listens" on Port L for a
+ connection from a user-PI and establishes a control
+ communication connection. It receives standard FTP commands
+ from the user-PI, sends replies, and governs the server-DTP.
+
+ type
+
+ The data representation type used for data transfer and
+ storage. Type implies certain transformations between the time
+ of data storage and data transfer. The representation types
+ defined in FTP are described in the Section on Establishing
+ Data Connections.
+
+
+
+
+Postel & Reynolds [Page 6]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ user
+
+ A person or a process on behalf of a person wishing to obtain
+ file transfer service. The human user may interact directly
+ with a server-FTP process, but use of a user-FTP process is
+ preferred since the protocol design is weighted towards
+ automata.
+
+ user-DTP
+
+ The data transfer process "listens" on the data port for a
+ connection from a server-FTP process. If two servers are
+ transferring data between them, the user-DTP is inactive.
+
+ user-FTP process
+
+ A set of functions including a protocol interpreter, a data
+ transfer process and a user interface which together perform
+ the function of file transfer in cooperation with one or more
+ server-FTP processes. The user interface allows a local
+ language to be used in the command-reply dialogue with the
+ user.
+
+ user-PI
+
+ The user protocol interpreter initiates the control connection
+ from its port U to the server-FTP process, initiates FTP
+ commands, and governs the user-DTP if that process is part of
+ the file transfer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 7]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 2.3. THE FTP MODEL
+
+ With the above definitions in mind, the following model (shown in
+ Figure 1) may be diagrammed for an FTP service.
+
+ -------------
+ |/---------\|
+ || User || --------
+ ||Interface|<--->| User |
+ |\----^----/| --------
+ ---------- | | |
+ |/------\| FTP Commands |/----V----\|
+ ||Server|<---------------->| User ||
+ || PI || FTP Replies || PI ||
+ |\--^---/| |\----^----/|
+ | | | | | |
+ -------- |/--V---\| Data |/----V----\| --------
+ | File |<--->|Server|<---------------->| User |<--->| File |
+ |System| || DTP || Connection || DTP || |System|
+ -------- |\------/| |\---------/| --------
+ ---------- -------------
+
+ Server-FTP USER-FTP
+
+ NOTES: 1. The data connection may be used in either direction.
+ 2. The data connection need not exist all of the time.
+
+ Figure 1 Model for FTP Use
+
+ In the model described in Figure 1, the user-protocol interpreter
+ initiates the control connection. The control connection follows
+ the Telnet protocol. At the initiation of the user, standard FTP
+ commands are generated by the user-PI and transmitted to the
+ server process via the control connection. (The user may
+ establish a direct control connection to the server-FTP, from a
+ TAC terminal for example, and generate standard FTP commands
+ independently, bypassing the user-FTP process.) Standard replies
+ are sent from the server-PI to the user-PI over the control
+ connection in response to the commands.
+
+ The FTP commands specify the parameters for the data connection
+ (data port, transfer mode, representation type, and structure) and
+ the nature of file system operation (store, retrieve, append,
+ delete, etc.). The user-DTP or its designate should "listen" on
+ the specified data port, and the server initiate the data
+ connection and data transfer in accordance with the specified
+ parameters. It should be noted that the data port need not be in
+
+
+Postel & Reynolds [Page 8]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ the same host that initiates the FTP commands via the control
+ connection, but the user or the user-FTP process must ensure a
+ "listen" on the specified data port. It ought to also be noted
+ that the data connection may be used for simultaneous sending and
+ receiving.
+
+ In another situation a user might wish to transfer files between
+ two hosts, neither of which is a local host. The user sets up
+ control connections to the two servers and then arranges for a
+ data connection between them. In this manner, control information
+ is passed to the user-PI but data is transferred between the
+ server data transfer processes. Following is a model of this
+ server-server interaction.
+
+
+ Control ------------ Control
+ ---------->| User-FTP |<-----------
+ | | User-PI | |
+ | | "C" | |
+ V ------------ V
+ -------------- --------------
+ | Server-FTP | Data Connection | Server-FTP |
+ | "A" |<---------------------->| "B" |
+ -------------- Port (A) Port (B) --------------
+
+
+ Figure 2
+
+ The protocol requires that the control connections be open while
+ data transfer is in progress. It is the responsibility of the
+ user to request the closing of the control connections when
+ finished using the FTP service, while it is the server who takes
+ the action. The server may abort data transfer if the control
+ connections are closed without command.
+
+ The Relationship between FTP and Telnet:
+
+ The FTP uses the Telnet protocol on the control connection.
+ This can be achieved in two ways: first, the user-PI or the
+ server-PI may implement the rules of the Telnet Protocol
+ directly in their own procedures; or, second, the user-PI or
+ the server-PI may make use of the existing Telnet module in the
+ system.
+
+ Ease of implementaion, sharing code, and modular programming
+ argue for the second approach. Efficiency and independence
+
+
+
+Postel & Reynolds [Page 9]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ argue for the first approach. In practice, FTP relies on very
+ little of the Telnet Protocol, so the first approach does not
+ necessarily involve a large amount of code.
+
+3. DATA TRANSFER FUNCTIONS
+
+ Files are transferred only via the data connection. The control
+ connection is used for the transfer of commands, which describe the
+ functions to be performed, and the replies to these commands (see the
+ Section on FTP Replies). Several commands are concerned with the
+ transfer of data between hosts. These data transfer commands include
+ the MODE command which specify how the bits of the data are to be
+ transmitted, and the STRUcture and TYPE commands, which are used to
+ define the way in which the data are to be represented. The
+ transmission and representation are basically independent but the
+ "Stream" transmission mode is dependent on the file structure
+ attribute and if "Compressed" transmission mode is used, the nature
+ of the filler byte depends on the representation type.
+
+ 3.1. DATA REPRESENTATION AND STORAGE
+
+ Data is transferred from a storage device in the sending host to a
+ storage device in the receiving host. Often it is necessary to
+ perform certain transformations on the data because data storage
+ representations in the two systems are different. For example,
+ NVT-ASCII has different data storage representations in different
+ systems. DEC TOPS-20s's generally store NVT-ASCII as five 7-bit
+ ASCII characters, left-justified in a 36-bit word. IBM Mainframe's
+ store NVT-ASCII as 8-bit EBCDIC codes. Multics stores NVT-ASCII
+ as four 9-bit characters in a 36-bit word. It is desirable to
+ convert characters into the standard NVT-ASCII representation when
+ transmitting text between dissimilar systems. The sending and
+ receiving sites would have to perform the necessary
+ transformations between the standard representation and their
+ internal representations.
+
+ A different problem in representation arises when transmitting
+ binary data (not character codes) between host systems with
+ different word lengths. It is not always clear how the sender
+ should send data, and the receiver store it. For example, when
+ transmitting 32-bit bytes from a 32-bit word-length system to a
+ 36-bit word-length system, it may be desirable (for reasons of
+ efficiency and usefulness) to store the 32-bit bytes
+ right-justified in a 36-bit word in the latter system. In any
+ case, the user should have the option of specifying data
+ representation and transformation functions. It should be noted
+
+
+
+Postel & Reynolds [Page 10]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ that FTP provides for very limited data type representations.
+ Transformations desired beyond this limited capability should be
+ performed by the user directly.
+
+ 3.1.1. DATA TYPES
+
+ Data representations are handled in FTP by a user specifying a
+ representation type. This type may implicitly (as in ASCII or
+ EBCDIC) or explicitly (as in Local byte) define a byte size for
+ interpretation which is referred to as the "logical byte size."
+ Note that this has nothing to do with the byte size used for
+ transmission over the data connection, called the "transfer
+ byte size", and the two should not be confused. For example,
+ NVT-ASCII has a logical byte size of 8 bits. If the type is
+ Local byte, then the TYPE command has an obligatory second
+ parameter specifying the logical byte size. The transfer byte
+ size is always 8 bits.
+
+ 3.1.1.1. ASCII TYPE
+
+ This is the default type and must be accepted by all FTP
+ implementations. It is intended primarily for the transfer
+ of text files, except when both hosts would find the EBCDIC
+ type more convenient.
+
+ The sender converts the data from an internal character
+ representation to the standard 8-bit NVT-ASCII
+ representation (see the Telnet specification). The receiver
+ will convert the data from the standard form to his own
+ internal form.
+
+ In accordance with the NVT standard, the <CRLF> sequence
+ should be used where necessary to denote the end of a line
+ of text. (See the discussion of file structure at the end
+ of the Section on Data Representation and Storage.)
+
+ Using the standard NVT-ASCII representation means that data
+ must be interpreted as 8-bit bytes.
+
+ The Format parameter for ASCII and EBCDIC types is discussed
+ below.
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 11]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 3.1.1.2. EBCDIC TYPE
+
+ This type is intended for efficient transfer between hosts
+ which use EBCDIC for their internal character
+ representation.
+
+ For transmission, the data are represented as 8-bit EBCDIC
+ characters. The character code is the only difference
+ between the functional specifications of EBCDIC and ASCII
+ types.
+
+ End-of-line (as opposed to end-of-record--see the discussion
+ of structure) will probably be rarely used with EBCDIC type
+ for purposes of denoting structure, but where it is
+ necessary the <NL> character should be used.
+
+ 3.1.1.3. IMAGE TYPE
+
+ The data are sent as contiguous bits which, for transfer,
+ are packed into the 8-bit transfer bytes. The receiving
+ site must store the data as contiguous bits. The structure
+ of the storage system might necessitate the padding of the
+ file (or of each record, for a record-structured file) to
+ some convenient boundary (byte, word or block). This
+ padding, which must be all zeros, may occur only at the end
+ of the file (or at the end of each record) and there must be
+ a way of identifying the padding bits so that they may be
+ stripped off if the file is retrieved. The padding
+ transformation should be well publicized to enable a user to
+ process a file at the storage site.
+
+ Image type is intended for the efficient storage and
+ retrieval of files and for the transfer of binary data. It
+ is recommended that this type be accepted by all FTP
+ implementations.
+
+ 3.1.1.4. LOCAL TYPE
+
+ The data is transferred in logical bytes of the size
+ specified by the obligatory second parameter, Byte size.
+ The value of Byte size must be a decimal integer; there is
+ no default value. The logical byte size is not necessarily
+ the same as the transfer byte size. If there is a
+ difference in byte sizes, then the logical bytes should be
+ packed contiguously, disregarding transfer byte boundaries
+ and with any necessary padding at the end.
+
+
+
+Postel & Reynolds [Page 12]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ When the data reaches the receiving host, it will be
+ transformed in a manner dependent on the logical byte size
+ and the particular host. This transformation must be
+ invertible (i.e., an identical file can be retrieved if the
+ same parameters are used) and should be well publicized by
+ the FTP implementors.
+
+ For example, a user sending 36-bit floating-point numbers to
+ a host with a 32-bit word could send that data as Local byte
+ with a logical byte size of 36. The receiving host would
+ then be expected to store the logical bytes so that they
+ could be easily manipulated; in this example putting the
+ 36-bit logical bytes into 64-bit double words should
+ suffice.
+
+ In another example, a pair of hosts with a 36-bit word size
+ may send data to one another in words by using TYPE L 36.
+ The data would be sent in the 8-bit transmission bytes
+ packed so that 9 transmission bytes carried two host words.
+
+ 3.1.1.5. FORMAT CONTROL
+
+ The types ASCII and EBCDIC also take a second (optional)
+ parameter; this is to indicate what kind of vertical format
+ control, if any, is associated with a file. The following
+ data representation types are defined in FTP:
+
+ A character file may be transferred to a host for one of
+ three purposes: for printing, for storage and later
+ retrieval, or for processing. If a file is sent for
+ printing, the receiving host must know how the vertical
+ format control is represented. In the second case, it must
+ be possible to store a file at a host and then retrieve it
+ later in exactly the same form. Finally, it should be
+ possible to move a file from one host to another and process
+ the file at the second host without undue trouble. A single
+ ASCII or EBCDIC format does not satisfy all these
+ conditions. Therefore, these types have a second parameter
+ specifying one of the following three formats:
+
+ 3.1.1.5.1. NON PRINT
+
+ This is the default format to be used if the second
+ (format) parameter is omitted. Non-print format must be
+ accepted by all FTP implementations.
+
+
+
+
+Postel & Reynolds [Page 13]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The file need contain no vertical format information. If
+ it is passed to a printer process, this process may
+ assume standard values for spacing and margins.
+
+ Normally, this format will be used with files destined
+ for processing or just storage.
+
+ 3.1.1.5.2. TELNET FORMAT CONTROLS
+
+ The file contains ASCII/EBCDIC vertical format controls
+ (i.e., <CR>, <LF>, <NL>, <VT>, <FF>) which the printer
+ process will interpret appropriately. <CRLF>, in exactly
+ this sequence, also denotes end-of-line.
+
+ 3.1.1.5.2. CARRIAGE CONTROL (ASA)
+
+ The file contains ASA (FORTRAN) vertical format control
+ characters. (See RFC 740 Appendix C; and Communications
+ of the ACM, Vol. 7, No. 10, p. 606, October 1964.) In a
+ line or a record formatted according to the ASA Standard,
+ the first character is not to be printed. Instead, it
+ should be used to determine the vertical movement of the
+ paper which should take place before the rest of the
+ record is printed.
+
+ The ASA Standard specifies the following control
+ characters:
+
+ Character Vertical Spacing
+
+ blank Move paper up one line
+ 0 Move paper up two lines
+ 1 Move paper to top of next page
+ + No movement, i.e., overprint
+
+ Clearly there must be some way for a printer process to
+ distinguish the end of the structural entity. If a file
+ has record structure (see below) this is no problem;
+ records will be explicitly marked during transfer and
+ storage. If the file has no record structure, the <CRLF>
+ end-of-line sequence is used to separate printing lines,
+ but these format effectors are overridden by the ASA
+ controls.
+
+
+
+
+
+
+Postel & Reynolds [Page 14]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 3.1.2. DATA STRUCTURES
+
+ In addition to different representation types, FTP allows the
+ structure of a file to be specified. Three file structures are
+ defined in FTP:
+
+ file-structure, where there is no internal structure and
+ the file is considered to be a
+ continuous sequence of data bytes,
+
+ record-structure, where the file is made up of sequential
+ records,
+
+ and page-structure, where the file is made up of independent
+ indexed pages.
+
+ File-structure is the default to be assumed if the STRUcture
+ command has not been used but both file and record structures
+ must be accepted for "text" files (i.e., files with TYPE ASCII
+ or EBCDIC) by all FTP implementations. The structure of a file
+ will affect both the transfer mode of a file (see the Section
+ on Transmission Modes) and the interpretation and storage of
+ the file.
+
+ The "natural" structure of a file will depend on which host
+ stores the file. A source-code file will usually be stored on
+ an IBM Mainframe in fixed length records but on a DEC TOPS-20
+ as a stream of characters partitioned into lines, for example
+ by <CRLF>. If the transfer of files between such disparate
+ sites is to be useful, there must be some way for one site to
+ recognize the other's assumptions about the file.
+
+ With some sites being naturally file-oriented and others
+ naturally record-oriented there may be problems if a file with
+ one structure is sent to a host oriented to the other. If a
+ text file is sent with record-structure to a host which is file
+ oriented, then that host should apply an internal
+ transformation to the file based on the record structure.
+ Obviously, this transformation should be useful, but it must
+ also be invertible so that an identical file may be retrieved
+ using record structure.
+
+ In the case of a file being sent with file-structure to a
+ record-oriented host, there exists the question of what
+ criteria the host should use to divide the file into records
+ which can be processed locally. If this division is necessary,
+ the FTP implementation should use the end-of-line sequence,
+
+
+Postel & Reynolds [Page 15]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ <CRLF> for ASCII, or <NL> for EBCDIC text files, as the
+ delimiter. If an FTP implementation adopts this technique, it
+ must be prepared to reverse the transformation if the file is
+ retrieved with file-structure.
+
+ 3.1.2.1. FILE STRUCTURE
+
+ File structure is the default to be assumed if the STRUcture
+ command has not been used.
+
+ In file-structure there is no internal structure and the
+ file is considered to be a continuous sequence of data
+ bytes.
+
+ 3.1.2.2. RECORD STRUCTURE
+
+ Record structures must be accepted for "text" files (i.e.,
+ files with TYPE ASCII or EBCDIC) by all FTP implementations.
+
+ In record-structure the file is made up of sequential
+ records.
+
+ 3.1.2.3. PAGE STRUCTURE
+
+ To transmit files that are discontinuous, FTP defines a page
+ structure. Files of this type are sometimes known as
+ "random access files" or even as "holey files". In these
+ files there is sometimes other information associated with
+ the file as a whole (e.g., a file descriptor), or with a
+ section of the file (e.g., page access controls), or both.
+ In FTP, the sections of the file are called pages.
+
+ To provide for various page sizes and associated
+ information, each page is sent with a page header. The page
+ header has the following defined fields:
+
+ Header Length
+
+ The number of logical bytes in the page header
+ including this byte. The minimum header length is 4.
+
+ Page Index
+
+ The logical page number of this section of the file.
+ This is not the transmission sequence number of this
+ page, but the index used to identify this page of the
+ file.
+
+
+Postel & Reynolds [Page 16]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Data Length
+
+ The number of logical bytes in the page data. The
+ minimum data length is 0.
+
+ Page Type
+
+ The type of page this is. The following page types
+ are defined:
+
+ 0 = Last Page
+
+ This is used to indicate the end of a paged
+ structured transmission. The header length must
+ be 4, and the data length must be 0.
+
+ 1 = Simple Page
+
+ This is the normal type for simple paged files
+ with no page level associated control
+ information. The header length must be 4.
+
+ 2 = Descriptor Page
+
+ This type is used to transmit the descriptive
+ information for the file as a whole.
+
+ 3 = Access Controlled Page
+
+ This type includes an additional header field
+ for paged files with page level access control
+ information. The header length must be 5.
+
+ Optional Fields
+
+ Further header fields may be used to supply per page
+ control information, for example, per page access
+ control.
+
+ All fields are one logical byte in length. The logical byte
+ size is specified by the TYPE command. See Appendix I for
+ further details and a specific case at the page structure.
+
+ A note of caution about parameters: a file must be stored and
+ retrieved with the same parameters if the retrieved version is to
+
+
+
+
+Postel & Reynolds [Page 17]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ be identical to the version originally transmitted. Conversely,
+ FTP implementations must return a file identical to the original
+ if the parameters used to store and retrieve a file are the same.
+
+ 3.2. ESTABLISHING DATA CONNECTIONS
+
+ The mechanics of transferring data consists of setting up the data
+ connection to the appropriate ports and choosing the parameters
+ for transfer. Both the user and the server-DTPs have a default
+ data port. The user-process default data port is the same as the
+ control connection port (i.e., U). The server-process default
+ data port is the port adjacent to the control connection port
+ (i.e., L-1).
+
+ The transfer byte size is 8-bit bytes. This byte size is relevant
+ only for the actual transfer of the data; it has no bearing on
+ representation of the data within a host's file system.
+
+ The passive data transfer process (this may be a user-DTP or a
+ second server-DTP) shall "listen" on the data port prior to
+ sending a transfer request command. The FTP request command
+ determines the direction of the data transfer. The server, upon
+ receiving the transfer request, will initiate the data connection
+ to the port. When the connection is established, the data
+ transfer begins between DTP's, and the server-PI sends a
+ confirming reply to the user-PI.
+
+ Every FTP implementation must support the use of the default data
+ ports, and only the USER-PI can initiate a change to non-default
+ ports.
+
+ It is possible for the user to specify an alternate data port by
+ use of the PORT command. The user may want a file dumped on a TAC
+ line printer or retrieved from a third party host. In the latter
+ case, the user-PI sets up control connections with both
+ server-PI's. One server is then told (by an FTP command) to
+ "listen" for a connection which the other will initiate. The
+ user-PI sends one server-PI a PORT command indicating the data
+ port of the other. Finally, both are sent the appropriate
+ transfer commands. The exact sequence of commands and replies
+ sent between the user-controller and the servers is defined in the
+ Section on FTP Replies.
+
+ In general, it is the server's responsibility to maintain the data
+ connection--to initiate it and to close it. The exception to this
+
+
+
+
+Postel & Reynolds [Page 18]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ is when the user-DTP is sending the data in a transfer mode that
+ requires the connection to be closed to indicate EOF. The server
+ MUST close the data connection under the following conditions:
+
+ 1. The server has completed sending data in a transfer mode
+ that requires a close to indicate EOF.
+
+ 2. The server receives an ABORT command from the user.
+
+ 3. The port specification is changed by a command from the
+ user.
+
+ 4. The control connection is closed legally or otherwise.
+
+ 5. An irrecoverable error condition occurs.
+
+ Otherwise the close is a server option, the exercise of which the
+ server must indicate to the user-process by either a 250 or 226
+ reply only.
+
+ 3.3. DATA CONNECTION MANAGEMENT
+
+ Default Data Connection Ports: All FTP implementations must
+ support use of the default data connection ports, and only the
+ User-PI may initiate the use of non-default ports.
+
+ Negotiating Non-Default Data Ports: The User-PI may specify a
+ non-default user side data port with the PORT command. The
+ User-PI may request the server side to identify a non-default
+ server side data port with the PASV command. Since a connection
+ is defined by the pair of addresses, either of these actions is
+ enough to get a different data connection, still it is permitted
+ to do both commands to use new ports on both ends of the data
+ connection.
+
+ Reuse of the Data Connection: When using the stream mode of data
+ transfer the end of the file must be indicated by closing the
+ connection. This causes a problem if multiple files are to be
+ transfered in the session, due to need for TCP to hold the
+ connection record for a time out period to guarantee the reliable
+ communication. Thus the connection can not be reopened at once.
+
+ There are two solutions to this problem. The first is to
+ negotiate a non-default port. The second is to use another
+ transfer mode.
+
+ A comment on transfer modes. The stream transfer mode is
+
+
+Postel & Reynolds [Page 19]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ inherently unreliable, since one can not determine if the
+ connection closed prematurely or not. The other transfer modes
+ (Block, Compressed) do not close the connection to indicate the
+ end of file. They have enough FTP encoding that the data
+ connection can be parsed to determine the end of the file.
+ Thus using these modes one can leave the data connection open
+ for multiple file transfers.
+
+ 3.4. TRANSMISSION MODES
+
+ The next consideration in transferring data is choosing the
+ appropriate transmission mode. There are three modes: one which
+ formats the data and allows for restart procedures; one which also
+ compresses the data for efficient transfer; and one which passes
+ the data with little or no processing. In this last case the mode
+ interacts with the structure attribute to determine the type of
+ processing. In the compressed mode, the representation type
+ determines the filler byte.
+
+ All data transfers must be completed with an end-of-file (EOF)
+ which may be explicitly stated or implied by the closing of the
+ data connection. For files with record structure, all the
+ end-of-record markers (EOR) are explicit, including the final one.
+ For files transmitted in page structure a "last-page" page type is
+ used.
+
+ NOTE: In the rest of this section, byte means "transfer byte"
+ except where explicitly stated otherwise.
+
+ For the purpose of standardized transfer, the sending host will
+ translate its internal end of line or end of record denotation
+ into the representation prescribed by the transfer mode and file
+ structure, and the receiving host will perform the inverse
+ translation to its internal denotation. An IBM Mainframe record
+ count field may not be recognized at another host, so the
+ end-of-record information may be transferred as a two byte control
+ code in Stream mode or as a flagged bit in a Block or Compressed
+ mode descriptor. End-of-line in an ASCII or EBCDIC file with no
+ record structure should be indicated by <CRLF> or <NL>,
+ respectively. Since these transformations imply extra work for
+ some systems, identical systems transferring non-record structured
+ text files might wish to use a binary representation and stream
+ mode for the transfer.
+
+
+
+
+
+
+Postel & Reynolds [Page 20]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The following transmission modes are defined in FTP:
+
+ 3.4.1. STREAM MODE
+
+ The data is transmitted as a stream of bytes. There is no
+ restriction on the representation type used; record structures
+ are allowed.
+
+ In a record structured file EOR and EOF will each be indicated
+ by a two-byte control code. The first byte of the control code
+ will be all ones, the escape character. The second byte will
+ have the low order bit on and zeros elsewhere for EOR and the
+ second low order bit on for EOF; that is, the byte will have
+ value 1 for EOR and value 2 for EOF. EOR and EOF may be
+ indicated together on the last byte transmitted by turning both
+ low order bits on (i.e., the value 3). If a byte of all ones
+ was intended to be sent as data, it should be repeated in the
+ second byte of the control code.
+
+ If the structure is a file structure, the EOF is indicated by
+ the sending host closing the data connection and all bytes are
+ data bytes.
+
+ 3.4.2. BLOCK MODE
+
+ The file is transmitted as a series of data blocks preceded by
+ one or more header bytes. The header bytes contain a count
+ field, and descriptor code. The count field indicates the
+ total length of the data block in bytes, thus marking the
+ beginning of the next data block (there are no filler bits).
+ The descriptor code defines: last block in the file (EOF) last
+ block in the record (EOR), restart marker (see the Section on
+ Error Recovery and Restart) or suspect data (i.e., the data
+ being transferred is suspected of errors and is not reliable).
+ This last code is NOT intended for error control within FTP.
+ It is motivated by the desire of sites exchanging certain types
+ of data (e.g., seismic or weather data) to send and receive all
+ the data despite local errors (such as "magnetic tape read
+ errors"), but to indicate in the transmission that certain
+ portions are suspect). Record structures are allowed in this
+ mode, and any representation type may be used.
+
+ The header consists of the three bytes. Of the 24 bits of
+ header information, the 16 low order bits shall represent byte
+ count, and the 8 high order bits shall represent descriptor
+ codes as shown below.
+
+
+
+Postel & Reynolds [Page 21]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Block Header
+
+ +----------------+----------------+----------------+
+ | Descriptor | Byte Count |
+ | 8 bits | 16 bits |
+ +----------------+----------------+----------------+
+
+
+ The descriptor codes are indicated by bit flags in the
+ descriptor byte. Four codes have been assigned, where each
+ code number is the decimal value of the corresponding bit in
+ the byte.
+
+ Code Meaning
+
+ 128 End of data block is EOR
+ 64 End of data block is EOF
+ 32 Suspected errors in data block
+ 16 Data block is a restart marker
+
+ With this encoding, more than one descriptor coded condition
+ may exist for a particular block. As many bits as necessary
+ may be flagged.
+
+ The restart marker is embedded in the data stream as an
+ integral number of 8-bit bytes representing printable
+ characters in the language being used over the control
+ connection (e.g., default--NVT-ASCII). <SP> (Space, in the
+ appropriate language) must not be used WITHIN a restart marker.
+
+ For example, to transmit a six-character marker, the following
+ would be sent:
+
+ +--------+--------+--------+
+ |Descrptr| Byte count |
+ |code= 16| = 6 |
+ +--------+--------+--------+
+
+ +--------+--------+--------+
+ | Marker | Marker | Marker |
+ | 8 bits | 8 bits | 8 bits |
+ +--------+--------+--------+
+
+ +--------+--------+--------+
+ | Marker | Marker | Marker |
+ | 8 bits | 8 bits | 8 bits |
+ +--------+--------+--------+
+
+
+Postel & Reynolds [Page 22]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 3.4.3. COMPRESSED MODE
+
+ There are three kinds of information to be sent: regular data,
+ sent in a byte string; compressed data, consisting of
+ replications or filler; and control information, sent in a
+ two-byte escape sequence. If n>0 bytes (up to 127) of regular
+ data are sent, these n bytes are preceded by a byte with the
+ left-most bit set to 0 and the right-most 7 bits containing the
+ number n.
+
+ Byte string:
+
+ 1 7 8 8
+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+ |0| n | | d(1) | ... | d(n) |
+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+ ^ ^
+ |---n bytes---|
+ of data
+
+ String of n data bytes d(1),..., d(n)
+ Count n must be positive.
+
+ To compress a string of n replications of the data byte d, the
+ following 2 bytes are sent:
+
+ Replicated Byte:
+
+ 2 6 8
+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+ |1 0| n | | d |
+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+
+ A string of n filler bytes can be compressed into a single
+ byte, where the filler byte varies with the representation
+ type. If the type is ASCII or EBCDIC the filler byte is <SP>
+ (Space, ASCII code 32, EBCDIC code 64). If the type is Image
+ or Local byte the filler is a zero byte.
+
+ Filler String:
+
+ 2 6
+ +-+-+-+-+-+-+-+-+
+ |1 1| n |
+ +-+-+-+-+-+-+-+-+
+
+ The escape sequence is a double byte, the first of which is the
+
+
+Postel & Reynolds [Page 23]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ escape byte (all zeros) and the second of which contains
+ descriptor codes as defined in Block mode. The descriptor
+ codes have the same meaning as in Block mode and apply to the
+ succeeding string of bytes.
+
+ Compressed mode is useful for obtaining increased bandwidth on
+ very large network transmissions at a little extra CPU cost.
+ It can be most effectively used to reduce the size of printer
+ files such as those generated by RJE hosts.
+
+ 3.5. ERROR RECOVERY AND RESTART
+
+ There is no provision for detecting bits lost or scrambled in data
+ transfer; this level of error control is handled by the TCP.
+ However, a restart procedure is provided to protect users from
+ gross system failures (including failures of a host, an
+ FTP-process, or the underlying network).
+
+ The restart procedure is defined only for the block and compressed
+ modes of data transfer. It requires the sender of data to insert
+ a special marker code in the data stream with some marker
+ information. The marker information has meaning only to the
+ sender, but must consist of printable characters in the default or
+ negotiated language of the control connection (ASCII or EBCDIC).
+ The marker could represent a bit-count, a record-count, or any
+ other information by which a system may identify a data
+ checkpoint. The receiver of data, if it implements the restart
+ procedure, would then mark the corresponding position of this
+ marker in the receiving system, and return this information to the
+ user.
+
+ In the event of a system failure, the user can restart the data
+ transfer by identifying the marker point with the FTP restart
+ procedure. The following example illustrates the use of the
+ restart procedure.
+
+ The sender of the data inserts an appropriate marker block in the
+ data stream at a convenient point. The receiving host marks the
+ corresponding data point in its file system and conveys the last
+ known sender and receiver marker information to the user, either
+ directly or over the control connection in a 110 reply (depending
+ on who is the sender). In the event of a system failure, the user
+ or controller process restarts the server at the last server
+ marker by sending a restart command with server's marker code as
+ its argument. The restart command is transmitted over the control
+
+
+
+
+Postel & Reynolds [Page 24]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ connection and is immediately followed by the command (such as
+ RETR, STOR or LIST) which was being executed when the system
+ failure occurred.
+
+4. FILE TRANSFER FUNCTIONS
+
+ The communication channel from the user-PI to the server-PI is
+ established as a TCP connection from the user to the standard server
+ port. The user protocol interpreter is responsible for sending FTP
+ commands and interpreting the replies received; the server-PI
+ interprets commands, sends replies and directs its DTP to set up the
+ data connection and transfer the data. If the second party to the
+ data transfer (the passive transfer process) is the user-DTP, then it
+ is governed through the internal protocol of the user-FTP host; if it
+ is a second server-DTP, then it is governed by its PI on command from
+ the user-PI. The FTP replies are discussed in the next section. In
+ the description of a few of the commands in this section, it is
+ helpful to be explicit about the possible replies.
+
+ 4.1. FTP COMMANDS
+
+ 4.1.1. ACCESS CONTROL COMMANDS
+
+ The following commands specify access control identifiers
+ (command codes are shown in parentheses).
+
+ USER NAME (USER)
+
+ The argument field is a Telnet string identifying the user.
+ The user identification is that which is required by the
+ server for access to its file system. This command will
+ normally be the first command transmitted by the user after
+ the control connections are made (some servers may require
+ this). Additional identification information in the form of
+ a password and/or an account command may also be required by
+ some servers. Servers may allow a new USER command to be
+ entered at any point in order to change the access control
+ and/or accounting information. This has the effect of
+ flushing any user, password, and account information already
+ supplied and beginning the login sequence again. All
+ transfer parameters are unchanged and any file transfer in
+ progress is completed under the old access control
+ parameters.
+
+
+
+
+
+
+Postel & Reynolds [Page 25]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ PASSWORD (PASS)
+
+ The argument field is a Telnet string specifying the user's
+ password. This command must be immediately preceded by the
+ user name command, and, for some sites, completes the user's
+ identification for access control. Since password
+ information is quite sensitive, it is desirable in general
+ to "mask" it or suppress typeout. It appears that the
+ server has no foolproof way to achieve this. It is
+ therefore the responsibility of the user-FTP process to hide
+ the sensitive password information.
+
+ ACCOUNT (ACCT)
+
+ The argument field is a Telnet string identifying the user's
+ account. The command is not necessarily related to the USER
+ command, as some sites may require an account for login and
+ others only for specific access, such as storing files. In
+ the latter case the command may arrive at any time.
+
+ There are reply codes to differentiate these cases for the
+ automation: when account information is required for login,
+ the response to a successful PASSword command is reply code
+ 332. On the other hand, if account information is NOT
+ required for login, the reply to a successful PASSword
+ command is 230; and if the account information is needed for
+ a command issued later in the dialogue, the server should
+ return a 332 or 532 reply depending on whether it stores
+ (pending receipt of the ACCounT command) or discards the
+ command, respectively.
+
+ CHANGE WORKING DIRECTORY (CWD)
+
+ This command allows the user to work with a different
+ directory or dataset for file storage or retrieval without
+ altering his login or accounting information. Transfer
+ parameters are similarly unchanged. The argument is a
+ pathname specifying a directory or other system dependent
+ file group designator.
+
+ CHANGE TO PARENT DIRECTORY (CDUP)
+
+ This command is a special case of CWD, and is included to
+ simplify the implementation of programs for transferring
+ directory trees between operating systems having different
+
+
+
+
+Postel & Reynolds [Page 26]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ syntaxes for naming the parent directory. The reply codes
+ shall be identical to the reply codes of CWD. See
+ Appendix II for further details.
+
+ STRUCTURE MOUNT (SMNT)
+
+ This command allows the user to mount a different file
+ system data structure without altering his login or
+ accounting information. Transfer parameters are similarly
+ unchanged. The argument is a pathname specifying a
+ directory or other system dependent file group designator.
+
+ REINITIALIZE (REIN)
+
+ This command terminates a USER, flushing all I/O and account
+ information, except to allow any transfer in progress to be
+ completed. All parameters are reset to the default settings
+ and the control connection is left open. This is identical
+ to the state in which a user finds himself immediately after
+ the control connection is opened. A USER command may be
+ expected to follow.
+
+ LOGOUT (QUIT)
+
+ This command terminates a USER and if file transfer is not
+ in progress, the server closes the control connection. If
+ file transfer is in progress, the connection will remain
+ open for result response and the server will then close it.
+ If the user-process is transferring files for several USERs
+ but does not wish to close and then reopen connections for
+ each, then the REIN command should be used instead of QUIT.
+
+ An unexpected close on the control connection will cause the
+ server to take the effective action of an abort (ABOR) and a
+ logout (QUIT).
+
+ 4.1.2. TRANSFER PARAMETER COMMANDS
+
+ All data transfer parameters have default values, and the
+ commands specifying data transfer parameters are required only
+ if the default parameter values are to be changed. The default
+ value is the last specified value, or if no value has been
+ specified, the standard default value is as stated here. This
+ implies that the server must "remember" the applicable default
+ values. The commands may be in any order except that they must
+ precede the FTP service request. The following commands
+ specify data transfer parameters:
+
+
+Postel & Reynolds [Page 27]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ DATA PORT (PORT)
+
+ The argument is a HOST-PORT specification for the data port
+ to be used in data connection. There are defaults for both
+ the user and server data ports, and under normal
+ circumstances this command and its reply are not needed. If
+ this command is used, the argument is the concatenation of a
+ 32-bit internet host address and a 16-bit TCP port address.
+ This address information is broken into 8-bit fields and the
+ value of each field is transmitted as a decimal number (in
+ character string representation). The fields are separated
+ by commas. A port command would be:
+
+ PORT h1,h2,h3,h4,p1,p2
+
+ where h1 is the high order 8 bits of the internet host
+ address.
+
+ PASSIVE (PASV)
+
+ This command requests the server-DTP to "listen" on a data
+ port (which is not its default data port) and to wait for a
+ connection rather than initiate one upon receipt of a
+ transfer command. The response to this command includes the
+ host and port address this server is listening on.
+
+ REPRESENTATION TYPE (TYPE)
+
+ The argument specifies the representation type as described
+ in the Section on Data Representation and Storage. Several
+ types take a second parameter. The first parameter is
+ denoted by a single Telnet character, as is the second
+ Format parameter for ASCII and EBCDIC; the second parameter
+ for local byte is a decimal integer to indicate Bytesize.
+ The parameters are separated by a <SP> (Space, ASCII code
+ 32).
+
+ The following codes are assigned for type:
+
+ \ /
+ A - ASCII | | N - Non-print
+ |-><-| T - Telnet format effectors
+ E - EBCDIC| | C - Carriage Control (ASA)
+ / \
+ I - Image
+
+ L <byte size> - Local byte Byte size
+
+
+Postel & Reynolds [Page 28]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The default representation type is ASCII Non-print. If the
+ Format parameter is changed, and later just the first
+ argument is changed, Format then returns to the Non-print
+ default.
+
+ FILE STRUCTURE (STRU)
+
+ The argument is a single Telnet character code specifying
+ file structure described in the Section on Data
+ Representation and Storage.
+
+ The following codes are assigned for structure:
+
+ F - File (no record structure)
+ R - Record structure
+ P - Page structure
+
+ The default structure is File.
+
+ TRANSFER MODE (MODE)
+
+ The argument is a single Telnet character code specifying
+ the data transfer modes described in the Section on
+ Transmission Modes.
+
+ The following codes are assigned for transfer modes:
+
+ S - Stream
+ B - Block
+ C - Compressed
+
+ The default transfer mode is Stream.
+
+ 4.1.3. FTP SERVICE COMMANDS
+
+ The FTP service commands define the file transfer or the file
+ system function requested by the user. The argument of an FTP
+ service command will normally be a pathname. The syntax of
+ pathnames must conform to server site conventions (with
+ standard defaults applicable), and the language conventions of
+ the control connection. The suggested default handling is to
+ use the last specified device, directory or file name, or the
+ standard default defined for local users. The commands may be
+ in any order except that a "rename from" command must be
+ followed by a "rename to" command and the restart command must
+ be followed by the interrupted service command (e.g., STOR or
+ RETR). The data, when transferred in response to FTP service
+
+
+Postel & Reynolds [Page 29]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ commands, shall always be sent over the data connection, except
+ for certain informative replies. The following commands
+ specify FTP service requests:
+
+ RETRIEVE (RETR)
+
+ This command causes the server-DTP to transfer a copy of the
+ file, specified in the pathname, to the server- or user-DTP
+ at the other end of the data connection. The status and
+ contents of the file at the server site shall be unaffected.
+
+ STORE (STOR)
+
+ This command causes the server-DTP to accept the data
+ transferred via the data connection and to store the data as
+ a file at the server site. If the file specified in the
+ pathname exists at the server site, then its contents shall
+ be replaced by the data being transferred. A new file is
+ created at the server site if the file specified in the
+ pathname does not already exist.
+
+ STORE UNIQUE (STOU)
+
+ This command behaves like STOR except that the resultant
+ file is to be created in the current directory under a name
+ unique to that directory. The 250 Transfer Started response
+ must include the name generated.
+
+ APPEND (with create) (APPE)
+
+ This command causes the server-DTP to accept the data
+ transferred via the data connection and to store the data in
+ a file at the server site. If the file specified in the
+ pathname exists at the server site, then the data shall be
+ appended to that file; otherwise the file specified in the
+ pathname shall be created at the server site.
+
+ ALLOCATE (ALLO)
+
+ This command may be required by some servers to reserve
+ sufficient storage to accommodate the new file to be
+ transferred. The argument shall be a decimal integer
+ representing the number of bytes (using the logical byte
+ size) of storage to be reserved for the file. For files
+ sent with record or page structure a maximum record or page
+ size (in logical bytes) might also be necessary; this is
+ indicated by a decimal integer in a second argument field of
+
+
+Postel & Reynolds [Page 30]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ the command. This second argument is optional, but when
+ present should be separated from the first by the three
+ Telnet characters <SP> R <SP>. This command shall be
+ followed by a STORe or APPEnd command. The ALLO command
+ should be treated as a NOOP (no operation) by those servers
+ which do not require that the maximum size of the file be
+ declared beforehand, and those servers interested in only
+ the maximum record or page size should accept a dummy value
+ in the first argument and ignore it.
+
+ RESTART (REST)
+
+ The argument field represents the server marker at which
+ file transfer is to be restarted. This command does not
+ cause file transfer but skips over the file to the specified
+ data checkpoint. This command shall be immediately followed
+ by the appropriate FTP service command which shall cause
+ file transfer to resume.
+
+ RENAME FROM (RNFR)
+
+ This command specifies the old pathname of the file which is
+ to be renamed. This command must be immediately followed by
+ a "rename to" command specifying the new file pathname.
+
+ RENAME TO (RNTO)
+
+ This command specifies the new pathname of the file
+ specified in the immediately preceding "rename from"
+ command. Together the two commands cause a file to be
+ renamed.
+
+ ABORT (ABOR)
+
+ This command tells the server to abort the previous FTP
+ service command and any associated transfer of data. The
+ abort command may require "special action", as discussed in
+ the Section on FTP Commands, to force recognition by the
+ server. No action is to be taken if the previous command
+ has been completed (including data transfer). The control
+ connection is not to be closed by the server, but the data
+ connection must be closed.
+
+ There are two cases for the server upon receipt of this
+ command: (1) the FTP service command was already completed,
+ or (2) the FTP service command is still in progress.
+
+
+
+Postel & Reynolds [Page 31]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ In the first case, the server closes the data connection
+ (if it is open) and responds with a 226 reply, indicating
+ that the abort command was successfully processed.
+
+ In the second case, the server aborts the FTP service in
+ progress and closes the data connection, returning a 426
+ reply to indicate that the service request terminated
+ abnormally. The server then sends a 226 reply,
+ indicating that the abort command was successfully
+ processed.
+
+ DELETE (DELE)
+
+ This command causes the file specified in the pathname to be
+ deleted at the server site. If an extra level of protection
+ is desired (such as the query, "Do you really wish to
+ delete?"), it should be provided by the user-FTP process.
+
+ REMOVE DIRECTORY (RMD)
+
+ This command causes the directory specified in the pathname
+ to be removed as a directory (if the pathname is absolute)
+ or as a subdirectory of the current working directory (if
+ the pathname is relative). See Appendix II.
+
+ MAKE DIRECTORY (MKD)
+
+ This command causes the directory specified in the pathname
+ to be created as a directory (if the pathname is absolute)
+ or as a subdirectory of the current working directory (if
+ the pathname is relative). See Appendix II.
+
+ PRINT WORKING DIRECTORY (PWD)
+
+ This command causes the name of the current working
+ directory to be returned in the reply. See Appendix II.
+
+ LIST (LIST)
+
+ This command causes a list to be sent from the server to the
+ passive DTP. If the pathname specifies a directory or other
+ group of files, the server should transfer a list of files
+ in the specified directory. If the pathname specifies a
+ file then the server should send current information on the
+ file. A null argument implies the user's current working or
+ default directory. The data transfer is over the data
+ connection in type ASCII or type EBCDIC. (The user must
+
+
+Postel & Reynolds [Page 32]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ ensure that the TYPE is appropriately ASCII or EBCDIC).
+ Since the information on a file may vary widely from system
+ to system, this information may be hard to use automatically
+ in a program, but may be quite useful to a human user.
+
+ NAME LIST (NLST)
+
+ This command causes a directory listing to be sent from
+ server to user site. The pathname should specify a
+ directory or other system-specific file group descriptor; a
+ null argument implies the current directory. The server
+ will return a stream of names of files and no other
+ information. The data will be transferred in ASCII or
+ EBCDIC type over the data connection as valid pathname
+ strings separated by <CRLF> or <NL>. (Again the user must
+ ensure that the TYPE is correct.) This command is intended
+ to return information that can be used by a program to
+ further process the files automatically. For example, in
+ the implementation of a "multiple get" function.
+
+ SITE PARAMETERS (SITE)
+
+ This command is used by the server to provide services
+ specific to his system that are essential to file transfer
+ but not sufficiently universal to be included as commands in
+ the protocol. The nature of these services and the
+ specification of their syntax can be stated in a reply to
+ the HELP SITE command.
+
+ SYSTEM (SYST)
+
+ This command is used to find out the type of operating
+ system at the server. The reply shall have as its first
+ word one of the system names listed in the current version
+ of the Assigned Numbers document [4].
+
+ STATUS (STAT)
+
+ This command shall cause a status response to be sent over
+ the control connection in the form of a reply. The command
+ may be sent during a file transfer (along with the Telnet IP
+ and Synch signals--see the Section on FTP Commands) in which
+ case the server will respond with the status of the
+ operation in progress, or it may be sent between file
+ transfers. In the latter case, the command may have an
+ argument field. If the argument is a pathname, the command
+ is analogous to the "list" command except that data shall be
+
+
+Postel & Reynolds [Page 33]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ transferred over the control connection. If a partial
+ pathname is given, the server may respond with a list of
+ file names or attributes associated with that specification.
+ If no argument is given, the server should return general
+ status information about the server FTP process. This
+ should include current values of all transfer parameters and
+ the status of connections.
+
+ HELP (HELP)
+
+ This command shall cause the server to send helpful
+ information regarding its implementation status over the
+ control connection to the user. The command may take an
+ argument (e.g., any command name) and return more specific
+ information as a response. The reply is type 211 or 214.
+ It is suggested that HELP be allowed before entering a USER
+ command. The server may use this reply to specify
+ site-dependent parameters, e.g., in response to HELP SITE.
+
+ NOOP (NOOP)
+
+ This command does not affect any parameters or previously
+ entered commands. It specifies no action other than that the
+ server send an OK reply.
+
+ The File Transfer Protocol follows the specifications of the Telnet
+ protocol for all communications over the control connection. Since
+ the language used for Telnet communication may be a negotiated
+ option, all references in the next two sections will be to the
+ "Telnet language" and the corresponding "Telnet end-of-line code".
+ Currently, one may take these to mean NVT-ASCII and <CRLF>. No other
+ specifications of the Telnet protocol will be cited.
+
+ FTP commands are "Telnet strings" terminated by the "Telnet end of
+ line code". The command codes themselves are alphabetic characters
+ terminated by the character <SP> (Space) if parameters follow and
+ Telnet-EOL otherwise. The command codes and the semantics of
+ commands are described in this section; the detailed syntax of
+ commands is specified in the Section on Commands, the reply sequences
+ are discussed in the Section on Sequencing of Commands and Replies,
+ and scenarios illustrating the use of commands are provided in the
+ Section on Typical FTP Scenarios.
+
+ FTP commands may be partitioned as those specifying access-control
+ identifiers, data transfer parameters, or FTP service requests.
+ Certain commands (such as ABOR, STAT, QUIT) may be sent over the
+ control connection while a data transfer is in progress. Some
+
+
+Postel & Reynolds [Page 34]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ servers may not be able to monitor the control and data connections
+ simultaneously, in which case some special action will be necessary
+ to get the server's attention. The following ordered format is
+ tentatively recommended:
+
+ 1. User system inserts the Telnet "Interrupt Process" (IP) signal
+ in the Telnet stream.
+
+ 2. User system sends the Telnet "Synch" signal.
+
+ 3. User system inserts the command (e.g., ABOR) in the Telnet
+ stream.
+
+ 4. Server PI, after receiving "IP", scans the Telnet stream for
+ EXACTLY ONE FTP command.
+
+ (For other servers this may not be necessary but the actions listed
+ above should have no unusual effect.)
+
+ 4.2. FTP REPLIES
+
+ Replies to File Transfer Protocol commands are devised to ensure
+ the synchronization of requests and actions in the process of file
+ transfer, and to guarantee that the user process always knows the
+ state of the Server. Every command must generate at least one
+ reply, although there may be more than one; in the latter case,
+ the multiple replies must be easily distinguished. In addition,
+ some commands occur in sequential groups, such as USER, PASS and
+ ACCT, or RNFR and RNTO. The replies show the existence of an
+ intermediate state if all preceding commands have been successful.
+ A failure at any point in the sequence necessitates the repetition
+ of the entire sequence from the beginning.
+
+ The details of the command-reply sequence are made explicit in
+ a set of state diagrams below.
+
+ An FTP reply consists of a three digit number (transmitted as
+ three alphanumeric characters) followed by some text. The number
+ is intended for use by automata to determine what state to enter
+ next; the text is intended for the human user. It is intended
+ that the three digits contain enough encoded information that the
+ user-process (the User-PI) will not need to examine the text and
+ may either discard it or pass it on to the user, as appropriate.
+ In particular, the text may be server-dependent, so there are
+ likely to be varying texts for each reply code.
+
+ A reply is defined to contain the 3-digit code, followed by Space
+
+
+Postel & Reynolds [Page 35]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ <SP>, followed by one line of text (where some maximum line length
+ has been specified), and terminated by the Telnet end-of-line
+ code. There will be cases however, where the text is longer than
+ a single line. In these cases the complete text must be bracketed
+ so the User-process knows when it may stop reading the reply (i.e.
+ stop processing input on the control connection) and go do other
+ things. This requires a special format on the first line to
+ indicate that more than one line is coming, and another on the
+ last line to designate it as the last. At least one of these must
+ contain the appropriate reply code to indicate the state of the
+ transaction. To satisfy all factions, it was decided that both
+ the first and last line codes should be the same.
+
+ Thus the format for multi-line replies is that the first line
+ will begin with the exact required reply code, followed
+ immediately by a Hyphen, "-" (also known as Minus), followed by
+ text. The last line will begin with the same code, followed
+ immediately by Space <SP>, optionally some text, and the Telnet
+ end-of-line code.
+
+ For example:
+ 123-First line
+ Second line
+ 234 A line beginning with numbers
+ 123 The last line
+
+ The user-process then simply needs to search for the second
+ occurrence of the same reply code, followed by <SP> (Space), at
+ the beginning of a line, and ignore all intermediary lines. If
+ an intermediary line begins with a 3-digit number, the Server
+ must pad the front to avoid confusion.
+
+ This scheme allows standard system routines to be used for
+ reply information (such as for the STAT reply), with
+ "artificial" first and last lines tacked on. In rare cases
+ where these routines are able to generate three digits and a
+ Space at the beginning of any line, the beginning of each
+ text line should be offset by some neutral text, like Space.
+
+ This scheme assumes that multi-line replies may not be nested.
+
+ The three digits of the reply each have a special significance.
+ This is intended to allow a range of very simple to very
+ sophisticated responses by the user-process. The first digit
+ denotes whether the response is good, bad or incomplete.
+ (Referring to the state diagram), an unsophisticated user-process
+ will be able to determine its next action (proceed as planned,
+
+
+Postel & Reynolds [Page 36]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ redo, retrench, etc.) by simply examining this first digit. A
+ user-process that wants to know approximately what kind of error
+ occurred (e.g. file system error, command syntax error) may
+ examine the second digit, reserving the third digit for the finest
+ gradation of information (e.g., RNTO command without a preceding
+ RNFR).
+
+ There are five values for the first digit of the reply code:
+
+ 1yz Positive Preliminary reply
+
+ The requested action is being initiated; expect another
+ reply before proceeding with a new command. (The
+ user-process sending another command before the
+ completion reply would be in violation of protocol; but
+ server-FTP processes should queue any commands that
+ arrive while a preceding command is in progress.) This
+ type of reply can be used to indicate that the command
+ was accepted and the user-process may now pay attention
+ to the data connections, for implementations where
+ simultaneous monitoring is difficult. The server-FTP
+ process may send at most, one 1yz reply per command.
+
+ 2yz Positive Completion reply
+
+ The requested action has been successfully completed. A
+ new request may be initiated.
+
+ 3yz Positive Intermediate reply
+
+ The command has been accepted, but the requested action
+ is being held in abeyance, pending receipt of further
+ information. The user should send another command
+ specifying this information. This reply is used in
+ command sequence groups.
+
+ 4yz Transient Negative Completion reply
+
+ The command was not accepted and the requested action did
+ not take place, but the error condition is temporary and
+ the action may be requested again. The user should
+ return to the beginning of the command sequence, if any.
+ It is difficult to assign a meaning to "transient",
+ particularly when two distinct sites (Server- and
+ User-processes) have to agree on the interpretation.
+ Each reply in the 4yz category might have a slightly
+ different time value, but the intent is that the
+
+
+Postel & Reynolds [Page 37]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ user-process is encouraged to try again. A rule of thumb
+ in determining if a reply fits into the 4yz or the 5yz
+ (Permanent Negative) category is that replies are 4yz if
+ the commands can be repeated without any change in
+ command form or in properties of the User or Server
+ (e.g., the command is spelled the same with the same
+ arguments used; the user does not change his file access
+ or user name; the server does not put up a new
+ implementation.)
+
+ 5yz Permanent Negative Completion reply
+
+ The command was not accepted and the requested action did
+ not take place. The User-process is discouraged from
+ repeating the exact request (in the same sequence). Even
+ some "permanent" error conditions can be corrected, so
+ the human user may want to direct his User-process to
+ reinitiate the command sequence by direct action at some
+ point in the future (e.g., after the spelling has been
+ changed, or the user has altered his directory status.)
+
+ The following function groupings are encoded in the second
+ digit:
+
+ x0z Syntax - These replies refer to syntax errors,
+ syntactically correct commands that don't fit any
+ functional category, unimplemented or superfluous
+ commands.
+
+ x1z Information - These are replies to requests for
+ information, such as status or help.
+
+ x2z Connections - Replies referring to the control and
+ data connections.
+
+ x3z Authentication and accounting - Replies for the login
+ process and accounting procedures.
+
+ x4z Unspecified as yet.
+
+ x5z File system - These replies indicate the status of the
+ Server file system vis-a-vis the requested transfer or
+ other file system action.
+
+ The third digit gives a finer gradation of meaning in each of
+ the function categories, specified by the second digit. The
+ list of replies below will illustrate this. Note that the text
+
+
+Postel & Reynolds [Page 38]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ associated with each reply is recommended, rather than
+ mandatory, and may even change according to the command with
+ which it is associated. The reply codes, on the other hand,
+ must strictly follow the specifications in the last section;
+ that is, Server implementations should not invent new codes for
+ situations that are only slightly different from the ones
+ described here, but rather should adapt codes already defined.
+
+ A command such as TYPE or ALLO whose successful execution
+ does not offer the user-process any new information will
+ cause a 200 reply to be returned. If the command is not
+ implemented by a particular Server-FTP process because it
+ has no relevance to that computer system, for example ALLO
+ at a TOPS20 site, a Positive Completion reply is still
+ desired so that the simple User-process knows it can proceed
+ with its course of action. A 202 reply is used in this case
+ with, for example, the reply text: "No storage allocation
+ necessary." If, on the other hand, the command requests a
+ non-site-specific action and is unimplemented, the response
+ is 502. A refinement of that is the 504 reply for a command
+ that is implemented, but that requests an unimplemented
+ parameter.
+
+ 4.2.1 Reply Codes by Function Groups
+
+ 200 Command okay.
+ 500 Syntax error, command unrecognized.
+ This may include errors such as command line too long.
+ 501 Syntax error in parameters or arguments.
+ 202 Command not implemented, superfluous at this site.
+ 502 Command not implemented.
+ 503 Bad sequence of commands.
+ 504 Command not implemented for that parameter.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 39]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 110 Restart marker reply.
+ In this case, the text is exact and not left to the
+ particular implementation; it must read:
+ MARK yyyy = mmmm
+ Where yyyy is User-process data stream marker, and mmmm
+ server's equivalent marker (note the spaces between markers
+ and "=").
+ 211 System status, or system help reply.
+ 212 Directory status.
+ 213 File status.
+ 214 Help message.
+ On how to use the server or the meaning of a particular
+ non-standard command. This reply is useful only to the
+ human user.
+ 215 NAME system type.
+ Where NAME is an official system name from the list in the
+ Assigned Numbers document.
+
+ 120 Service ready in nnn minutes.
+ 220 Service ready for new user.
+ 221 Service closing control connection.
+ Logged out if appropriate.
+ 421 Service not available, closing control connection.
+ This may be a reply to any command if the service knows it
+ must shut down.
+ 125 Data connection already open; transfer starting.
+ 225 Data connection open; no transfer in progress.
+ 425 Can't open data connection.
+ 226 Closing data connection.
+ Requested file action successful (for example, file
+ transfer or file abort).
+ 426 Connection closed; transfer aborted.
+ 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
+
+ 230 User logged in, proceed.
+ 530 Not logged in.
+ 331 User name okay, need password.
+ 332 Need account for login.
+ 532 Need account for storing files.
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 40]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 150 File status okay; about to open data connection.
+ 250 Requested file action okay, completed.
+ 257 "PATHNAME" created.
+ 350 Requested file action pending further information.
+ 450 Requested file action not taken.
+ File unavailable (e.g., file busy).
+ 550 Requested action not taken.
+ File unavailable (e.g., file not found, no access).
+ 451 Requested action aborted. Local error in processing.
+ 551 Requested action aborted. Page type unknown.
+ 452 Requested action not taken.
+ Insufficient storage space in system.
+ 552 Requested file action aborted.
+ Exceeded storage allocation (for current directory or
+ dataset).
+ 553 Requested action not taken.
+ File name not allowed.
+
+
+ 4.2.2 Numeric Order List of Reply Codes
+
+ 110 Restart marker reply.
+ In this case, the text is exact and not left to the
+ particular implementation; it must read:
+ MARK yyyy = mmmm
+ Where yyyy is User-process data stream marker, and mmmm
+ server's equivalent marker (note the spaces between markers
+ and "=").
+ 120 Service ready in nnn minutes.
+ 125 Data connection already open; transfer starting.
+ 150 File status okay; about to open data connection.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 41]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 200 Command okay.
+ 202 Command not implemented, superfluous at this site.
+ 211 System status, or system help reply.
+ 212 Directory status.
+ 213 File status.
+ 214 Help message.
+ On how to use the server or the meaning of a particular
+ non-standard command. This reply is useful only to the
+ human user.
+ 215 NAME system type.
+ Where NAME is an official system name from the list in the
+ Assigned Numbers document.
+ 220 Service ready for new user.
+ 221 Service closing control connection.
+ Logged out if appropriate.
+ 225 Data connection open; no transfer in progress.
+ 226 Closing data connection.
+ Requested file action successful (for example, file
+ transfer or file abort).
+ 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
+ 230 User logged in, proceed.
+ 250 Requested file action okay, completed.
+ 257 "PATHNAME" created.
+
+ 331 User name okay, need password.
+ 332 Need account for login.
+ 350 Requested file action pending further information.
+
+ 421 Service not available, closing control connection.
+ This may be a reply to any command if the service knows it
+ must shut down.
+ 425 Can't open data connection.
+ 426 Connection closed; transfer aborted.
+ 450 Requested file action not taken.
+ File unavailable (e.g., file busy).
+ 451 Requested action aborted: local error in processing.
+ 452 Requested action not taken.
+ Insufficient storage space in system.
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 42]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 500 Syntax error, command unrecognized.
+ This may include errors such as command line too long.
+ 501 Syntax error in parameters or arguments.
+ 502 Command not implemented.
+ 503 Bad sequence of commands.
+ 504 Command not implemented for that parameter.
+ 530 Not logged in.
+ 532 Need account for storing files.
+ 550 Requested action not taken.
+ File unavailable (e.g., file not found, no access).
+ 551 Requested action aborted: page type unknown.
+ 552 Requested file action aborted.
+ Exceeded storage allocation (for current directory or
+ dataset).
+ 553 Requested action not taken.
+ File name not allowed.
+
+
+5. DECLARATIVE SPECIFICATIONS
+
+ 5.1. MINIMUM IMPLEMENTATION
+
+ In order to make FTP workable without needless error messages, the
+ following minimum implementation is required for all servers:
+
+ TYPE - ASCII Non-print
+ MODE - Stream
+ STRUCTURE - File, Record
+ COMMANDS - USER, QUIT, PORT,
+ TYPE, MODE, STRU,
+ for the default values
+ RETR, STOR,
+ NOOP.
+
+ The default values for transfer parameters are:
+
+ TYPE - ASCII Non-print
+ MODE - Stream
+ STRU - File
+
+ All hosts must accept the above as the standard defaults.
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 43]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 5.2. CONNECTIONS
+
+ The server protocol interpreter shall "listen" on Port L. The
+ user or user protocol interpreter shall initiate the full-duplex
+ control connection. Server- and user- processes should follow the
+ conventions of the Telnet protocol as specified in the
+ ARPA-Internet Protocol Handbook [1]. Servers are under no
+ obligation to provide for editing of command lines and may require
+ that it be done in the user host. The control connection shall be
+ closed by the server at the user's request after all transfers and
+ replies are completed.
+
+ The user-DTP must "listen" on the specified data port; this may be
+ the default user port (U) or a port specified in the PORT command.
+ The server shall initiate the data connection from his own default
+ data port (L-1) using the specified user data port. The direction
+ of the transfer and the port used will be determined by the FTP
+ service command.
+
+ Note that all FTP implementation must support data transfer using
+ the default port, and that only the USER-PI may initiate the use
+ of non-default ports.
+
+ When data is to be transferred between two servers, A and B (refer
+ to Figure 2), the user-PI, C, sets up control connections with
+ both server-PI's. One of the servers, say A, is then sent a PASV
+ command telling him to "listen" on his data port rather than
+ initiate a connection when he receives a transfer service command.
+ When the user-PI receives an acknowledgment to the PASV command,
+ which includes the identity of the host and port being listened
+ on, the user-PI then sends A's port, a, to B in a PORT command; a
+ reply is returned. The user-PI may then send the corresponding
+ service commands to A and B. Server B initiates the connection
+ and the transfer proceeds. The command-reply sequence is listed
+ below where the messages are vertically synchronous but
+ horizontally asynchronous:
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 44]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ User-PI - Server A User-PI - Server B
+ ------------------ ------------------
+
+ C->A : Connect C->B : Connect
+ C->A : PASV
+ A->C : 227 Entering Passive Mode. A1,A2,A3,A4,a1,a2
+ C->B : PORT A1,A2,A3,A4,a1,a2
+ B->C : 200 Okay
+ C->A : STOR C->B : RETR
+ B->A : Connect to HOST-A, PORT-a
+
+ Figure 3
+
+ The data connection shall be closed by the server under the
+ conditions described in the Section on Establishing Data
+ Connections. If the data connection is to be closed following a
+ data transfer where closing the connection is not required to
+ indicate the end-of-file, the server must do so immediately.
+ Waiting until after a new transfer command is not permitted
+ because the user-process will have already tested the data
+ connection to see if it needs to do a "listen"; (remember that the
+ user must "listen" on a closed data port BEFORE sending the
+ transfer request). To prevent a race condition here, the server
+ sends a reply (226) after closing the data connection (or if the
+ connection is left open, a "file transfer completed" reply (250)
+ and the user-PI should wait for one of these replies before
+ issuing a new transfer command).
+
+ Any time either the user or server see that the connection is
+ being closed by the other side, it should promptly read any
+ remaining data queued on the connection and issue the close on its
+ own side.
+
+ 5.3. COMMANDS
+
+ The commands are Telnet character strings transmitted over the
+ control connections as described in the Section on FTP Commands.
+ The command functions and semantics are described in the Section
+ on Access Control Commands, Transfer Parameter Commands, FTP
+ Service Commands, and Miscellaneous Commands. The command syntax
+ is specified here.
+
+ The commands begin with a command code followed by an argument
+ field. The command codes are four or fewer alphabetic characters.
+ Upper and lower case alphabetic characters are to be treated
+ identically. Thus, any of the following may represent the
+ retrieve command:
+
+
+Postel & Reynolds [Page 45]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ RETR Retr retr ReTr rETr
+
+ This also applies to any symbols representing parameter values,
+ such as A or a for ASCII TYPE. The command codes and the argument
+ fields are separated by one or more spaces.
+
+ The argument field consists of a variable length character string
+ ending with the character sequence <CRLF> (Carriage Return, Line
+ Feed) for NVT-ASCII representation; for other negotiated languages
+ a different end of line character might be used. It should be
+ noted that the server is to take no action until the end of line
+ code is received.
+
+ The syntax is specified below in NVT-ASCII. All characters in the
+ argument field are ASCII characters including any ASCII
+ represented decimal integers. Square brackets denote an optional
+ argument field. If the option is not taken, the appropriate
+ default is implied.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 46]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 5.3.1. FTP COMMANDS
+
+ The following are the FTP commands:
+
+ USER <SP> <username> <CRLF>
+ PASS <SP> <password> <CRLF>
+ ACCT <SP> <account-information> <CRLF>
+ CWD <SP> <pathname> <CRLF>
+ CDUP <CRLF>
+ SMNT <SP> <pathname> <CRLF>
+ QUIT <CRLF>
+ REIN <CRLF>
+ PORT <SP> <host-port> <CRLF>
+ PASV <CRLF>
+ TYPE <SP> <type-code> <CRLF>
+ STRU <SP> <structure-code> <CRLF>
+ MODE <SP> <mode-code> <CRLF>
+ RETR <SP> <pathname> <CRLF>
+ STOR <SP> <pathname> <CRLF>
+ STOU <CRLF>
+ APPE <SP> <pathname> <CRLF>
+ ALLO <SP> <decimal-integer>
+ [<SP> R <SP> <decimal-integer>] <CRLF>
+ REST <SP> <marker> <CRLF>
+ RNFR <SP> <pathname> <CRLF>
+ RNTO <SP> <pathname> <CRLF>
+ ABOR <CRLF>
+ DELE <SP> <pathname> <CRLF>
+ RMD <SP> <pathname> <CRLF>
+ MKD <SP> <pathname> <CRLF>
+ PWD <CRLF>
+ LIST [<SP> <pathname>] <CRLF>
+ NLST [<SP> <pathname>] <CRLF>
+ SITE <SP> <string> <CRLF>
+ SYST <CRLF>
+ STAT [<SP> <pathname>] <CRLF>
+ HELP [<SP> <string>] <CRLF>
+ NOOP <CRLF>
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 47]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 5.3.2. FTP COMMAND ARGUMENTS
+
+ The syntax of the above argument fields (using BNF notation
+ where applicable) is:
+
+ <username> ::= <string>
+ <password> ::= <string>
+ <account-information> ::= <string>
+ <string> ::= <char> | <char><string>
+ <char> ::= any of the 128 ASCII characters except <CR> and
+ <LF>
+ <marker> ::= <pr-string>
+ <pr-string> ::= <pr-char> | <pr-char><pr-string>
+ <pr-char> ::= printable characters, any
+ ASCII code 33 through 126
+ <byte-size> ::= <number>
+ <host-port> ::= <host-number>,<port-number>
+ <host-number> ::= <number>,<number>,<number>,<number>
+ <port-number> ::= <number>,<number>
+ <number> ::= any decimal integer 1 through 255
+ <form-code> ::= N | T | C
+ <type-code> ::= A [<sp> <form-code>]
+ | E [<sp> <form-code>]
+ | I
+ | L <sp> <byte-size>
+ <structure-code> ::= F | R | P
+ <mode-code> ::= S | B | C
+ <pathname> ::= <string>
+ <decimal-integer> ::= any decimal integer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 48]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 5.4. SEQUENCING OF COMMANDS AND REPLIES
+
+ The communication between the user and server is intended to be an
+ alternating dialogue. As such, the user issues an FTP command and
+ the server responds with a prompt primary reply. The user should
+ wait for this initial primary success or failure response before
+ sending further commands.
+
+ Certain commands require a second reply for which the user should
+ also wait. These replies may, for example, report on the progress
+ or completion of file transfer or the closing of the data
+ connection. They are secondary replies to file transfer commands.
+
+ One important group of informational replies is the connection
+ greetings. Under normal circumstances, a server will send a 220
+ reply, "awaiting input", when the connection is completed. The
+ user should wait for this greeting message before sending any
+ commands. If the server is unable to accept input right away, a
+ 120 "expected delay" reply should be sent immediately and a 220
+ reply when ready. The user will then know not to hang up if there
+ is a delay.
+
+ Spontaneous Replies
+
+ Sometimes "the system" spontaneously has a message to be sent
+ to a user (usually all users). For example, "System going down
+ in 15 minutes". There is no provision in FTP for such
+ spontaneous information to be sent from the server to the user.
+ It is recommended that such information be queued in the
+ server-PI and delivered to the user-PI in the next reply
+ (possibly making it a multi-line reply).
+
+ The table below lists alternative success and failure replies for
+ each command. These must be strictly adhered to; a server may
+ substitute text in the replies, but the meaning and action implied
+ by the code numbers and by the specific command reply sequence
+ cannot be altered.
+
+ Command-Reply Sequences
+
+ In this section, the command-reply sequence is presented. Each
+ command is listed with its possible replies; command groups are
+ listed together. Preliminary replies are listed first (with
+ their succeeding replies indented and under them), then
+ positive and negative completion, and finally intermediary
+
+
+
+
+Postel & Reynolds [Page 49]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ replies with the remaining commands from the sequence
+ following. This listing forms the basis for the state
+ diagrams, which will be presented separately.
+
+ Connection Establishment
+ 120
+ 220
+ 220
+ 421
+ Login
+ USER
+ 230
+ 530
+ 500, 501, 421
+ 331, 332
+ PASS
+ 230
+ 202
+ 530
+ 500, 501, 503, 421
+ 332
+ ACCT
+ 230
+ 202
+ 530
+ 500, 501, 503, 421
+ CWD
+ 250
+ 500, 501, 502, 421, 530, 550
+ CDUP
+ 200
+ 500, 501, 502, 421, 530, 550
+ SMNT
+ 202, 250
+ 500, 501, 502, 421, 530, 550
+ Logout
+ REIN
+ 120
+ 220
+ 220
+ 421
+ 500, 502
+ QUIT
+ 221
+ 500
+
+
+
+
+Postel & Reynolds [Page 50]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Transfer parameters
+ PORT
+ 200
+ 500, 501, 421, 530
+ PASV
+ 227
+ 500, 501, 502, 421, 530
+ MODE
+ 200
+ 500, 501, 504, 421, 530
+ TYPE
+ 200
+ 500, 501, 504, 421, 530
+ STRU
+ 200
+ 500, 501, 504, 421, 530
+ File action commands
+ ALLO
+ 200
+ 202
+ 500, 501, 504, 421, 530
+ REST
+ 500, 501, 502, 421, 530
+ 350
+ STOR
+ 125, 150
+ (110)
+ 226, 250
+ 425, 426, 451, 551, 552
+ 532, 450, 452, 553
+ 500, 501, 421, 530
+ STOU
+ 125, 150
+ (110)
+ 226, 250
+ 425, 426, 451, 551, 552
+ 532, 450, 452, 553
+ 500, 501, 421, 530
+ RETR
+ 125, 150
+ (110)
+ 226, 250
+ 425, 426, 451
+ 450, 550
+ 500, 501, 421, 530
+
+
+
+
+Postel & Reynolds [Page 51]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ LIST
+ 125, 150
+ 226, 250
+ 425, 426, 451
+ 450
+ 500, 501, 502, 421, 530
+ NLST
+ 125, 150
+ 226, 250
+ 425, 426, 451
+ 450
+ 500, 501, 502, 421, 530
+ APPE
+ 125, 150
+ (110)
+ 226, 250
+ 425, 426, 451, 551, 552
+ 532, 450, 550, 452, 553
+ 500, 501, 502, 421, 530
+ RNFR
+ 450, 550
+ 500, 501, 502, 421, 530
+ 350
+ RNTO
+ 250
+ 532, 553
+ 500, 501, 502, 503, 421, 530
+ DELE
+ 250
+ 450, 550
+ 500, 501, 502, 421, 530
+ RMD
+ 250
+ 500, 501, 502, 421, 530, 550
+ MKD
+ 257
+ 500, 501, 502, 421, 530, 550
+ PWD
+ 257
+ 500, 501, 502, 421, 550
+ ABOR
+ 225, 226
+ 500, 501, 502, 421
+
+
+
+
+
+
+Postel & Reynolds [Page 52]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Informational commands
+ SYST
+ 215
+ 500, 501, 502, 421
+ STAT
+ 211, 212, 213
+ 450
+ 500, 501, 502, 421, 530
+ HELP
+ 211, 214
+ 500, 501, 502, 421
+ Miscellaneous commands
+ SITE
+ 200
+ 202
+ 500, 501, 530
+ NOOP
+ 200
+ 500 421
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 53]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+6. STATE DIAGRAMS
+
+ Here we present state diagrams for a very simple minded FTP
+ implementation. Only the first digit of the reply codes is used.
+ There is one state diagram for each group of FTP commands or command
+ sequences.
+
+ The command groupings were determined by constructing a model for
+ each command then collecting together the commands with structurally
+ identical models.
+
+ For each command or command sequence there are three possible
+ outcomes: success (S), failure (F), and error (E). In the state
+ diagrams below we use the symbol B for "begin", and the symbol W for
+ "wait for reply".
+
+ We first present the diagram that represents the largest group of FTP
+ commands:
+
+
+ 1,3 +---+
+ ----------->| E |
+ | +---+
+ |
+ +---+ cmd +---+ 2 +---+
+ | B |---------->| W |---------->| S |
+ +---+ +---+ +---+
+ |
+ | 4,5 +---+
+ ----------->| F |
+ +---+
+
+
+ This diagram models the commands:
+
+ ABOR, ALLO, DELE, CWD, CDUP, SMNT, HELP, MODE, NOOP, PASV,
+ QUIT, SITE, PORT, SYST, STAT, RMD, MKD, PWD, STRU, and TYPE.
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 54]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The other large group of commands is represented by a very similar
+ diagram:
+
+
+ 3 +---+
+ ----------->| E |
+ | +---+
+ |
+ +---+ cmd +---+ 2 +---+
+ | B |---------->| W |---------->| S |
+ +---+ --->+---+ +---+
+ | | |
+ | | | 4,5 +---+
+ | 1 | ----------->| F |
+ ----- +---+
+
+
+ This diagram models the commands:
+
+ APPE, LIST, NLST, REIN, RETR, STOR, and STOU.
+
+ Note that this second model could also be used to represent the first
+ group of commands, the only difference being that in the first group
+ the 100 series replies are unexpected and therefore treated as error,
+ while the second group expects (some may require) 100 series replies.
+ Remember that at most, one 100 series reply is allowed per command.
+
+ The remaining diagrams model command sequences, perhaps the simplest
+ of these is the rename sequence:
+
+
+ +---+ RNFR +---+ 1,2 +---+
+ | B |---------->| W |---------->| E |
+ +---+ +---+ -->+---+
+ | | |
+ 3 | | 4,5 |
+ -------------- ------ |
+ | | | +---+
+ | ------------->| S |
+ | | 1,3 | | +---+
+ | 2| --------
+ | | | |
+ V | | |
+ +---+ RNTO +---+ 4,5 ----->+---+
+ | |---------->| W |---------->| F |
+ +---+ +---+ +---+
+
+
+
+Postel & Reynolds [Page 55]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The next diagram is a simple model of the Restart command:
+
+
+ +---+ REST +---+ 1,2 +---+
+ | B |---------->| W |---------->| E |
+ +---+ +---+ -->+---+
+ | | |
+ 3 | | 4,5 |
+ -------------- ------ |
+ | | | +---+
+ | ------------->| S |
+ | | 3 | | +---+
+ | 2| --------
+ | | | |
+ V | | |
+ +---+ cmd +---+ 4,5 ----->+---+
+ | |---------->| W |---------->| F |
+ +---+ -->+---+ +---+
+ | |
+ | 1 |
+ ------
+
+
+ Where "cmd" is APPE, STOR, or RETR.
+
+ We note that the above three models are similar. The Restart differs
+ from the Rename two only in the treatment of 100 series replies at
+ the second stage, while the second group expects (some may require)
+ 100 series replies. Remember that at most, one 100 series reply is
+ allowed per command.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 56]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The most complicated diagram is for the Login sequence:
+
+
+ 1
+ +---+ USER +---+------------->+---+
+ | B |---------->| W | 2 ---->| E |
+ +---+ +---+------ | -->+---+
+ | | | | |
+ 3 | | 4,5 | | |
+ -------------- ----- | | |
+ | | | | |
+ | | | | |
+ | --------- |
+ | 1| | | |
+ V | | | |
+ +---+ PASS +---+ 2 | ------>+---+
+ | |---------->| W |------------->| S |
+ +---+ +---+ ---------->+---+
+ | | | | |
+ 3 | |4,5| | |
+ -------------- -------- |
+ | | | | |
+ | | | | |
+ | -----------
+ | 1,3| | | |
+ V | 2| | |
+ +---+ ACCT +---+-- | ----->+---+
+ | |---------->| W | 4,5 -------->| F |
+ +---+ +---+------------->+---+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 57]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Finally, we present a generalized diagram that could be used to model
+ the command and reply interchange:
+
+
+ ------------------------------------
+ | |
+ Begin | |
+ | V |
+ | +---+ cmd +---+ 2 +---+ |
+ -->| |------->| |---------->| | |
+ | | | W | | S |-----|
+ -->| | -->| |----- | | |
+ | +---+ | +---+ 4,5 | +---+ |
+ | | | | | | |
+ | | | 1| |3 | +---+ |
+ | | | | | | | | |
+ | | ---- | ---->| F |-----
+ | | | | |
+ | | | +---+
+ -------------------
+ |
+ |
+ V
+ End
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 58]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+7. TYPICAL FTP SCENARIO
+
+ User at host U wanting to transfer files to/from host S:
+
+ In general, the user will communicate to the server via a mediating
+ user-FTP process. The following may be a typical scenario. The
+ user-FTP prompts are shown in parentheses, '---->' represents
+ commands from host U to host S, and '<----' represents replies from
+ host S to host U.
+
+ LOCAL COMMANDS BY USER ACTION INVOLVED
+
+ ftp (host) multics<CR> Connect to host S, port L,
+ establishing control connections.
+ <---- 220 Service ready <CRLF>.
+ username Doe <CR> USER Doe<CRLF>---->
+ <---- 331 User name ok,
+ need password<CRLF>.
+ password mumble <CR> PASS mumble<CRLF>---->
+ <---- 230 User logged in<CRLF>.
+ retrieve (local type) ASCII<CR>
+ (local pathname) test 1 <CR> User-FTP opens local file in ASCII.
+ (for. pathname) test.pl1<CR> RETR test.pl1<CRLF> ---->
+ <---- 150 File status okay;
+ about to open data
+ connection<CRLF>.
+ Server makes data connection
+ to port U.
+
+ <---- 226 Closing data connection,
+ file transfer successful<CRLF>.
+ type Image<CR> TYPE I<CRLF> ---->
+ <---- 200 Command OK<CRLF>
+ store (local type) image<CR>
+ (local pathname) file dump<CR> User-FTP opens local file in Image.
+ (for.pathname) >udd>cn>fd<CR> STOR >udd>cn>fd<CRLF> ---->
+ <---- 550 Access denied<CRLF>
+ terminate QUIT <CRLF> ---->
+ Server closes all
+ connections.
+
+8. CONNECTION ESTABLISHMENT
+
+ The FTP control connection is established via TCP between the user
+ process port U and the server process port L. This protocol is
+ assigned the service port 21 (25 octal), that is L=21.
+
+
+
+Postel & Reynolds [Page 59]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+APPENDIX I - PAGE STRUCTURE
+
+ The need for FTP to support page structure derives principally from
+ the need to support efficient transmission of files between TOPS-20
+ systems, particularly the files used by NLS.
+
+ The file system of TOPS-20 is based on the concept of pages. The
+ operating system is most efficient at manipulating files as pages.
+ The operating system provides an interface to the file system so that
+ many applications view files as sequential streams of characters.
+ However, a few applications use the underlying page structures
+ directly, and some of these create holey files.
+
+ A TOPS-20 disk file consists of four things: a pathname, a page
+ table, a (possibly empty) set of pages, and a set of attributes.
+
+ The pathname is specified in the RETR or STOR command. It includes
+ the directory name, file name, file name extension, and generation
+ number.
+
+ The page table contains up to 2**18 entries. Each entry may be
+ EMPTY, or may point to a page. If it is not empty, there are also
+ some page-specific access bits; not all pages of a file need have the
+ same access protection.
+
+ A page is a contiguous set of 512 words of 36 bits each.
+
+ The attributes of the file, in the File Descriptor Block (FDB),
+ contain such things as creation time, write time, read time, writer's
+ byte-size, end-of-file pointer, count of reads and writes, backup
+ system tape numbers, etc.
+
+ Note that there is NO requirement that entries in the page table be
+ contiguous. There may be empty page table slots between occupied
+ ones. Also, the end of file pointer is simply a number. There is no
+ requirement that it in fact point at the "last" datum in the file.
+ Ordinary sequential I/O calls in TOPS-20 will cause the end of file
+ pointer to be left after the last datum written, but other operations
+ may cause it not to be so, if a particular programming system so
+ requires.
+
+ In fact, in both of these special cases, "holey" files and
+ end-of-file pointers NOT at the end of the file, occur with NLS data
+ files.
+
+
+
+
+
+Postel & Reynolds [Page 60]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The TOPS-20 paged files can be sent with the FTP transfer parameters:
+ TYPE L 36, STRU P, and MODE S (in fact, any mode could be used).
+
+ Each page of information has a header. Each header field, which is a
+ logical byte, is a TOPS-20 word, since the TYPE is L 36.
+
+ The header fields are:
+
+ Word 0: Header Length.
+
+ The header length is 5.
+
+ Word 1: Page Index.
+
+ If the data is a disk file page, this is the number of that
+ page in the file's page map. Empty pages (holes) in the file
+ are simply not sent. Note that a hole is NOT the same as a
+ page of zeros.
+
+ Word 2: Data Length.
+
+ The number of data words in this page, following the header.
+ Thus, the total length of the transmission unit is the Header
+ Length plus the Data Length.
+
+ Word 3: Page Type.
+
+ A code for what type of chunk this is. A data page is type 3,
+ the FDB page is type 2.
+
+ Word 4: Page Access Control.
+
+ The access bits associated with the page in the file's page
+ map. (This full word quantity is put into AC2 of an SPACS by
+ the program reading from net to disk.)
+
+ After the header are Data Length data words. Data Length is
+ currently either 512 for a data page or 31 for an FDB. Trailing
+ zeros in a disk file page may be discarded, making Data Length less
+ than 512 in that case.
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 61]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+APPENDIX II - DIRECTORY COMMANDS
+
+ Since UNIX has a tree-like directory structure in which directories
+ are as easy to manipulate as ordinary files, it is useful to expand
+ the FTP servers on these machines to include commands which deal with
+ the creation of directories. Since there are other hosts on the
+ ARPA-Internet which have tree-like directories (including TOPS-20 and
+ Multics), these commands are as general as possible.
+
+ Four directory commands have been added to FTP:
+
+ MKD pathname
+
+ Make a directory with the name "pathname".
+
+ RMD pathname
+
+ Remove the directory with the name "pathname".
+
+ PWD
+
+ Print the current working directory name.
+
+ CDUP
+
+ Change to the parent of the current working directory.
+
+ The "pathname" argument should be created (removed) as a
+ subdirectory of the current working directory, unless the "pathname"
+ string contains sufficient information to specify otherwise to the
+ server, e.g., "pathname" is an absolute pathname (in UNIX and
+ Multics), or pathname is something like "<abso.lute.path>" to
+ TOPS-20.
+
+ REPLY CODES
+
+ The CDUP command is a special case of CWD, and is included to
+ simplify the implementation of programs for transferring directory
+ trees between operating systems having different syntaxes for
+ naming the parent directory. The reply codes for CDUP be
+ identical to the reply codes of CWD.
+
+ The reply codes for RMD be identical to the reply codes for its
+ file analogue, DELE.
+
+ The reply codes for MKD, however, are a bit more complicated. A
+ freshly created directory will probably be the object of a future
+
+
+Postel & Reynolds [Page 62]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ CWD command. Unfortunately, the argument to MKD may not always be
+ a suitable argument for CWD. This is the case, for example, when
+ a TOPS-20 subdirectory is created by giving just the subdirectory
+ name. That is, with a TOPS-20 server FTP, the command sequence
+
+ MKD MYDIR
+ CWD MYDIR
+
+ will fail. The new directory may only be referred to by its
+ "absolute" name; e.g., if the MKD command above were issued while
+ connected to the directory <DFRANKLIN>, the new subdirectory
+ could only be referred to by the name <DFRANKLIN.MYDIR>.
+
+ Even on UNIX and Multics, however, the argument given to MKD may
+ not be suitable. If it is a "relative" pathname (i.e., a pathname
+ which is interpreted relative to the current directory), the user
+ would need to be in the same current directory in order to reach
+ the subdirectory. Depending on the application, this may be
+ inconvenient. It is not very robust in any case.
+
+ To solve these problems, upon successful completion of an MKD
+ command, the server should return a line of the form:
+
+ 257<space>"<directory-name>"<space><commentary>
+
+ That is, the server will tell the user what string to use when
+ referring to the created directory. The directory name can
+ contain any character; embedded double-quotes should be escaped by
+ double-quotes (the "quote-doubling" convention).
+
+ For example, a user connects to the directory /usr/dm, and creates
+ a subdirectory, named pathname:
+
+ CWD /usr/dm
+ 200 directory changed to /usr/dm
+ MKD pathname
+ 257 "/usr/dm/pathname" directory created
+
+ An example with an embedded double quote:
+
+ MKD foo"bar
+ 257 "/usr/dm/foo""bar" directory created
+ CWD /usr/dm/foo"bar
+ 200 directory changed to /usr/dm/foo"bar
+
+
+
+
+
+Postel & Reynolds [Page 63]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The prior existence of a subdirectory with the same name is an
+ error, and the server must return an "access denied" error reply
+ in that case.
+
+ CWD /usr/dm
+ 200 directory changed to /usr/dm
+ MKD pathname
+ 521-"/usr/dm/pathname" directory already exists;
+ 521 taking no action.
+
+ The failure replies for MKD are analogous to its file creating
+ cousin, STOR. Also, an "access denied" return is given if a file
+ name with the same name as the subdirectory will conflict with the
+ creation of the subdirectory (this is a problem on UNIX, but
+ shouldn't be one on TOPS-20).
+
+ Essentially because the PWD command returns the same type of
+ information as the successful MKD command, the successful PWD
+ command uses the 257 reply code as well.
+
+ SUBTLETIES
+
+ Because these commands will be most useful in transferring
+ subtrees from one machine to another, carefully observe that the
+ argument to MKD is to be interpreted as a sub-directory of the
+ current working directory, unless it contains enough information
+ for the destination host to tell otherwise. A hypothetical
+ example of its use in the TOPS-20 world:
+
+ CWD <some.where>
+ 200 Working directory changed
+ MKD overrainbow
+ 257 "<some.where.overrainbow>" directory created
+ CWD overrainbow
+ 431 No such directory
+ CWD <some.where.overrainbow>
+ 200 Working directory changed
+
+ CWD <some.where>
+ 200 Working directory changed to <some.where>
+ MKD <unambiguous>
+ 257 "<unambiguous>" directory created
+ CWD <unambiguous>
+
+ Note that the first example results in a subdirectory of the
+ connected directory. In contrast, the argument in the second
+ example contains enough information for TOPS-20 to tell that the
+
+
+Postel & Reynolds [Page 64]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ <unambiguous> directory is a top-level directory. Note also that
+ in the first example the user "violated" the protocol by
+ attempting to access the freshly created directory with a name
+ other than the one returned by TOPS-20. Problems could have
+ resulted in this case had there been an <overrainbow> directory;
+ this is an ambiguity inherent in some TOPS-20 implementations.
+ Similar considerations apply to the RMD command. The point is
+ this: except where to do so would violate a host's conventions for
+ denoting relative versus absolute pathnames, the host should treat
+ the operands of the MKD and RMD commands as subdirectories. The
+ 257 reply to the MKD command must always contain the absolute
+ pathname of the created directory.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 65]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+APPENDIX III - RFCs on FTP
+
+ Bhushan, Abhay, "A File Transfer Protocol", RFC 114 (NIC 5823),
+ MIT-Project MAC, 16 April 1971.
+
+ Harslem, Eric, and John Heafner, "Comments on RFC 114 (A File
+ Transfer Protocol)", RFC 141 (NIC 6726), RAND, 29 April 1971.
+
+ Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 172
+ (NIC 6794), MIT-Project MAC, 23 June 1971.
+
+ Braden, Bob, "Comments on DTP and FTP Proposals", RFC 238 (NIC 7663),
+ UCLA/CCN, 29 September 1971.
+
+ Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 265
+ (NIC 7813), MIT-Project MAC, 17 November 1971.
+
+ McKenzie, Alex, "A Suggested Addition to File Transfer Protocol",
+ RFC 281 (NIC 8163), BBN, 8 December 1971.
+
+ Bhushan, Abhay, "The Use of "Set Data Type" Transaction in File
+ Transfer Protocol", RFC 294 (NIC 8304), MIT-Project MAC,
+ 25 January 1972.
+
+ Bhushan, Abhay, "The File Transfer Protocol", RFC 354 (NIC 10596),
+ MIT-Project MAC, 8 July 1972.
+
+ Bhushan, Abhay, "Comments on the File Transfer Protocol (RFC 354)",
+ RFC 385 (NIC 11357), MIT-Project MAC, 18 August 1972.
+
+ Hicks, Greg, "User FTP Documentation", RFC 412 (NIC 12404), Utah,
+ 27 November 1972.
+
+ Bhushan, Abhay, "File Transfer Protocol (FTP) Status and Further
+ Comments", RFC 414 (NIC 12406), MIT-Project MAC, 20 November 1972.
+
+ Braden, Bob, "Comments on File Transfer Protocol", RFC 430
+ (NIC 13299), UCLA/CCN, 7 February 1973.
+
+ Thomas, Bob, and Bob Clements, "FTP Server-Server Interaction",
+ RFC 438 (NIC 13770), BBN, 15 January 1973.
+
+ Braden, Bob, "Print Files in FTP", RFC 448 (NIC 13299), UCLA/CCN,
+ 27 February 1973.
+
+ McKenzie, Alex, "File Transfer Protocol", RFC 454 (NIC 14333), BBN,
+ 16 February 1973.
+
+
+Postel & Reynolds [Page 66]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Bressler, Bob, and Bob Thomas, "Mail Retrieval via FTP", RFC 458
+ (NIC 14378), BBN-NET and BBN-TENEX, 20 February 1973.
+
+ Neigus, Nancy, "File Transfer Protocol", RFC 542 (NIC 17759), BBN,
+ 12 July 1973.
+
+ Krilanovich, Mark, and George Gregg, "Comments on the File Transfer
+ Protocol", RFC 607 (NIC 21255), UCSB, 7 January 1974.
+
+ Pogran, Ken, and Nancy Neigus, "Response to RFC 607 - Comments on the
+ File Transfer Protocol", RFC 614 (NIC 21530), BBN, 28 January 1974.
+
+ Krilanovich, Mark, George Gregg, Wayne Hathaway, and Jim White,
+ "Comments on the File Transfer Protocol", RFC 624 (NIC 22054), UCSB,
+ Ames Research Center, SRI-ARC, 28 February 1974.
+
+ Bhushan, Abhay, "FTP Comments and Response to RFC 430", RFC 463
+ (NIC 14573), MIT-DMCG, 21 February 1973.
+
+ Braden, Bob, "FTP Data Compression", RFC 468 (NIC 14742), UCLA/CCN,
+ 8 March 1973.
+
+ Bhushan, Abhay, "FTP and Network Mail System", RFC 475 (NIC 14919),
+ MIT-DMCG, 6 March 1973.
+
+ Bressler, Bob, and Bob Thomas "FTP Server-Server Interaction - II",
+ RFC 478 (NIC 14947), BBN-NET and BBN-TENEX, 26 March 1973.
+
+ White, Jim, "Use of FTP by the NIC Journal", RFC 479 (NIC 14948),
+ SRI-ARC, 8 March 1973.
+
+ White, Jim, "Host-Dependent FTP Parameters", RFC 480 (NIC 14949),
+ SRI-ARC, 8 March 1973.
+
+ Padlipsky, Mike, "An FTP Command-Naming Problem", RFC 506
+ (NIC 16157), MIT-Multics, 26 June 1973.
+
+ Day, John, "Memo to FTP Group (Proposal for File Access Protocol)",
+ RFC 520 (NIC 16819), Illinois, 25 June 1973.
+
+ Merryman, Robert, "The UCSD-CC Server-FTP Facility", RFC 532
+ (NIC 17451), UCSD-CC, 22 June 1973.
+
+ Braden, Bob, "TENEX FTP Problem", RFC 571 (NIC 18974), UCLA/CCN,
+ 15 November 1973.
+
+
+
+
+Postel & Reynolds [Page 67]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ McKenzie, Alex, and Jon Postel, "Telnet and FTP Implementation -
+ Schedule Change", RFC 593 (NIC 20615), BBN and MITRE,
+ 29 November 1973.
+
+ Sussman, Julie, "FTP Error Code Usage for More Reliable Mail
+ Service", RFC 630 (NIC 30237), BBN, 10 April 1974.
+
+ Postel, Jon, "Revised FTP Reply Codes", RFC 640 (NIC 30843),
+ UCLA/NMC, 5 June 1974.
+
+ Harvey, Brian, "Leaving Well Enough Alone", RFC 686 (NIC 32481),
+ SU-AI, 10 May 1975.
+
+ Harvey, Brian, "One More Try on the FTP", RFC 691 (NIC 32700), SU-AI,
+ 28 May 1975.
+
+ Lieb, J., "CWD Command of FTP", RFC 697 (NIC 32963), 14 July 1975.
+
+ Harrenstien, Ken, "FTP Extension: XSEN", RFC 737 (NIC 42217), SRI-KL,
+ 31 October 1977.
+
+ Harrenstien, Ken, "FTP Extension: XRSQ/XRCP", RFC 743 (NIC 42758),
+ SRI-KL, 30 December 1977.
+
+ Lebling, P. David, "Survey of FTP Mail and MLFL", RFC 751, MIT,
+ 10 December 1978.
+
+ Postel, Jon, "File Transfer Protocol Specification", RFC 765, ISI,
+ June 1980.
+
+ Mankins, David, Dan Franklin, and Buzz Owen, "Directory Oriented FTP
+ Commands", RFC 776, BBN, December 1980.
+
+ Padlipsky, Michael, "FTP Unique-Named Store Command", RFC 949, MITRE,
+ July 1985.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 68]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+REFERENCES
+
+ [1] Feinler, Elizabeth, "Internet Protocol Transition Workbook",
+ Network Information Center, SRI International, March 1982.
+
+ [2] Postel, Jon, "Transmission Control Protocol - DARPA Internet
+ Program Protocol Specification", RFC 793, DARPA, September 1981.
+
+ [3] Postel, Jon, and Joyce Reynolds, "Telnet Protocol
+ Specification", RFC 854, ISI, May 1983.
+
+ [4] Reynolds, Joyce, and Jon Postel, "Assigned Numbers", RFC 943,
+ ISI, April 1985.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 69]
+
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..3f708952a4
--- /dev/null
+++ b/netwerk/protocol/ftp/ftpCore.h
@@ -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/. */
+
+#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..060fb7575b
--- /dev/null
+++ b/netwerk/protocol/ftp/moz.build
@@ -0,0 +1,45 @@
+# -*- 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 += [
+ '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['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/protocol/ftp/nsFTPChannel.cpp b/netwerk/protocol/ftp/nsFTPChannel.cpp
new file mode 100644
index 0000000000..2a0f049153
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFTPChannel.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sts=4 sw=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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,
+ nsIChannelWithDivertableParentListener)
+
+//-----------------------------------------------------------------------------
+
+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 **stream)
+{
+ NS_ENSURE_ARG_POINTER(stream);
+ *stream = mUploadStream;
+ NS_IF_ADDREF(*stream);
+ 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)
+{
+ *aProxyInfo = ProxyInfo();
+ NS_IF_ADDREF(*aProxyInfo);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+nsFtpChannel::OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel)
+{
+ if (!async)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsFtpState *state = new nsFtpState();
+ if (!state)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(state);
+
+ nsresult rv = state->Init(this);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(state);
+ return rv;
+ }
+
+ *result = state;
+ 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() {}
+
+public:
+ explicit FTPEventSinkProxy(nsIFTPEventSink* aTarget)
+ : mTarget(aTarget)
+ , mTargetThread(do_GetCurrentThread())
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIFTPEVENTSINK
+
+ class OnFTPControlLogRunnable : public Runnable
+ {
+ public:
+ OnFTPControlLogRunnable(nsIFTPEventSink* aTarget,
+ bool aServer,
+ const char* aMessage)
+ : mTarget(aTarget)
+ , mServer(aServer)
+ , mMessage(aMessage)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIFTPEventSink> mTarget;
+ bool mServer;
+ nsCString mMessage;
+ };
+
+private:
+ nsCOMPtr<nsIFTPEventSink> mTarget;
+ nsCOMPtr<nsIThread> mTargetThread;
+};
+
+NS_IMPL_ISUPPORTS(FTPEventSinkProxy, nsIFTPEventSink)
+
+NS_IMETHODIMP
+FTPEventSinkProxy::OnFTPControlLog(bool aServer, const char* aMsg)
+{
+ RefPtr<OnFTPControlLogRunnable> r =
+ new OnFTPControlLogRunnable(mTarget, aServer, aMsg);
+ return mTargetThread->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));
+
+ nsresult rv = nsBaseChannel::Suspend();
+
+ nsresult rvParentChannel = NS_OK;
+ if (mParentChannel) {
+ rvParentChannel = mParentChannel->SuspendMessageDiversion();
+ }
+
+ return NS_FAILED(rv) ? rv : rvParentChannel;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::Resume()
+{
+ LOG(("nsFtpChannel::Resume [this=%p]\n", this));
+
+ nsresult rv = nsBaseChannel::Resume();
+
+ nsresult rvParentChannel = NS_OK;
+ if (mParentChannel) {
+ rvParentChannel = mParentChannel->ResumeMessageDiversion();
+ }
+
+ return NS_FAILED(rv) ? rv : rvParentChannel;
+}
+
+//-----------------------------------------------------------------------------
+// AChannelHasDivertableParentChannelAsListener internal functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpChannel::MessageDiversionStarted(ADivertableParentChannel *aParentChannel)
+{
+ MOZ_ASSERT(!mParentChannel);
+ mParentChannel = aParentChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::MessageDiversionStop()
+{
+ LOG(("nsFtpChannel::MessageDiversionStop [this=%p]", this));
+ MOZ_ASSERT(mParentChannel);
+ mParentChannel = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::SuspendInternal()
+{
+ LOG(("nsFtpChannel::SuspendInternal [this=%p]\n", this));
+
+ return nsBaseChannel::Suspend();
+}
+
+NS_IMETHODIMP
+nsFtpChannel::ResumeInternal()
+{
+ LOG(("nsFtpChannel::ResumeInternal [this=%p]\n", this));
+
+ return nsBaseChannel::Resume();
+}
diff --git a/netwerk/protocol/ftp/nsFTPChannel.h b/netwerk/protocol/ftp/nsFTPChannel.h
new file mode 100644
index 0000000000..549e577b3d
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFTPChannel.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFTPChannel_h___
+#define nsFTPChannel_h___
+
+#include "nsBaseChannel.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIChannelWithDivertableParentListener.h"
+#include "nsIFTPChannel.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsIProxyInfo.h"
+#include "nsIProxiedChannel.h"
+#include "nsIResumableChannel.h"
+
+class nsIURI;
+using mozilla::net::ADivertableParentChannel;
+
+class nsFtpChannel final : public nsBaseChannel,
+ public nsIFTPChannel,
+ public nsIUploadChannel,
+ public nsIResumableChannel,
+ public nsIProxiedChannel,
+ public nsIForcePendingChannel,
+ public nsIChannelWithDivertableParentListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIRESUMABLECHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSICHANNELWITHDIVERTABLEPARENTLISTENER
+
+ nsFtpChannel(nsIURI *uri, nsIProxyInfo *pi)
+ : mProxyInfo(pi)
+ , mStartPos(0)
+ , mResumeRequested(false)
+ , mLastModifiedTime(0)
+ , mForcePending(false)
+ {
+ SetURI(uri);
+ }
+
+ 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 nsCSubstring &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() {}
+ 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;
+ RefPtr<ADivertableParentChannel> mParentChannel;
+};
+
+#endif /* nsFTPChannel_h___ */
diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
new file mode 100644
index 0000000000..d428b093c0
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
@@ -0,0 +1,2256 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set tw=80 ts=4 sts=4 sw=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <ctype.h>
+
+#include "prprf.h"
+#include "mozilla/Logging.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 "nsIStreamListenerTee.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIStringBundle.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIProtocolProxyService.h"
+#include "nsICancelable.h"
+#include "nsIOutputStream.h"
+#include "nsIPrompt.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProxyInfo.h"
+#include "nsIRunnable.h"
+#include "nsISocketTransportService.h"
+#include "nsIURI.h"
+#include "nsILoadInfo.h"
+#include "nsNullPrincipal.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIFTPChannelParentInternal.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "NetStatistics.h"
+#endif
+
+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)
+ , mReceivedControlData(false)
+ , mTryingCachedControl(false)
+ , mRETRFailed(false)
+ , mFileSize(kJS_MAX_SAFE_UINTEGER)
+ , mServerType(FTP_GENERIC_TYPE)
+ , mAction(GET)
+ , mAnonymous(true)
+ , mRetryPass(false)
+ , mStorReplyReceived(false)
+ , mInternalError(NS_OK)
+ , mReconnectAndLoginAgain(false)
+ , mCacheConnection(true)
+ , mPort(21)
+ , mAddressChecked(false)
+ , mServerIsIPv6(false)
+ , mUseUTF8(false)
+ , mControlStatus(NS_OK)
+ , mDeferredCallbackPending(false)
+{
+ LOG_INFO(("FTP:(%x) nsFtpState created", this));
+
+ // make sure handler stays around
+ NS_ADDREF(gFtpHandler);
+}
+
+nsFtpState::~nsFtpState()
+{
+ LOG_INFO(("FTP:(%x) nsFtpState destroyed", this));
+
+ if (mProxyRequest)
+ mProxyRequest->Cancel(NS_ERROR_FAILURE);
+
+ // release reference to handler
+ nsFtpProtocolHandler *handler = gFtpHandler;
+ NS_RELEASE(handler);
+}
+
+// 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 &&
+ isdigit(line[0]) &&
+ isdigit(line[1]) &&
+ isdigit(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 [%x was-cached=%u]\n",
+ this, mControlConnection.get(), 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:(%x) 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(NS_LITERAL_CSTRING("UTF-8"));
+
+ // 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=%x]\n", this,
+ mControlConnection.get(), rv));
+ mControlConnection = nullptr;
+ return rv;
+ }
+
+ return mControlConnection->WaitData(this);
+}
+
+void
+nsFtpState::MoveToNextState(FTP_STATE nextState)
+{
+ if (NS_FAILED(mInternalError)) {
+ mState = FTP_ERROR;
+ LOG(("FTP:(%x) FAILED (%x)\n", this, 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:(%x) FTP_ERROR - calling StopProcessing\n", this));
+ rv = StopProcessing();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
+ processingRead = false;
+ }
+ break;
+
+ case FTP_COMPLETE:
+ LOG(("FTP:(%x) 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,
+ EmptyString(),
+ EmptyCString());
+
+ 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.Append(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 {
+ nsXPIDLCString 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",
+ getter_Copies(anonPassword));
+ }
+ }
+ if (!anonPassword.IsEmpty()) {
+ passwordStr.AppendASCII(anonPassword);
+ } 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,
+ EmptyString(),
+ EmptyCString());
+
+ 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.Append(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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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.");
+ // Guessing causes crashes.
+ // (Of course, the parsing code should be more robust...)
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleService)
+ return FTP_ERROR;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(NECKO_MSGS_URL,
+ getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+
+ char16_t* ucs2Response = ToNewUnicode(mResponseMsg);
+ const char16_t *formatStrings[1] = { ucs2Response };
+ NS_NAMED_LITERAL_STRING(name, "UnsupportedFTPServer");
+
+ nsXPIDLString formattedString;
+ rv = bundle->FormatStringFromName(name.get(), formatStrings, 1,
+ getter_Copies(formattedString));
+ free(ucs2Response);
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+
+ // TODO(darin): this code should not be dictating UI like this!
+ nsCOMPtr<nsIPrompt> prompter;
+ mChannel->GetCallback(prompter);
+ if (prompter)
+ prompter->Alert(nullptr, formattedString.get());
+
+ // since we just alerted the user, 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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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.Insert("CWD ",0);
+ cwdStr.Append(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.Insert("SIZE ",0);
+ sizeBuf.Append(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.Insert("MDTM ",0);
+ mdtmBuf.Append(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('/');
+ url->SetFilePath(filePath);
+ }
+ }
+ return mChannel->SetContentType(
+ NS_LITERAL_CSTRING(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(EmptyCString());
+
+ 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) {
+ // 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) {
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ 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.Insert("RETR ",0);
+ retrStr.Append(CRLF);
+ return SendFTPCommand(retrStr);
+}
+
+FTP_STATE
+nsFtpState::R_retr() {
+ if (mResponseCode/100 == 2) {
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ return FTP_COMPLETE;
+ }
+
+ if (mResponseCode/100 == 1) {
+ 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.Append(CRLF);
+
+ return SendFTPCommand(restString);
+}
+
+FTP_STATE
+nsFtpState::R_rest() {
+ if (mResponseCode/100 == 4) {
+ // If REST fails, then we can't resume
+ mChannel->SetEntityID(EmptyCString());
+
+ 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.Insert("STOR ",0);
+ storStr.Append(CRLF);
+
+ return SendFTPCommand(storStr);
+}
+
+FTP_STATE
+nsFtpState::R_stor() {
+ if (mResponseCode/100 == 2) {
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ mStorReplyReceived = true;
+
+ // Call Close() if it was not called in nsFtpState::OnStoprequest()
+ if (!mUploadRequest && !IsClosed())
+ Close();
+
+ return FTP_COMPLETE;
+ }
+
+ if (mResponseCode/100 == 1) {
+ LOG(("FTP:(%x) writing on DT\n", this));
+ 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 (!IsIPAddrAny(&mServerAddress))
+ mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) &&
+ !IsIPAddrV4Mapped(&mServerAddress);
+ 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) &&
+ !IsIPAddrV4Mapped(&selfAddress);
+ }
+ }
+ }
+ }
+
+ 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;
+ 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 (!IsIPAddrAny(&mServerAddress)) {
+ char buf[kIPv6CStrBufSize];
+ NetAddrToString(&mServerAddress, 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(nullptr, 0, 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:(%x) created DT (%s:%x)\n", this, host.get(), port));
+
+ // hook ourself up as a proxy for status notifications
+ rv = mDataTransport->SetEventSink(this, NS_GetCurrentThread());
+ 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;
+ rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier),
+ mChannel->UploadStream(),
+ output,
+ stEventTarget,
+ true, // upload stream is buffered
+ false); // output is NOT buffered
+ 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(NS_LITERAL_CSTRING("FEAT" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_feat() {
+ if (mResponseCode/100 == 2) {
+ if (mResponseMsg.Find(NS_LITERAL_CSTRING(CRLF " UTF8" CRLF), true) > -1) {
+ // This FTP server supports UTF-8 encoding
+ mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
+ 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(NS_LITERAL_CSTRING("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
+
+ // initialize counter for network metering
+ mCountRecv = 0;
+
+#ifdef MOZ_WIDGET_GONK
+ nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+ GetActiveNetworkInfo(activeNetworkInfo);
+ mActiveNetworkInfo =
+ new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo);
+#endif
+
+ 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()->GetPath(path);
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ removeParamsFromPath(path);
+
+ // FTP parameters such as type=i are ignored
+ if (url) {
+ url->SetFilePath(path);
+ } else {
+ mChannel->URI()->SetPath(path);
+ }
+
+ // 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,
+ 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: %x\n", 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;
+}
+
+class nsFtpAsyncAlert : public Runnable
+{
+public:
+ nsFtpAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg)
+ : mPrompter(aPrompter)
+ , mResponseMsg(aResponseMsg)
+ {
+ MOZ_COUNT_CTOR(nsFtpAsyncAlert);
+ }
+protected:
+ virtual ~nsFtpAsyncAlert()
+ {
+ MOZ_COUNT_DTOR(nsFtpAsyncAlert);
+ }
+public:
+ NS_IMETHOD Run() override
+ {
+ if (mPrompter) {
+ mPrompter->Alert(nullptr, mResponseMsg.get());
+ }
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIPrompt> mPrompter;
+ nsString mResponseMsg;
+};
+
+
+nsresult
+nsFtpState::StopProcessing()
+{
+ // Only do this function once.
+ if (!mKeepRunning)
+ return NS_OK;
+ mKeepRunning = false;
+
+ LOG_INFO(("FTP:(%x) nsFtpState stopping", this));
+
+ if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) {
+ // check to see if the control status is bad.
+ // web shell wont throw an alert. we better:
+
+ // XXX(darin): this code should not be dictating UI like this!
+ nsCOMPtr<nsIPrompt> prompter;
+ mChannel->GetCallback(prompter);
+ if (prompter) {
+ nsCOMPtr<nsIRunnable> alertEvent;
+ if (mUseUTF8) {
+ alertEvent = new nsFtpAsyncAlert(prompter,
+ NS_ConvertUTF8toUTF16(mResponseMsg));
+ } else {
+ alertEvent = new nsFtpAsyncAlert(prompter,
+ NS_ConvertASCIItoUTF16(mResponseMsg));
+ }
+ NS_DispatchToMainThread(alertEvent);
+ }
+ 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 nsCSubstring& command)
+{
+ NS_ASSERTION(mControlConnection, "null control connection");
+
+ // we don't want to log the password:
+ nsAutoCString logcmd(command);
+ if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS ")))
+ logcmd = "PASS xxxxx";
+
+ LOG(("FTP:(%x) 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:(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok));
+ LOG(("FTP:(%x) 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:(%x) 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:(%x) 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:(%x) ConvertDirspecToVMS to: \"%s\"\n", this, dirSpec.get()));
+}
+
+// Convert an absolute VMS style dirspec to UNIX format
+void
+nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec)
+{
+ LOG(("FTP:(%x) 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:(%x) 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:
+ 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, nsISupports *context)
+{
+ mStorReplyReceived = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context,
+ 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);
+ if (NS_SUCCEEDED(rv)) {
+ CountRecvBytes(*result);
+ }
+ return rv;
+ }
+
+ return nsBaseContentStream::ReadSegments(writer, closure, count, result);
+}
+
+nsresult
+nsFtpState::SaveNetworkStats(bool enforce)
+{
+#ifdef MOZ_WIDGET_GONK
+ // Obtain app id
+ uint32_t appId;
+ bool isInBrowser;
+ NS_GetAppInfo(mChannel, &appId, &isInBrowser);
+
+ // Check if active network and appid are valid.
+ if (!mActiveNetworkInfo || appId == NECKO_NO_APP_ID) {
+ return NS_OK;
+ }
+
+ if (mCountRecv <= 0) {
+ // There is no traffic, no need to save.
+ return NS_OK;
+ }
+
+ // If |enforce| is false, the traffic amount is saved
+ // only when the total amount exceeds the predefined
+ // threshold.
+ if (!enforce && mCountRecv < NETWORK_STATS_THRESHOLD) {
+ return NS_OK;
+ }
+
+ // Create the event to save the network statistics.
+ // the event is then dispathed to the main thread.
+ RefPtr<Runnable> event =
+ new SaveNetworkStatsEvent(appId, isInBrowser, mActiveNetworkInfo,
+ mCountRecv, 0, false);
+ NS_DispatchToMainThread(event);
+
+ // Reset the counters after saving.
+ mCountRecv = 0;
+
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsFtpState::CloseWithStatus(nsresult status)
+{
+ LOG(("FTP:(%p) close [%x]\n", this, status));
+
+ // Shutdown the control connection processing if we are being closed with an
+ // error. Note: This method may be called several times.
+ if (!IsClosed() && status != NS_BASE_STREAM_CLOSED && NS_FAILED(status)) {
+ if (NS_SUCCEEDED(mInternalError))
+ mInternalError = status;
+ StopProcessing();
+ }
+
+ if (mUploadRequest) {
+ mUploadRequest->Cancel(NS_ERROR_ABORT);
+ mUploadRequest = nullptr;
+ }
+
+ if (mDataTransport) {
+ // Save the network stats before data transport is closing.
+ SaveNetworkStats(true);
+
+ // Shutdown the data transport.
+ mDataTransport->Close(NS_ERROR_ABORT);
+ mDataTransport = nullptr;
+ }
+
+ 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->GetLoadInfo(getter_AddRefs(loadInfo));
+
+ return pph->NewProxiedChannel2(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..dd48da5626
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpConnectionThread.h
@@ -0,0 +1,231 @@
+/* -*- 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 __nsFtpConnectionThread__h_
+#define __nsFtpConnectionThread__h_
+
+#include "nsBaseContentStream.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsAutoPtr.h"
+#include "nsITransport.h"
+#include "mozilla/net/DNS.h"
+#include "nsFtpControlConnection.h"
+#include "nsIProtocolProxyCallback.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsProxyRelease.h"
+#endif
+
+// 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 nsCSubstring& 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
+
+ // ****** 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
+ 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;
+
+// These members are used for network per-app metering (bug 855948)
+// Currently, they are only available on gonk.
+ uint64_t mCountRecv;
+#ifdef MOZ_WIDGET_GONK
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+ nsresult SaveNetworkStats(bool);
+ void CountRecvBytes(uint64_t recvBytes)
+ {
+ mCountRecv += recvBytes;
+ SaveNetworkStats(false);
+ }
+};
+
+#endif //__nsFtpConnectionThread__h_
diff --git a/netwerk/protocol/ftp/nsFtpControlConnection.cpp b/netwerk/protocol/ftp/nsFtpControlConnection.cpp
new file mode 100644
index 0000000000..ab55cd4f6a
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpControlConnection.cpp
@@ -0,0 +1,189 @@
+/* -*- 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 "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 "nsIOutputStream.h"
+#include "nsNetCID.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 nsCSubstring& host,
+ uint32_t port)
+ : mServerType(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(nullptr, 0, 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, NS_GetCurrentThread());
+
+ // 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, NS_GetCurrentThread());
+}
+
+nsresult
+nsFtpControlConnection::Disconnect(nsresult status)
+{
+ if (!mSocket)
+ return NS_OK; // already disconnected
+
+ LOG_INFO(("FTP:(%p) CC disconnecting (%x)", this, 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 nsCSubstring& 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..5301bb9cee
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpControlConnection.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et ts=4 sts=4 sw=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/. */
+
+#ifndef nsFtpControlConnection_h___
+#define nsFtpControlConnection_h___
+
+#include "nsCOMPtr.h"
+
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsAutoPtr.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 nsCSubstring& host, uint32_t port);
+
+ nsresult Connect(nsIProxyInfo* proxyInfo, nsITransportEventSink* eventSink);
+ nsresult Disconnect(nsresult status);
+ nsresult Write(const nsCSubstring& 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..46b12767a3
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp
@@ -0,0 +1,426 @@
+/* -*- 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 Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+using namespace mozilla;
+using namespace mozilla::net;
+
+#include "nsFtpProtocolHandler.h"
+#include "nsFTPChannel.h"
+#include "nsIStandardURL.h"
+#include "mozilla/Logging.h"
+#include "nsIPrefService.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 QOS_DATA_PREF "network.ftp.data.qos"
+#define QOS_CONTROL_PREF "network.ftp.control.qos"
+
+nsFtpProtocolHandler *gFtpHandler = nullptr;
+
+//-----------------------------------------------------------------------------
+
+nsFtpProtocolHandler::nsFtpProtocolHandler()
+ : mIdleTimeout(-1)
+ , mSessionId(0)
+ , mControlQoSBits(0x00)
+ , mDataQoSBits(0x00)
+{
+ LOG(("FTP:creating handler @%x\n", this));
+
+ gFtpHandler = this;
+}
+
+nsFtpProtocolHandler::~nsFtpProtocolHandler()
+{
+ LOG(("FTP:destroying handler @%x\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;
+
+ 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::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsAutoCString spec(aSpec);
+ spec.Trim(" \t\n\r"); // Match NS_IsAsciiWhitespace instead of HTML5
+
+ char *fwdPtr = spec.BeginWriting();
+
+ // now unescape it... %xx reduced inline to resulting character
+
+ int32_t len = NS_UnescapeURL(fwdPtr);
+
+ // NS_UnescapeURL() modified spec's buffer, truncate to ensure
+ // spec knows its new length.
+ spec.Truncate(len);
+
+ // return an error if we find a NUL, CR, or LF in the path
+ if (spec.FindCharInSet(CRLF) >= 0 || spec.FindChar('\0') >= 0)
+ return NS_ERROR_MALFORMED_URI;
+
+ nsresult rv;
+ nsCOMPtr<nsIStandardURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, 21, aSpec, aCharset, aBaseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(url, result);
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewChannel2(nsIURI* url,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ return NewProxiedChannel2(url, nullptr, 0, nullptr, aLoadInfo, result);
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewChannel(nsIURI* url, nsIChannel* *result)
+{
+ return NewChannel2(url, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewProxiedChannel2(nsIURI* uri, nsIProxyInfo* proxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel* *result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ RefPtr<nsBaseChannel> channel;
+ if (IsNeckoChild())
+ channel = new FTPChannelChild(uri);
+ else
+ channel = new nsFtpChannel(uri, proxyInfo);
+
+ nsresult rv = channel->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ channel.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* proxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ nsIChannel* *result)
+{
+ return NewProxiedChannel2(uri, proxyInfo, proxyResolveFlags,
+ proxyURI, nullptr /*loadinfo*/,
+ result);
+}
+
+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()));
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ timerStruct* ts = new timerStruct();
+ if (!ts)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = timer->InitWithFuncCallback(nsFtpProtocolHandler::Timeout,
+ ts,
+ mIdleTimeout*1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ if (NS_FAILED(rv)) {
+ delete ts;
+ return rv;
+ }
+
+ ts->key = ToNewCString(spec);
+ 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;
+
+ 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 {
+ NS_NOTREACHED("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..0bc3b0dfd4
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.h
@@ -0,0 +1,87 @@
+/* -*- 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 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;
+
+ // 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..78c0dad584
--- /dev/null
+++ b/netwerk/protocol/ftp/test/frametest/index.html
@@ -0,0 +1,13 @@
+<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..afe3afcf2c
--- /dev/null
+++ b/netwerk/protocol/ftp/test/frametest/menu.html
@@ -0,0 +1,373 @@
+<html>
+
+<script language="javascript">
+
+<!--
+
+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/http/ASpdySession.cpp b/netwerk/protocol/http/ASpdySession.cpp
new file mode 100644
index 0000000000..6bd54c7c0f
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -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/. */
+
+// 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()
+{
+}
+
+ASpdySession::~ASpdySession() = default;
+
+ASpdySession *
+ASpdySession::NewSpdySession(uint32_t version,
+ nsISocketTransport *aTransport)
+{
+ // This is a necko only interface, so we can enforce version
+ // requests as a precondition
+ MOZ_ASSERT(version == HTTP_VERSION_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.
+
+ Telemetry::Accumulate(Telemetry::SPDY_VERSION2, version);
+
+ return new Http2Session(aTransport, version);
+}
+
+SpdyInformation::SpdyInformation()
+{
+ // highest index of enabled protocols is the
+ // most preferred for ALPN negotiaton
+ Version[0] = HTTP_VERSION_2;
+ VersionString[0] = NS_LITERAL_CSTRING("h2");
+ 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()
+{
+}
+
+SpdyPushCache::~SpdyPushCache()
+{
+ mHashHttp2.Clear();
+}
+
+bool
+SpdyPushCache::RegisterPushedStreamHttp2(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(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;
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h
new file mode 100644
index 0000000000..e116d423b9
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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();
+ virtual ~ASpdySession();
+
+ virtual bool AddStream(nsAHttpTransaction *, int32_t,
+ 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;
+
+ static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *);
+
+ // 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 override 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.
+ 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);
+ }
+};
+
+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() {}
+
+ 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.
+ 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;
+
+ uint8_t 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..b24514685e
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
@@ -0,0 +1,151 @@
+#include "mozilla/net/AltDataOutputStreamChild.h"
+#include "mozilla/Unused.h"
+#include "nsIInputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(AltDataOutputStreamChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) AltDataOutputStreamChild::Release()
+{
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "AltDataOutputStreamChild");
+
+ if (mRefCnt == 1 && mIPCOpen) {
+ // Send_delete calls NeckoChild::PAltDataOutputStreamChild, which will release
+ // again to refcount == 0
+ PAltDataOutputStreamChild::Send__delete__(this);
+ return 0;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(AltDataOutputStreamChild)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+AltDataOutputStreamChild::AltDataOutputStreamChild()
+ : mIPCOpen(false)
+ , mError(NS_OK)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+AltDataOutputStreamChild::~AltDataOutputStreamChild()
+{
+}
+
+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;
+ Release();
+}
+
+bool
+AltDataOutputStreamChild::WriteDataInChunks(const nsCString& 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()
+{
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ Unused << SendClose();
+ return 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(nsCString(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;
+}
+
+bool
+AltDataOutputStreamChild::RecvError(const nsresult& err)
+{
+ mError = err;
+ return true;
+}
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltDataOutputStreamChild.h b/netwerk/protocol/http/AltDataOutputStreamChild.h
new file mode 100644
index 0000000000..76b4b82ba1
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AltDataOutputStreamChild_h
+#define mozilla_net_AltDataOutputStreamChild_h
+
+#include "mozilla/net/PAltDataOutputStreamChild.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+class AltDataOutputStreamChild
+ : public PAltDataOutputStreamChild
+ , public nsIOutputStream
+{
+public:
+ NS_DECL_ISUPPORTS
+ 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 bool RecvError(const nsresult& err) override;
+
+private:
+ virtual ~AltDataOutputStreamChild();
+ // Sends data to the parent process in 256k chunks.
+ bool WriteDataInChunks(const nsCString& data);
+
+ 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;
+};
+
+} // 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..1181209dc7
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+AltDataOutputStreamParent::~AltDataOutputStreamParent()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+bool
+AltDataOutputStreamParent::RecvWriteData(const nsCString& data)
+{
+ if (NS_FAILED(mStatus)) {
+ Unused << SendError(mStatus);
+ return true;
+ }
+ nsresult rv;
+ uint32_t n;
+ if (mOutputStream) {
+ rv = mOutputStream->Write(data.BeginReading(), data.Length(), &n);
+ MOZ_ASSERT(n == data.Length());
+ if (NS_FAILED(rv)) {
+ Unused << SendError(rv);
+ }
+ }
+ return true;
+}
+
+bool
+AltDataOutputStreamParent::RecvClose()
+{
+ if (NS_FAILED(mStatus)) {
+ Unused << SendError(mStatus);
+ return true;
+ }
+ nsresult rv;
+ if (mOutputStream) {
+ rv = mOutputStream->Close();
+ if (NS_FAILED(rv)) {
+ Unused << SendError(rv);
+ }
+ mOutputStream = nullptr;
+ }
+ return true;
+}
+
+void
+AltDataOutputStreamParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltDataOutputStreamParent.h b/netwerk/protocol/http/AltDataOutputStreamParent.h
new file mode 100644
index 0000000000..208339efb9
--- /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.
+ virtual bool RecvWriteData(const nsCString& data) override;
+ // Called when AltDataOutputStreamChild::Close() is
+ // Closes and nulls the output stream.
+ virtual bool RecvClose() override;
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ // Sets an error that will be reported to the content process.
+ void SetError(nsresult status) { mStatus = status; }
+
+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;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamParent_h
diff --git a/netwerk/protocol/http/AlternateServices.cpp b/netwerk/protocol/http/AlternateServices.cpp
new file mode 100644
index 0000000000..b3e6babe39
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -0,0 +1,1075 @@
+/* -*- 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 "nsEscape.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsThreadUtils.h"
+#include "nsHttpTransaction.h"
+#include "NullHttpTransaction.h"
+#include "nsISSLStatusProvider.h"
+#include "nsISSLStatus.h"
+#include "nsISSLSocketControl.h"
+#include "nsIWellKnownOpportunisticUtils.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.Equals(NS_LITERAL_CSTRING("https"));
+
+ if (!outIsHTTPS && !originScheme.Equals(NS_LITERAL_CSTRING("http"))) {
+ MOZ_ASSERT(false, "unexpected scheme");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+void
+AltSvcMapping::ProcessHeader(const nsCString &buf, const nsCString &originScheme,
+ const nsCString &originHost, int32_t originPort,
+ const nsACString &username, bool privateBrowsing,
+ nsIInterfaceRequestor *callbacks, nsProxyInfo *proxyInfo,
+ uint32_t caps, const NeckoOriginAttributes &originAttributes)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("AltSvcMapping::ProcessHeader: %s\n", buf.get()));
+ if (!callbacks) {
+ return;
+ }
+
+ if (proxyInfo && !proxyInfo->IsDirect()) {
+ 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);
+
+ 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;
+
+ 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.Equals(NS_LITERAL_CSTRING("clear"))) {
+ clearEntry = true;
+ break;
+ }
+
+ // h2=[hostname]:443
+ 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.Equals(NS_LITERAL_CSTRING("ma"))) {
+ maxage = atoi(PromiseFlatCString(currentValue).get());
+ break;
+ } else {
+ LOG(("Alt Svc ignoring parameter %s", currentName.BeginReading()));
+ }
+ }
+
+ if (clearEntry) {
+ LOG(("Alt Svc clearing mapping for %s:%d", originHost.get(), originPort));
+ gHttpHandler->ConnMgr()->ClearHostMapping(originHost, originPort);
+ continue;
+ }
+
+ // unescape modifies a c string in place, so afterwards
+ // update nsCString length
+ nsUnescape(npnToken.BeginWriting());
+ npnToken.SetLength(strlen(npnToken.BeginReading()));
+
+ uint32_t spdyIndex;
+ SpdyInformation *spdyInfo = gHttpHandler->SpdyInfo();
+ if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
+ spdyInfo->ProtocolEnabled(spdyIndex))) {
+ LOG(("Alt Svc unknown protocol %s, ignoring", npnToken.get()));
+ continue;
+ }
+
+ RefPtr<AltSvcMapping> mapping = new AltSvcMapping(gHttpHandler->ConnMgr()->GetStoragePtr(),
+ gHttpHandler->ConnMgr()->StorageEpoch(),
+ originScheme,
+ originHost, originPort,
+ username, privateBrowsing,
+ NowInSeconds() + maxage,
+ hostname, portno, npnToken);
+ 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->ConnMgr()->ClearHostMapping(originHost, originPort);
+ } else {
+ gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps,
+ originAttributes);
+ }
+ }
+}
+
+AltSvcMapping::AltSvcMapping(DataStorage *storage, int32_t epoch,
+ const nsACString &originScheme,
+ const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &username,
+ bool privateBrowsing,
+ uint32_t expiresAt,
+ const nsACString &alternateHost,
+ int32_t alternatePort,
+ const nsACString &npnToken)
+ : mStorage(storage)
+ , mStorageEpoch(epoch)
+ , mAlternateHost(alternateHost)
+ , mAlternatePort(alternatePort)
+ , mOriginHost(originHost)
+ , mOriginPort(originPort)
+ , mUsername(username)
+ , mPrivate(privateBrowsing)
+ , mExpiresAt(expiresAt)
+ , mValidated(false)
+ , mMixedScheme(false)
+ , mNPNToken(npnToken)
+{
+ 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())) {
+ LOG(("Alt Svc is also origin Svc - ignoring\n"));
+ mExpiresAt = 0; // invalid
+ }
+
+ if (mExpiresAt) {
+ MakeHashKey(mHashKey, originScheme, mOriginHost, mOriginPort, mPrivate);
+ }
+}
+
+void
+AltSvcMapping::MakeHashKey(nsCString &outKey,
+ const nsACString &originScheme,
+ const nsACString &originHost,
+ int32_t originPort,
+ bool privateBrowsing)
+{
+ outKey.Truncate();
+
+ if (originPort == -1) {
+ bool isHttps = originScheme.Equals("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' : '.');
+}
+
+int32_t
+AltSvcMapping::TTL()
+{
+ return mExpiresAt - NowInSeconds();
+}
+
+void
+AltSvcMapping::SyncString(nsCString str)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mStorage->Put(HashKey(), str,
+ mPrivate ? DataStorage_Private : DataStorage_Persistent);
+}
+
+void
+AltSvcMapping::Sync()
+{
+ if (!mStorage) {
+ return;
+ }
+ nsCString value;
+ Serialize(value);
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> r;
+ r = NewRunnableMethod<nsCString>(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 NeckoOriginAttributes &originAttributes)
+{
+ RefPtr<nsHttpConnectionInfo> ci =
+ new nsHttpConnectionInfo(mOriginHost, mOriginPort, mNPNToken,
+ mUsername, pi, originAttributes,
+ mAlternateHost, mAlternatePort);
+
+ // http:// without the mixed-scheme attribute needs to be segmented in the
+ // connection manager connection information hash with this attribute
+ if (!mHttps && !mMixedScheme) {
+ ci->SetInsecureScheme(true);
+ }
+ ci->SetPrivate(mPrivate);
+ ci.forget(outCI);
+}
+
+void
+AltSvcMapping::Serialize(nsCString &out)
+{
+ out = mHttps ? NS_LITERAL_CSTRING("https:") : NS_LITERAL_CSTRING("http:");
+ 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(':');
+}
+
+AltSvcMapping::AltSvcMapping(DataStorage *storage, int32_t epoch, const nsCString &str)
+ : mStorage(storage)
+ , mStorageEpoch(epoch)
+{
+ mValidated = false;
+ nsresult code;
+
+ // 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(':', start); if (idx < 0) break;
+ int32_t start = 0;
+ int32_t idx;
+ idx = str.FindChar(':', start); if (idx < 0) break;
+ mHttps = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("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).Equals(NS_LITERAL_CSTRING("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).Equals(NS_LITERAL_CSTRING("y"));
+ _NS_NEXT_TOKEN;
+ mStorageEpoch = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mMixedScheme = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
+ #undef _NS_NEXT_TOKEN
+
+ MakeHashKey(mHashKey, mHttps ? NS_LITERAL_CSTRING("https") : NS_LITERAL_CSTRING("http"),
+ mOriginHost, mOriginPort, mPrivate);
+ } while (false);
+}
+
+// This is the asynchronous null transaction used to validate
+// an alt-svc advertisement only for https://
+class AltSvcTransaction final : public NullHttpTransaction
+{
+public:
+ AltSvcTransaction(AltSvcMapping *map,
+ nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps)
+ : NullHttpTransaction(ci, callbacks, caps & ~NS_HTTP_ALLOW_KEEPALIVE)
+ , mMapping(map)
+ , mRunning(true)
+ , mTriedToValidate(false)
+ , mTriedToWrite(false)
+ {
+ LOG(("AltSvcTransaction ctor %p map %p [%s -> %s]",
+ this, map, map->OriginHost().get(), map->AlternateHost().get()));
+ MOZ_ASSERT(mMapping);
+ MOZ_ASSERT(mMapping->HTTPS());
+ }
+
+ ~AltSvcTransaction() override
+ {
+ LOG(("AltSvcTransaction dtor %p map %p running %d",
+ this, mMapping.get(), mRunning));
+
+ if (mRunning) {
+ MaybeValidate(NS_OK);
+ }
+ if (!mMapping->Validated()) {
+ // try again later
+ mMapping->SetExpiresAt(NowInSeconds() + 2);
+ }
+ LOG(("AltSvcTransaction dtor %p map %p validated %d [%s]",
+ this, mMapping.get(), mMapping->Validated(),
+ mMapping->HashKey().get()));
+ }
+
+private:
+ // check on alternate route.
+ // also evaluate 'reasonable assurances' for opportunistic security
+ void MaybeValidate(nsresult reason)
+ {
+ MOZ_ASSERT(mMapping->HTTPS()); // http:// uses the .wk path
+
+ if (mTriedToValidate) {
+ return;
+ }
+ mTriedToValidate = true;
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p reason=%x running=%d conn=%p write=%d",
+ this, 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;
+ }
+
+ // insist on >= http/2
+ uint32_t version = mConnection->Version();
+ LOG(("AltSvcTransaction::MaybeValidate() %p version %d\n", this, version));
+ if (version != HTTP_VERSION_2) {
+ LOG(("AltSvcTransaction::MaybeValidate %p Failed due to protocol version", this));
+ return;
+ }
+
+ 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;
+ }
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p "
+ "validating alternate service with successful auth check", this));
+ mMapping->SetValidated(true);
+ }
+
+public:
+ void Close(nsresult reason) override
+ {
+ LOG(("AltSvcTransaction::Close() %p reason=%x running %d",
+ this, reason, mRunning));
+
+ MaybeValidate(reason);
+ if (!mMapping->Validated() && mConnection) {
+ mConnection->DontReuse();
+ }
+ NullHttpTransaction::Close(reason);
+ }
+
+ nsresult ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead) override
+ {
+ LOG(("AltSvcTransaction::ReadSegements() %p\n"));
+ mTriedToWrite = true;
+ return NullHttpTransaction::ReadSegments(reader, count, countRead);
+ }
+
+private:
+ RefPtr<AltSvcMapping> mMapping;
+ uint32_t mRunning : 1;
+ uint32_t mTriedToValidate : 1;
+ uint32_t mTriedToWrite : 1;
+};
+
+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_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ loadInfo->SetOriginAttributes(mCI->GetOriginAttributes());
+
+ 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\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.Equals(NS_LITERAL_CSTRING("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, mAlternatePort);
+ if (NS_SUCCEEDED(rv)) {
+ bool validWK = false;
+ bool mixedScheme = false;
+ int32_t lifetime = 0;
+ uu->GetValid(&validWK);
+ uu->GetLifetime(&lifetime);
+ uu->GetMixed(&mixedScheme);
+ if (!validWK) {
+ LOG(("WellKnownChecker::Done %p json parser declares invalid\n%s\n", this, mTransactionAlternate->mWKResponse.get()));
+ accepted = false;
+ }
+ if (accepted && (lifetime > 0)) {
+ if (mMapping->TTL() > lifetime) {
+ LOG(("WellKnownChecker::Done %p atl-svc lifetime reduced by .wk\n", this));
+ mMapping->SetExpiresAt(NowInSeconds() + lifetime);
+ } else {
+ LOG(("WellKnownChecker::Done %p .wk lifetime exceeded alt-svc ma so ignored\n", this));
+ }
+ }
+ if (accepted && mixedScheme) {
+ mMapping->SetMixedScheme(true);
+ LOG(("WellKnownChecker::Done %p atl-svc .wk allows mixed scheme\n", this));
+ }
+ } 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)
+ {
+ nsID channelId;
+ nsLoadFlags flags;
+ if (NS_FAILED(gHttpHandler->NewChannelId(&channelId)) ||
+ NS_FAILED(chan->Init(uri, caps, nullptr, 0, nullptr, channelId)) ||
+ 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->AsyncOpen2(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)
+ , mAuthOK(false)
+ , mVersionOK(false)
+ , mStatusOK(false)
+{
+ LOG(("TransactionObserver ctor %p channel %p checker %p\n", this, channel, checker));
+ mChannelRef = do_QueryInterface((nsIHttpChannel *)channel);
+}
+
+void
+TransactionObserver::Complete(nsHttpTransaction *aTrans, nsresult reason)
+{
+ // socket thread
+ MOZ_ASSERT(!NS_IsMainThread());
+ if (mRanOnce) {
+ return;
+ }
+ mRanOnce = true;
+
+ RefPtr<nsAHttpConnection> conn = aTrans->GetConnectionReference();
+ LOG(("TransactionObserver::Complete %p aTrans %p reason %x conn %p\n",
+ this, aTrans, reason, conn.get()));
+ if (!conn) {
+ return;
+ }
+ uint32_t version = conn->Version();
+ mVersionOK = (((reason == NS_BASE_STREAM_CLOSED) || (reason == NS_OK)) &&
+ conn->Version() == HTTP_VERSION_2);
+
+ nsCOMPtr<nsISupports> secInfo;
+ conn->GetSecurityInfo(getter_AddRefs(secInfo));
+ nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
+ LOG(("TransactionObserver::Complete version %u socketControl %p\n",
+ version, socketControl.get()));
+ if (!socketControl) {
+ return;
+ }
+
+ mAuthOK = !socketControl->GetFailedVerification();
+ LOG(("TransactionObserve::Complete %p trans %p authOK %d versionOK %d\n",
+ this, aTrans, mAuthOK, mVersionOK));
+}
+
+#define MAX_WK 32768
+
+NS_IMETHODIMP
+TransactionObserver::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // only consider the first 32KB.. because really.
+ mWKResponse.SetCapacity(MAX_WK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
+ nsIInputStream *aStream, uint64_t aOffset, uint32_t aCount)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ uint64_t newLen = aCount + mWKResponse.Length();
+ if (newLen < MAX_WK) {
+ char *startByte = reinterpret_cast<char *>(mWKResponse.BeginWriting()) + mWKResponse.Length();
+ uint32_t amtRead;
+ if (NS_SUCCEEDED(aStream->Read(startByte, aCount, &amtRead))) {
+ MOZ_ASSERT(mWKResponse.Length() + amtRead < MAX_WK);
+ mWKResponse.SetLength(mWKResponse.Length() + amtRead);
+ 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, nsISupports *aContext, nsresult code)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("TransactionObserver onStopRequest %p code %x\n", this, 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;
+}
+
+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;
+ }
+ 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->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();
+}
+
+void
+AltSvcCache::UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
+ nsIInterfaceRequestor *aCallbacks,
+ uint32_t caps,
+ const NeckoOriginAttributes &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()));
+ }
+ }
+ return;
+ }
+
+ // new alternate. remove old entry and start new validation
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p overwrites %p\n",
+ this, map, existing.get()));
+ existing = nullptr;
+ mStorage->Remove(map->HashKey(),
+ map->Private() ? DataStorage_Private : DataStorage_Persistent);
+ }
+
+ 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
+ MOZ_ASSERT(!map->Validated());
+ map->Sync();
+
+ 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<AltSvcTransaction> nullTransaction =
+ new AltSvcTransaction(map, ci, aCallbacks, caps);
+ gHttpHandler->ConnMgr()->SpeculativeConnect(ci, callbacks, caps, nullTransaction);
+ } else {
+ // for http:// resources we fetch .well-known too
+ nsAutoCString origin (NS_LITERAL_CSTRING("http://") + map->OriginHost());
+ if (map->OriginPort() != NS_HTTP_DEFAULT_PORT) {
+ origin.Append(':');
+ origin.AppendInt(map->OriginPort());
+ }
+
+ nsCOMPtr<nsIURI> wellKnown;
+ nsAutoCString uri(origin);
+ uri.Append(NS_LITERAL_CSTRING("/.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 isHTTPS;
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mStorage) {
+ // 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(NS_LITERAL_STRING("AlternateServices.txt"));
+ if (mStorage) {
+ bool storageWillPersist = false;
+ if (NS_FAILED(mStorage->Init(storageWillPersist))) {
+ mStorage = nullptr;
+ }
+ }
+ if (!mStorage) {
+ LOG(("AltSvcCache::GetAltServiceMapping WARN NO STORAGE\n"));
+ }
+ mStorageEpoch = NowInSeconds();
+ }
+
+ if (NS_FAILED(SchemeIsHTTPS(scheme, isHTTPS))) {
+ return nullptr;
+ }
+ if (!gHttpHandler->AllowAltSvc()) {
+ return nullptr;
+ }
+ if (!gHttpHandler->AllowAltSvcOE() && !isHTTPS) {
+ return nullptr;
+ }
+
+ nsAutoCString key;
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing);
+ 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()) {
+ existing = nullptr;
+ }
+ return existing.forget();
+}
+
+class ProxyClearHostMapping : public Runnable {
+public:
+ explicit ProxyClearHostMapping(const nsACString &host, int32_t port)
+ : mHost(host)
+ , mPort(port)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ gHttpHandler->ConnMgr()->ClearHostMapping(mHost, mPort);
+ return NS_OK;
+ }
+private:
+ nsCString mHost;
+ int32_t mPort;
+};
+
+void
+AltSvcCache::ClearHostMapping(const nsACString &host, int32_t port)
+{
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> event = new ProxyClearHostMapping(host, port);
+ if (event) {
+ NS_DispatchToMainThread(event);
+ }
+ return;
+ }
+ nsAutoCString key;
+ AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, true);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, true);
+ if (existing) {
+ existing->SetExpired();
+ }
+
+ AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, true);
+ existing = LookupMapping(key, true);
+ if (existing) {
+ existing->SetExpired();
+ }
+
+ AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, false);
+ existing = LookupMapping(key, false);
+ if (existing) {
+ existing->SetExpired();
+ }
+
+ AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, false);
+ existing = LookupMapping(key, false);
+ if (existing) {
+ existing->SetExpired();
+ }
+}
+
+void
+AltSvcCache::ClearHostMapping(nsHttpConnectionInfo *ci)
+{
+ if (!ci->GetOrigin().IsEmpty()) {
+ ClearHostMapping(ci->GetOrigin(), ci->OriginPort());
+ }
+}
+
+void
+AltSvcCache::ClearAltServiceMappings()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mStorage) {
+ mStorage->Clear();
+ }
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetInterface(const nsIID &iid, void **result)
+{
+ if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+ return NS_OK;
+ }
+ return mCallbacks->GetInterface(iid, result);
+}
+
+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..13403cd360
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -0,0 +1,191 @@
+/* -*- 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"
+
+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,
+ bool privateBrowsing,
+ uint32_t expiresAt,
+ const nsACString &alternateHost,
+ int32_t alternatePort,
+ const nsACString &npnToken);
+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, bool privateBrowsing,
+ nsIInterfaceRequestor *callbacks, nsProxyInfo *proxyInfo,
+ uint32_t caps, const NeckoOriginAttributes &originAttributes);
+
+ 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 NeckoOriginAttributes &originAttributes);
+
+ int32_t TTL();
+ int32_t StorageEpoch() { return mStorageEpoch; }
+ bool Private() { return mPrivate; }
+
+ void SetValidated(bool val);
+ void SetMixedScheme(bool val);
+ void SetExpiresAt(int32_t val);
+ void SetExpired();
+ void Sync();
+
+ static void MakeHashKey(nsCString &outKey,
+ const nsACString &originScheme,
+ const nsACString &originHost,
+ int32_t originPort,
+ bool privateBrowsing);
+
+private:
+ virtual ~AltSvcMapping() {};
+ void SyncString(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;
+ MOZ_INIT_OUTSIDE_CTOR bool mPrivate;
+
+ 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;
+};
+
+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() {}
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+class TransactionObserver : public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ TransactionObserver(nsHttpChannel *channel, WellKnownChecker *checker);
+ void Complete(nsHttpTransaction *, nsresult);
+private:
+ friend class WellKnownChecker;
+ virtual ~TransactionObserver() {}
+
+ nsCOMPtr<nsISupports> mChannelRef;
+ nsHttpChannel *mChannel;
+ WellKnownChecker *mChecker;
+ nsCString mWKResponse;
+
+ bool mRanOnce;
+ bool mAuthOK; // confirmed no TLS failure
+ bool mVersionOK; // connection h2
+ bool mStatusOK; // HTTP Status 200
+};
+
+class AltSvcCache
+{
+public:
+ AltSvcCache() : mStorageEpoch(0) {}
+ virtual ~AltSvcCache () {};
+ void UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
+ nsIInterfaceRequestor *, uint32_t caps,
+ const NeckoOriginAttributes &originAttributes); // main thread
+ already_AddRefed<AltSvcMapping> GetAltServiceMapping(const nsACString &scheme,
+ const nsACString &host,
+ int32_t port, bool pb);
+ void ClearAltServiceMappings();
+ void ClearHostMapping(const nsACString &host, int32_t port);
+ void ClearHostMapping(nsHttpConnectionInfo *ci);
+ DataStorage *GetStoragePtr() { return mStorage.get(); }
+ int32_t StorageEpoch() { return mStorageEpoch; }
+
+private:
+ already_AddRefed<AltSvcMapping> LookupMapping(const nsCString &key, bool privateBrowsing);
+ RefPtr<DataStorage> mStorage;
+ int32_t mStorageEpoch;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // include guard
diff --git a/netwerk/protocol/http/CacheControlParser.cpp b/netwerk/protocol/http/CacheControlParser.cpp
new file mode 100644
index 0000000000..68c6377063
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.cpp
@@ -0,0 +1,123 @@
+/* -*- 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)
+ , mNoCache(false)
+ , mNoStore(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 {
+ 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::NoCache()
+{
+ return mNoCache;
+}
+
+bool CacheControlParser::NoStore()
+{
+ return mNoStore;
+}
+
+} // net
+} // mozilla
diff --git a/netwerk/protocol/http/CacheControlParser.h b/netwerk/protocol/http/CacheControlParser.h
new file mode 100644
index 0000000000..5f1b44213a
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.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 CacheControlParser_h__
+#define CacheControlParser_h__
+
+#include "mozilla/Tokenizer.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheControlParser final : Tokenizer
+{
+public:
+ explicit CacheControlParser(nsACString const &header);
+
+ bool MaxAge(uint32_t *seconds);
+ bool MaxStale(uint32_t *seconds);
+ bool MinFresh(uint32_t *seconds);
+ bool NoCache();
+ bool NoStore();
+
+private:
+ void Directive();
+ void IgnoreDirective();
+ 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 mNoCache;
+ bool mNoStore;
+};
+
+} // net
+} // mozilla
+
+#endif
diff --git a/netwerk/protocol/http/ConnectionDiagnostics.cpp b/netwerk/protocol/http/ConnectionDiagnostics.cpp
new file mode 100644
index 0000000000..9ddc8e362b
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -0,0 +1,211 @@
+/* -*- 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 "Http2Session.h"
+#include "nsHttpHandler.h"
+#include "nsIConsoleService.h"
+#include "nsHttpRequestHead.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla {
+namespace net {
+
+void
+nsHttpConnectionMgr::PrintDiagnostics()
+{
+ PostEvent(&nsHttpConnectionMgr::OnMsgPrintDiagnostics, 0, nullptr);
+}
+
+void
+nsHttpConnectionMgr::OnMsgPrintDiagnostics(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ mLogData.AppendPrintf(" ent host = %s hashkey = %s\n",
+ ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get());
+ mLogData.AppendPrintf(" AtActiveConnectionLimit = %d\n",
+ AtActiveConnectionLimit(ent, NS_HTTP_ALLOW_KEEPALIVE));
+ mLogData.AppendPrintf(" RestrictConnections = %d\n",
+ RestrictConnections(ent));
+ mLogData.AppendPrintf(" Pending Q Length = %u\n",
+ ent->mPendingQ.Length());
+ mLogData.AppendPrintf(" Active Conns Length = %u\n",
+ ent->mActiveConns.Length());
+ mLogData.AppendPrintf(" Idle Conns Length = %u\n",
+ ent->mIdleConns.Length());
+ mLogData.AppendPrintf(" Half Opens Length = %u\n",
+ ent->mHalfOpens.Length());
+ mLogData.AppendPrintf(" Coalescing Keys Length = %u\n",
+ ent->mCoalescingKeys.Length());
+ mLogData.AppendPrintf(" Spdy using = %d, preferred = %d\n",
+ ent->mUsingSpdy, ent->mInPreferredHash);
+ mLogData.AppendPrintf(" pipelinestate = %d penalty = %d\n",
+ ent->mPipelineState, ent->mPipeliningPenalty);
+
+ uint32_t i;
+ for (i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
+ mLogData.AppendPrintf(" pipeline per class penalty 0x%x %d\n",
+ i, ent->mPipeliningClassPenalty[i]);
+ }
+ for (i = 0; i < ent->mActiveConns.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Active Connection #%u\n", i);
+ ent->mActiveConns[i]->PrintDiagnostics(mLogData);
+ }
+ for (i = 0; i < ent->mIdleConns.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Idle Connection #%u\n", i);
+ ent->mIdleConns[i]->PrintDiagnostics(mLogData);
+ }
+ for (i = 0; i < ent->mHalfOpens.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Half Open #%u\n", i);
+ ent->mHalfOpens[i]->PrintDiagnostics(mLogData);
+ }
+ for (i = 0; i < ent->mPendingQ.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Pending Transaction #%u\n", i);
+ ent->mPendingQ[i]->PrintDiagnostics(mLogData);
+ }
+ for (i = 0; i < ent->mCoalescingKeys.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Coalescing Key #%u %s\n",
+ i, ent->mCoalescingKeys[i].get());
+ }
+ }
+
+ consoleService->LogStringMessage(NS_ConvertUTF8toUTF16(mLogData).Data());
+ mLogData.Truncate();
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::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",
+ 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 %lld/%lld/%lld\n",
+ mMaxBytesRead, mTotalBytesRead, mTotalBytesWritten);
+
+ log.AppendPrintf(" rtt = %ums\n", PR_IntervalToMilliseconds(mRtt));
+
+ log.AppendPrintf(" idlemonitoring = %d transactionCount=%d\n",
+ mIdleMonitoring, mHttp1xTransactionCount);
+
+ log.AppendPrintf(" supports pipeline = %d classification = 0x%x\n",
+ mSupportsPipelining, mClassification);
+
+ if (mSpdySession)
+ mSpdySession->PrintDiagnostics(log);
+}
+
+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 = %d\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);
+ log.AppendPrintf(" classification = 0x%x\n", mClassification);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HSTSPrimerListener.cpp b/netwerk/protocol/http/HSTSPrimerListener.cpp
new file mode 100644
index 0000000000..8c9d28d363
--- /dev/null
+++ b/netwerk/protocol/http/HSTSPrimerListener.cpp
@@ -0,0 +1,273 @@
+/* -*- 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 "nsHttp.h"
+
+#include "HSTSPrimerListener.h"
+#include "nsIHstsPrimingCallback.h"
+#include "nsIPrincipal.h"
+#include "nsSecurityHeaderParser.h"
+#include "nsISiteSecurityService.h"
+#include "nsISocketProvider.h"
+#include "nsISSLStatus.h"
+#include "nsISSLStatusProvider.h"
+#include "nsStreamUtils.h"
+#include "nsHttpChannel.h"
+#include "LoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(HSTSPrimingListener, nsIStreamListener,
+ nsIRequestObserver, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+HSTSPrimingListener::GetInterface(const nsIID & aIID, void **aResult)
+{
+ return QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+HSTSPrimingListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ nsresult primingResult = CheckHSTSPrimingRequestStatus(aRequest);
+ nsCOMPtr<nsIHstsPrimingCallback> callback(mCallback);
+ mCallback = nullptr;
+
+ nsCOMPtr<nsITimedChannel> timingChannel =
+ do_QueryInterface(callback);
+ if (timingChannel) {
+ TimeStamp channelCreationTime;
+ nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime);
+ if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
+ PRUint32 interval =
+ (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION,
+ (NS_SUCCEEDED(primingResult)) ? NS_LITERAL_CSTRING("success")
+ : NS_LITERAL_CSTRING("failure"),
+ interval);
+ }
+ }
+
+ if (NS_FAILED(primingResult)) {
+ LOG(("HSTS Priming Failed (request was not approved)"));
+ return callback->OnHSTSPrimingFailed(primingResult, false);
+ }
+
+ LOG(("HSTS Priming Succeeded (request was approved)"));
+ return callback->OnHSTSPrimingSucceeded(false);
+}
+
+NS_IMETHODIMP
+HSTSPrimingListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+nsresult
+HSTSPrimingListener::CheckHSTSPrimingRequestStatus(nsIRequest* aRequest)
+{
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_FAILED(status)) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(httpChannel);
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(internal);
+
+ bool succeedded;
+ rv = httpChannel->GetRequestSucceeded(&succeedded);
+ if (NS_FAILED(rv) || !succeedded) {
+ // If the request did not return a 2XX response, don't process it
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ bool synthesized = false;
+ nsHttpChannel* rawHttpChannel = static_cast<nsHttpChannel*>(httpChannel.get());
+ rv = rawHttpChannel->GetResponseSynthesized(&synthesized);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (synthesized) {
+ // Don't consider synthesized responses
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ // check to see if the HSTS cache was updated
+ nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = httpChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(uri, NS_ERROR_CONTENT_BLOCKED);
+
+ bool hsts;
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0, nullptr, &hsts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (hsts) {
+ // An HSTS upgrade was found
+ return NS_OK;
+ }
+
+ // There is no HSTS upgrade available
+ return NS_ERROR_CONTENT_BLOCKED;
+}
+
+/** nsIStreamListener methods **/
+
+NS_IMETHODIMP
+HSTSPrimingListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset,
+ uint32_t count)
+{
+ uint32_t totalRead;
+ return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
+}
+
+// static
+nsresult
+HSTSPrimingListener::StartHSTSPriming(nsIChannel* aRequestChannel,
+ nsIHstsPrimingCallback* aCallback)
+{
+
+ nsCOMPtr<nsIURI> finalChannelURI;
+ nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(finalChannelURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_GetSecureUpgradedURI(finalChannelURI, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // check the HSTS cache
+ bool hsts;
+ bool cached;
+ nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0, &cached, &hsts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (hsts) {
+ // already saw this host and will upgrade if allowed by preferences
+ return aCallback->OnHSTSPrimingSucceeded(true);
+ }
+
+ if (cached) {
+ // there is a non-expired entry in the cache that doesn't allow us to
+ // upgrade, so go ahead and fail early.
+ return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
+ }
+
+ // Either it wasn't cached or the cached result has expired. Build a
+ // channel for the HEAD request.
+
+ nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo();
+ MOZ_ASSERT(originalLoadInfo, "can not perform HSTS priming without a loadInfo");
+ if (!originalLoadInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = static_cast<mozilla::LoadInfo*>
+ (originalLoadInfo.get())->CloneForNewRequest();
+
+ // the LoadInfo must have a security flag set in order to pass through priming
+ // if none of these security flags are set, go ahead and fail now instead of
+ // crashing in nsContentSecurityManager::ValidateSecurityFlags
+ nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
+ if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
+ securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ securityMode != nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
+ return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsLoadFlags loadFlags;
+ rv = aRequestChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadFlags &= HttpBaseChannel::INHIBIT_CACHING |
+ HttpBaseChannel::INHIBIT_PERSISTENT_CACHING |
+ HttpBaseChannel::LOAD_BYPASS_CACHE |
+ HttpBaseChannel::LOAD_FROM_CACHE |
+ HttpBaseChannel::VALIDATE_ALWAYS;
+ // Priming requests should never be intercepted by service workers and
+ // are always anonymous.
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
+ nsIRequest::LOAD_ANONYMOUS;
+
+ // Create a new channel to send the priming request
+ nsCOMPtr<nsIChannel> primingChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(primingChannel),
+ uri,
+ loadInfo,
+ loadGroup,
+ nullptr, // aCallbacks are set later
+ loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set method and headers
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(primingChannel);
+ if (!httpChannel) {
+ NS_ERROR("HSTSPrimingListener: Failed to QI to nsIHttpChannel!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Currently using HEAD per the draft, but under discussion to change to GET
+ // with credentials so if the upgrade is approved the result is already cached.
+ rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpChannel->
+ SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
+ NS_LITERAL_CSTRING("1"), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // attempt to set the class of service flags on the new channel
+ nsCOMPtr<nsIClassOfService> requestClass = do_QueryInterface(aRequestChannel);
+ if (!requestClass) {
+ NS_ERROR("HSTSPrimingListener: aRequestChannel is not an nsIClassOfService");
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIClassOfService> primingClass = do_QueryInterface(httpChannel);
+ if (!primingClass) {
+ NS_ERROR("HSTSPrimingListener: aRequestChannel is not an nsIClassOfService");
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t classFlags = 0;
+ rv = requestClass ->GetClassFlags(&classFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = primingClass->SetClassFlags(classFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set up listener which will start the original channel
+ nsCOMPtr<nsIStreamListener> primingListener(new HSTSPrimingListener(aCallback));
+
+ // Start priming
+ rv = primingChannel->AsyncOpen2(primingListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HSTSPrimerListener.h b/netwerk/protocol/http/HSTSPrimerListener.h
new file mode 100644
index 0000000000..05089911bf
--- /dev/null
+++ b/netwerk/protocol/http/HSTSPrimerListener.h
@@ -0,0 +1,108 @@
+/* -*- 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 HSTSPrimingListener_h__
+#define HSTSPrimingListener_h__
+
+#include "nsCOMPtr.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+
+#include "mozilla/Attributes.h"
+
+class nsIPrincipal;
+class nsINetworkInterceptController;
+class nsIHstsPrimingCallback;
+
+namespace mozilla {
+namespace net {
+
+class HttpChannelParent;
+class nsHttpChannel;
+
+/*
+ * How often do we get back an HSTS priming result which upgrades the connection to HTTPS?
+ */
+enum HSTSPrimingResult {
+ // This site has been seen before and won't be upgraded
+ eHSTS_PRIMING_CACHED_NO_UPGRADE = 0,
+ // This site has been seen before and will be upgraded
+ eHSTS_PRIMING_CACHED_DO_UPGRADE = 1,
+ // This site has been seen before and will be blocked
+ eHSTS_PRIMING_CACHED_BLOCK = 2,
+ // The request was already upgraded, probably through
+ // upgrade-insecure-requests
+ eHSTS_PRIMING_ALREADY_UPGRADED = 3,
+ // HSTS priming is successful and the connection will be upgraded to HTTPS
+ eHSTS_PRIMING_SUCCEEDED = 4,
+ // When priming succeeds, but preferences require preservation of the order
+ // of mixed-content and hsts, and mixed-content blocks the load
+ eHSTS_PRIMING_SUCCEEDED_BLOCK = 5,
+ // When priming succeeds, but preferences require preservation of the order
+ // of mixed-content and hsts, and mixed-content allows the load over http
+ eHSTS_PRIMING_SUCCEEDED_HTTP = 6,
+ // HSTS priming failed, and the load is blocked by mixed-content
+ eHSTS_PRIMING_FAILED_BLOCK = 7,
+ // HSTS priming failed, and the load is allowed by mixed-content
+ eHSTS_PRIMING_FAILED_ACCEPT = 8
+};
+
+//////////////////////////////////////////////////////////////////////////
+// Class used as streamlistener and notification callback when
+// doing the HEAD request for an HSTS Priming check. Needs to be an
+// nsIStreamListener in order to receive events from AsyncOpen2
+class HSTSPrimingListener final : public nsIStreamListener,
+ public nsIInterfaceRequestor
+{
+public:
+ explicit HSTSPrimingListener(nsIHstsPrimingCallback* aCallback)
+ : mCallback(aCallback)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+private:
+ ~HSTSPrimingListener() {}
+
+ // Only nsHttpChannel can invoke HSTS priming
+ friend class mozilla::net::nsHttpChannel;
+
+ /**
+ * Start the HSTS priming request. This will send an anonymous HEAD request to
+ * the URI aRequestChannel is attempting to load. On success, the new HSTS
+ * priming channel is allocated in aHSTSPrimingChannel.
+ *
+ * @param aRequestChannel the reference channel used to initialze the HSTS
+ * priming channel
+ * @param aCallback the callback stored to handle the results of HSTS priming.
+ * @param aHSTSPrimingChannel if the new HSTS priming channel is allocated
+ * successfully, it will be placed here.
+ */
+ static nsresult StartHSTSPriming(nsIChannel* aRequestChannel,
+ nsIHstsPrimingCallback* aCallback);
+
+ /**
+ * Given a request, return NS_OK if it has resulted in a cached HSTS update.
+ * We don't need to check for the header as that has already been done for us.
+ */
+ nsresult CheckHSTSPrimingRequestStatus(nsIRequest* aRequest);
+
+ /**
+ * the nsIHttpChannel to notify with the result of HSTS priming.
+ */
+ nsCOMPtr<nsIHstsPrimingCallback> mCallback;
+};
+
+
+}} // mozilla::net
+
+#endif // HSTSPrimingListener_h__
diff --git a/netwerk/protocol/http/Http2Compression.cpp b/netwerk/protocol/http/Http2Compression.cpp
new file mode 100644
index 0000000000..1b4603e1af
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.cpp
@@ -0,0 +1,1487 @@
+/* -*- 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 "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+static nsDeque *gStaticHeaders = nullptr;
+
+class HpackStaticTableReporter final : public nsIMemoryReporter
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ HpackStaticTableReporter() {}
+
+ 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() {}
+};
+
+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() {}
+
+ 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, EmptyCString());
+}
+
+static void
+InitializeStaticHeaders()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (!gStaticHeaders) {
+ gStaticHeaders = new nsDeque();
+ gStaticReporter = new HpackStaticTableReporter();
+ RegisterStrongMemoryReporter(gStaticReporter);
+ AddStaticElement(NS_LITERAL_CSTRING(":authority"));
+ AddStaticElement(NS_LITERAL_CSTRING(":method"), NS_LITERAL_CSTRING("GET"));
+ AddStaticElement(NS_LITERAL_CSTRING(":method"), NS_LITERAL_CSTRING("POST"));
+ AddStaticElement(NS_LITERAL_CSTRING(":path"), NS_LITERAL_CSTRING("/"));
+ AddStaticElement(NS_LITERAL_CSTRING(":path"), NS_LITERAL_CSTRING("/index.html"));
+ AddStaticElement(NS_LITERAL_CSTRING(":scheme"), NS_LITERAL_CSTRING("http"));
+ AddStaticElement(NS_LITERAL_CSTRING(":scheme"), NS_LITERAL_CSTRING("https"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("200"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("204"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("206"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("304"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("400"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("404"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("500"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept-charset"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept-encoding"), NS_LITERAL_CSTRING("gzip, deflate"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept-language"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept-ranges"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept"));
+ AddStaticElement(NS_LITERAL_CSTRING("access-control-allow-origin"));
+ AddStaticElement(NS_LITERAL_CSTRING("age"));
+ AddStaticElement(NS_LITERAL_CSTRING("allow"));
+ AddStaticElement(NS_LITERAL_CSTRING("authorization"));
+ AddStaticElement(NS_LITERAL_CSTRING("cache-control"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-disposition"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-encoding"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-language"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-length"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-location"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-range"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-type"));
+ AddStaticElement(NS_LITERAL_CSTRING("cookie"));
+ AddStaticElement(NS_LITERAL_CSTRING("date"));
+ AddStaticElement(NS_LITERAL_CSTRING("etag"));
+ AddStaticElement(NS_LITERAL_CSTRING("expect"));
+ AddStaticElement(NS_LITERAL_CSTRING("expires"));
+ AddStaticElement(NS_LITERAL_CSTRING("from"));
+ AddStaticElement(NS_LITERAL_CSTRING("host"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-match"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-modified-since"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-none-match"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-range"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-unmodified-since"));
+ AddStaticElement(NS_LITERAL_CSTRING("last-modified"));
+ AddStaticElement(NS_LITERAL_CSTRING("link"));
+ AddStaticElement(NS_LITERAL_CSTRING("location"));
+ AddStaticElement(NS_LITERAL_CSTRING("max-forwards"));
+ AddStaticElement(NS_LITERAL_CSTRING("proxy-authenticate"));
+ AddStaticElement(NS_LITERAL_CSTRING("proxy-authorization"));
+ AddStaticElement(NS_LITERAL_CSTRING("range"));
+ AddStaticElement(NS_LITERAL_CSTRING("referer"));
+ AddStaticElement(NS_LITERAL_CSTRING("refresh"));
+ AddStaticElement(NS_LITERAL_CSTRING("retry-after"));
+ AddStaticElement(NS_LITERAL_CSTRING("server"));
+ AddStaticElement(NS_LITERAL_CSTRING("set-cookie"));
+ AddStaticElement(NS_LITERAL_CSTRING("strict-transport-security"));
+ AddStaticElement(NS_LITERAL_CSTRING("transfer-encoding"));
+ AddStaticElement(NS_LITERAL_CSTRING("user-agent"));
+ AddStaticElement(NS_LITERAL_CSTRING("vary"));
+ AddStaticElement(NS_LITERAL_CSTRING("via"));
+ AddStaticElement(NS_LITERAL_CSTRING("www-authenticate"));
+ }
+}
+
+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)
+{
+ mByteCount += name.Length() + value.Length() + 32;
+ nvPair *pair = new nvPair(name, value);
+ mTable.PushFront(pair);
+}
+
+void
+nvFIFO::AddElement(const nsCString &name)
+{
+ AddElement(name, EmptyCString());
+}
+
+void
+nvFIFO::RemoveElement()
+{
+ nvPair *pair = static_cast<nvPair *>(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 static_cast<nvPair *>(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 static_cast<nvPair *>(mTable.ObjectAt(index - gStaticHeaders->GetSize()));
+ }
+ return static_cast<nvPair *>(gStaticHeaders->ObjectAt(index));
+}
+
+Http2BaseCompressor::Http2BaseCompressor()
+ : mOutput(nullptr)
+ , mMaxBuffer(kDefaultMaxBuffer)
+ , mMaxBufferSetting(kDefaultMaxBuffer)
+ , mSetInitialMaxBufferSizeAllowed(true)
+ , mPeakSize(0)
+ , mPeakCount(0)
+{
+ 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()
+{
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ 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;
+}
+
+nsresult
+Http2Decompressor::DecodeHeaderBlock(const uint8_t *data, uint32_t datalen,
+ nsACString &output, bool isPush)
+{
+ mSetInitialMaxBufferSizeAllowed = false;
+ mOffset = 0;
+ mData = data;
+ mDataLen = datalen;
+ mOutput = &output;
+ mOutput->Truncate();
+ 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 < datalen)) {
+ bool modifiesTable = true;
+ if (mData[mOffset] & 0x80) {
+ rv = DoIndexed();
+ LOG(("Decompressor state after indexed"));
+ } else if (mData[mOffset] & 0x40) {
+ rv = DoLiteralWithIncremental();
+ LOG(("Decompressor state after literal with incremental"));
+ } else if (mData[mOffset] & 0x20) {
+ rv = DoContextUpdate();
+ LOG(("Decompressor state after context update"));
+ } else if (mData[mOffset] & 0x10) {
+ modifiesTable = false;
+ rv = DoLiteralNeverIndexed();
+ LOG(("Decompressor state after literal never index"));
+ } else {
+ modifiesTable = false;
+ rv = DoLiteralWithoutIndex();
+ LOG(("Decompressor state after literal without index"));
+ }
+ DumpState();
+ 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)
+{
+ nsCCharSeparatedTokenizer t(headerValue, '\n');
+ while (t.hasMoreTokens()) {
+ const nsDependentCSubstring& authMethod = t.nextToken();
+ 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(NS_LITERAL_CSTRING("HTTP/2.0 "));
+ 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)
+{
+ 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)
+{
+ 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 (bytesConsumed >= mDataLen) {
+ if (!bitsLeft || (bytesConsumed > 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;
+ }
+
+ 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;
+ }
+
+ // 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()));
+ LOG(("Decompressor state after ClearHeaderTable"));
+ DumpState();
+ 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,
+ bool connectForm, nsACString &output)
+{
+ mSetInitialMaxBufferSizeAllowed = false;
+ mOutput = &output;
+ output.SetCapacity(1024);
+ output.Truncate();
+ mParsedContentLength = -1;
+
+ // first thing's first - context size updates (if necessary)
+ if (mBufferSizeChangeWaiting) {
+ if (mLowestBufferSizeWaiting < mMaxBufferSetting) {
+ EncodeTableSizeChange(mLowestBufferSizeWaiting);
+ }
+ EncodeTableSizeChange(mMaxBufferSetting);
+ mBufferSizeChangeWaiting = false;
+ }
+
+ // colon headers first
+ if (!connectForm) {
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false, false);
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":path"), path), true, false);
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false, false);
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":scheme"), scheme), false, false);
+ } else {
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false, false);
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), 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")) {
+ 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"));
+ }
+ }
+
+ mOutput = nullptr;
+ LOG(("Compressor state after EncodeHeaderBlock"));
+ DumpState();
+ 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);
+ LOG(("Compressor state after literal never index"));
+ DumpState();
+ return;
+ }
+
+ if (noLocalIndex || (newSize > (mMaxBuffer / 2)) || (mMaxBuffer < 128)) {
+ DoOutput(kPlainLiteral, &inputPair, nameReference);
+ LOG(("Compressor state after literal without index"));
+ DumpState();
+ 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));
+ LOG(("Compressor state after literal with index"));
+ DumpState();
+ return;
+ }
+
+ // emit an index
+ DoOutput(kIndex, &inputPair, matchedIndex);
+
+ LOG(("Compressor state after index"));
+ DumpState();
+ return;
+}
+
+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..0fb391bf73
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.h
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 "nsIMemoryReporter.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 mTable;
+};
+
+class HpackDynamicTableReporter;
+
+class Http2BaseCompressor
+{
+public:
+ Http2BaseCompressor();
+ virtual ~Http2BaseCompressor();
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ nsresult SetInitialMaxBufferSize(uint32_t maxBufferSize);
+
+protected:
+ const static uint32_t kDefaultMaxBuffer = 4096;
+
+ virtual void ClearHeaderTable();
+ virtual void MakeRoom(uint32_t amount, const char *direction);
+ virtual void DumpState();
+ 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::ID mPeakSizeID;
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::ID mPeakCountID;
+
+private:
+ RefPtr<HpackDynamicTableReporter> mDynamicReporter;
+};
+
+class Http2Compressor;
+
+class Http2Decompressor final : public Http2BaseCompressor
+{
+public:
+ Http2Decompressor()
+ {
+ mPeakSizeID = Telemetry::HPACK_PEAK_SIZE_DECOMPRESSOR;
+ mPeakCountID = Telemetry::HPACK_PEAK_COUNT_DECOMPRESSOR;
+ };
+ virtual ~Http2Decompressor() { } ;
+
+ // NS_OK: Produces the working set of HTTP/1 formatted headers
+ 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:
+ nsresult DoIndexed();
+ nsresult DoLiteralWithoutIndex();
+ nsresult DoLiteralWithIncremental();
+ nsresult DoLiteralInternal(nsACString &, nsACString &, uint32_t);
+ nsresult DoLiteralNeverIndexed();
+ nsresult DoContextUpdate();
+
+ nsresult DecodeInteger(uint32_t prefixLen, uint32_t &result);
+ nsresult OutputHeader(uint32_t index);
+ nsresult OutputHeader(const nsACString &name, const nsACString &value);
+
+ nsresult CopyHeaderString(uint32_t index, nsACString &name);
+ nsresult CopyStringFromInput(uint32_t index, nsACString &val);
+ uint8_t ExtractByte(uint8_t bitsLeft, uint32_t &bytesConsumed);
+ nsresult CopyHuffmanStringFromInput(uint32_t index, nsACString &val);
+ nsresult DecodeHuffmanCharacter(const HuffmanIncomingTable *table, uint8_t &c,
+ uint32_t &bytesConsumed, uint8_t &bitsLeft);
+ 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() { }
+
+ // HTTP/1 formatted header block as input - HTTP/2 formatted
+ // header block as output
+ nsresult EncodeHeaderBlock(const nsCString &nvInput,
+ const nsACString &method, const nsACString &path,
+ const nsACString &host, const nsACString &scheme,
+ bool connectForm, 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..cf035cd904
--- /dev/null
+++ b/netwerk/protocol/http/Http2HuffmanIncoming.h
@@ -0,0 +1,4054 @@
+/*
+ * 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 {
+ const uint16_t mValue:9; // 9 bits so it can hold 0..256
+ const 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..ba59e6bd5a
--- /dev/null
+++ b/netwerk/protocol/http/Http2HuffmanOutgoing.h
@@ -0,0 +1,278 @@
+/*
+ * 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 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..b6fc485e2f
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -0,0 +1,510 @@
+/* -*- 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 "nsHttpChannel.h"
+#include "nsIHttpPushListener.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class CallChannelOnPush final : public Runnable {
+ public:
+ CallChannelOnPush(nsIHttpChannelInternal *associatedChannel,
+ const nsACString &pushedURI,
+ Http2PushedStream *pushStream)
+ : mAssociatedChannel(associatedChannel)
+ , mPushedURI(pushedURI)
+ , mPushedStream(pushStream)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<nsHttpChannel> channel;
+ CallQueryInterface(mAssociatedChannel, channel.StartAssignment());
+ MOZ_ASSERT(channel);
+ if (channel && NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStream))) {
+ return NS_OK;
+ }
+
+ LOG3(("Http2PushedStream Orphan %p failed OnPush\n", this));
+ mPushedStream->OnPushFailed();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIHttpChannelInternal> mAssociatedChannel;
+ const nsCString mPushedURI;
+ Http2PushedStream *mPushedStream;
+};
+
+//////////////////////////////////////////
+// Http2PushedStream
+//////////////////////////////////////////
+
+Http2PushedStream::Http2PushedStream(Http2PushTransactionBuffer *aTransaction,
+ Http2Session *aSession,
+ Http2Stream *aAssociatedStream,
+ uint32_t aID)
+ :Http2Stream(aTransaction, aSession, 0)
+ , 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();
+ SetPriority(aAssociatedStream->Priority() + 1);
+}
+
+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 %x\n", this, status));
+
+ if (NS_SUCCEEDED(status) && mDeferCleanupOnSuccess) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %x defer on success\n", this, status));
+ return true;
+ }
+ if (mDeferCleanupOnPush) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %x defer onPush ref\n", this, status));
+ return true;
+ }
+ if (mConsumerStream) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %x defer active consumer\n", this, status));
+ return true;
+ }
+ LOG3(("Http2PushedStream::DeferCleanup Query %p %x not deferred\n", this, status));
+ return false;
+}
+
+// return true if channel implements nsIHttpPushListener
+bool
+Http2PushedStream::TryOnPush()
+{
+ nsHttpTransaction *trans = mAssociatedTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return false;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel());
+ if (!associatedChannel) {
+ return false;
+ }
+
+ if (!(trans->Caps() & NS_HTTP_ONPUSH_LISTENER)) {
+ return false;
+ }
+
+ mDeferCleanupOnPush = true;
+ nsCString uri = Origin() + Path();
+ NS_DispatchToMainThread(new CallChannelOnPush(associatedChannel, uri, this));
+ 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;
+ }
+ nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel());
+ if (!associatedChannel) {
+ 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;
+
+ 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
+ CreatePushHashKey(mHeaderScheme, mHeaderHost,
+ 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)
+{
+ 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;
+}
+
+//////////////////////////////////////////
+// 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;
+}
+
+void
+Http2PushTransactionBuffer::SetDNSWasRefreshed()
+{
+}
+
+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 [%u] 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::AddTransaction(nsAHttpTransaction *trans)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+Http2PushTransactionBuffer::PipelineDepth()
+{
+ return 0;
+}
+
+nsresult
+Http2PushTransactionBuffer::SetPipelinePosition(int32_t position)
+{
+ return NS_OK;
+}
+
+int32_t
+Http2PushTransactionBuffer::PipelinePosition()
+{
+ return 1;
+}
+
+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..fd39eb2c7c
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 "nsILoadGroup.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);
+ virtual ~Http2PushedStream() {}
+
+ 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);
+ bool GetHashKey(nsCString &key);
+
+ // override of Http2Stream
+ nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *) override;
+ nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *) override;
+ void AdjustInitialWindow() override;
+
+ nsIRequestContext *RequestContext() override { return mRequestContext; };
+ void ConnectPushedStream(Http2Stream *consumer);
+
+ bool TryOnPush();
+ 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; }
+
+ nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten);
+
+ // overload of Http2Stream
+ virtual bool HasSink() override { return !!mConsumerStream; }
+
+ nsCString &GetRequestString() { return mRequestString; }
+
+private:
+
+ 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;
+
+};
+
+class Http2PushTransactionBuffer final : public nsAHttpTransaction
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+
+ Http2PushTransactionBuffer();
+
+ nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten);
+ void SetPushStream(Http2PushedStream *stream) { mPushStream = stream; }
+
+private:
+ virtual ~Http2PushTransactionBuffer();
+
+ 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;
+};
+
+} // 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..a2721017d3
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -0,0 +1,3884 @@
+/* -*- 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 "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 "nsISSLStatus.h"
+#include "nsISSLStatusProvider.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"
+
+namespace mozilla {
+namespace net {
+
+// Http2Session has multiple inheritance of things that implement
+// nsISupports, so this magic is taken from nsHttpPipeline that
+// implements some of the same abstract classes.
+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
+};
+
+#define RETURN_SESSION_ERROR(o,x) \
+do { \
+ (o)->mGoAwayReason = (x); \
+ return NS_ERROR_ILLEGAL_VALUE; \
+ } while (0)
+
+Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version)
+ : 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)
+ , 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)
+ , mWaitingForSettingsAck(false)
+ , mGoAwayOnPush(false)
+ , mUseH2Deps(false)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ static uint64_t sSerial;
+ mSerial = ++sSerial;
+
+ LOG3(("Http2Session::Http2Session %p serial=0x%X\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();
+ SendHello();
+
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ mPingThreshold = gHttpHandler->SpdyPingThreshold();
+ mPreviousPingThreshold = mPingThreshold;
+}
+
+void
+Http2Session::Shutdown()
+{
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<Http2Stream> &stream = iter.Data();
+
+ // 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 {
+ CloseStream(stream, NS_ERROR_ABORT);
+ }
+ }
+}
+
+Http2Session::~Http2Session()
+{
+ LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X",
+ this, mDownstreamState));
+
+ Shutdown();
+
+ Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
+ Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
+ Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
+ mServerPushedResources);
+ Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
+ Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
+}
+
+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
+};
+
+bool
+Http2Session::RoomForMoreConcurrent()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n",
+ this, PR_IntervalToSeconds(now - mLastReadEpoch)));
+
+ if (!mPingThreshold)
+ return UINT32_MAX;
+
+ if ((now - mLastReadEpoch) < mPingThreshold) {
+ // recent activity means ping is not an issue
+ if (mPingSentEpoch) {
+ mPingSentEpoch = 0;
+ if (mPreviousUsed) {
+ // restore the former value
+ mPingThreshold = mPreviousPingThreshold;
+ mPreviousUsed = false;
+ }
+ }
+
+ return PR_IntervalToSeconds(mPingThreshold) -
+ PR_IntervalToSeconds(now - mLastReadEpoch);
+ }
+
+ if (mPingSentEpoch) {
+ LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n"));
+ 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);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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;
+ }
+
+ LOG3(("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.Get(aNewID)) {
+ LOG3((" New ID already present\n"));
+ MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
+ mShouldGoAway = true;
+ return kDeadStreamID;
+ }
+
+ mStreamIDHash.Put(aNewID, stream);
+ return aNewID;
+}
+
+bool
+Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
+ int32_t aPriority,
+ bool aUseTunnel,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // integrity check
+ if (mStreamTransactionHash.Get(aHttpTransaction)) {
+ LOG3((" New transaction already present\n"));
+ MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
+ return false;
+ }
+
+ if (!mConnection) {
+ mConnection = aHttpTransaction->Connection();
+ }
+
+ if (mClosed || mShouldGoAway) {
+ nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
+ if (trans && !trans->GetPushedStream()) {
+ LOG3(("Http2Session::AddStream %p atrans=%p trans=%p session unusable - resched.\n",
+ this, aHttpTransaction, trans));
+ aHttpTransaction->SetConnection(nullptr);
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ return true;
+ }
+ }
+
+ aHttpTransaction->SetConnection(this);
+
+ if (aUseTunnel) {
+ LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
+ this, aHttpTransaction));
+ DispatchOnTunnel(aHttpTransaction, aCallbacks);
+ return true;
+ }
+
+ Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority);
+
+ LOG3(("Http2Session::AddStream session=%p stream=%p serial=%u "
+ "NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID));
+
+ mStreamTransactionHash.Put(aHttpTransaction, stream);
+
+ 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;
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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 = static_cast<Http2Stream *>(mQueuedStreams.ObjectAt(i));
+ MOZ_ASSERT(qStream != stream);
+ MOZ_ASSERT(qStream->Queued());
+ }
+#endif
+
+ stream->SetQueued(true);
+ mQueuedStreams.Push(stream);
+}
+
+void
+Http2Session::ProcessPending()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ Http2Stream*stream;
+ while (RoomForMoreConcurrent() &&
+ (stream = static_cast<Http2Stream *>(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(PR_GetCurrentThread() == gSocketThread);
+
+ if (!count) {
+ *countWritten = 0;
+ return NS_OK;
+ }
+
+ nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
+ if (NS_SUCCEEDED(rv) && *countWritten > 0)
+ mLastReadEpoch = PR_IntervalNow();
+ return rv;
+}
+
+void
+Http2Session::SetWriteCallbacks()
+{
+ if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
+ mConnection->ResumeSend();
+}
+
+void
+Http2Session::RealignOutputQueue()
+{
+ 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;
+
+ rv = mSegmentReader->
+ OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
+ &countRead);
+ LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%x actual=%d",
+ this, avail, rv, countRead));
+
+ // Dont worry about errors on write, we will pick this up as a read error too
+ if (NS_FAILED(rv))
+ return;
+
+ if (countRead == avail) {
+ mOutputQueueUsed = 0;
+ mOutputQueueSent = 0;
+ return;
+ }
+
+ mOutputQueueSent += countRead;
+
+ // 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));
+ mShouldGoAway = true;
+ if (!mStreamTransactionHash.Count())
+ Close(NS_OK);
+}
+
+uint32_t
+Http2Session::GetWriteQueueSize()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ return mReadyForWrite.GetSize();
+}
+
+void
+Http2Session::ChangeDownstreamState(enum internalStateType newState)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X",
+ this, mDownstreamState, newState));
+ mDownstreamState = newState;
+}
+
+void
+Http2Session::ResetDownstreamState()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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 %d\n", this, aStream));
+ QueueStream(aStream);
+ return false;
+ }
+
+ LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream));
+ IncrementConcurrent(aStream);
+ return true;
+}
+
+void
+Http2Session::IncrementConcurrent(Http2Stream *stream)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::GeneratePriority %p %X %X\n",
+ this, aID, aPriorityWeight));
+
+ uint32_t frameSize = kFrameHeaderBytes + 5;
+ char *packet = EnsureOutputBuffer(frameSize);
+ mOutputQueueUsed += frameSize;
+
+ CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, aID);
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, 0);
+ memcpy(packet + frameSize - 1, &aPriorityWeight, 1);
+ LogIO(this, nullptr, "Generate Priority", packet, frameSize);
+ FlushOutputQueue();
+}
+
+void
+Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // 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(PR_GetCurrentThread() == gSocketThread);
+ 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] 5 priority frames for streams which will never be opened with headers
+// these streams (3, 5, 7, 9, b) build a dependency tree that all other
+// streams will be direct leaves of.
+void
+Http2Session::SendHello()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::SendHello %p\n", this));
+
+ // sized for magic + 5 settings and a session window update and 5 priority frames
+ // 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window update,
+ // 5 priority frames at 14 (9 + 5) each
+ static const uint32_t maxSettings = 5;
+ static const uint32_t prioritySize = 5 * (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;
+ }
+
+ FlushOutputQueue();
+}
+
+void
+Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, uint8_t weight,
+ const char *label)
+{
+ char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
+ mOutputQueueUsed += kFrameHeaderBytes + 5;
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, dependsOn); // depends on
+ packet[kFrameHeaderBytes + 4] = weight; // 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(PR_GetCurrentThread() == gSocketThread);
+
+#ifndef DEBUG
+ // Only do the real verification in debug builds
+ return true;
+#endif
+
+ 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.Get(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 (0);
+
+ 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;
+}
+
+void
+Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
+ errorType aResetCode)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::CleanupStream %p %p 0x%X %X\n",
+ this, aStream, aStream ? aStream->StreamID() : 0, aResult));
+ if (!aStream) {
+ return;
+ }
+
+ 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;
+ }
+
+ 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));
+ pushSource->SetConsumerStream(nullptr);
+ }
+
+ // 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;
+ pushStream->GetHashKey(hashKey);
+ nsIRequestContext *requestContext = aStream->RequestContext();
+ if (requestContext) {
+ SpdyPushCache *cache = nullptr;
+ requestContext->GetSpdyPushCache(&cache);
+ if (cache) {
+ Http2PushedStream *trash = cache->RemovePushedStreamHttp2(hashKey);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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 &queue)
+{
+ size_t size = queue.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http2Stream *stream = static_cast<Http2Stream *>(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(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::CloseStream %p %p 0x%x %X\n",
+ this, aStream, aStream->StreamID(), 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_SESSION_ERROR(this, 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;
+ }
+ self->SetInputFrameDataStream(self->mInputFrameID);
+
+ // 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;
+ nsresult rv;
+
+ 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_SESSION_ERROR(self, 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_SESSION_ERROR(self, 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 (!endHeadersFlag) { // more are coming - don't process yet
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ 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));
+
+ // only interpret headers once, afterwards ignore as trailers
+ if (mInputFrameDataStream->AllHeadersReceived()) {
+ LOG3(("Http2Session::ResponseHeadersComplete extra headers"));
+ MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
+ nsresult rv = UncompressAndDiscard(false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::ResponseHeadersComplete extra uncompress 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_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameID) {
+ LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
+ RETURN_SESSION_ERROR(self, 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);
+ if (self->mInputFrameDataStream) {
+ self->mInputFrameDataStream->SetPriorityDependency(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_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameID) {
+ LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
+ RETURN_SESSION_ERROR(self, 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));
+
+ self->SetInputFrameDataStream(self->mInputFrameID);
+ 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_SESSION_ERROR(self, 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_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ 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"));
+ RETURN_SESSION_ERROR(self, 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_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+ // We stick to the default for simplicity's sake, so nothing to change
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ self->ResetDownstreamState();
+
+ if (!(self->mInputFrameFlags & kFlag_ACK)) {
+ self->GenerateSettingsAck();
+ } else if (self->mWaitingForSettingsAck) {
+ self->mGoAwayOnPush = true;
+ }
+
+ 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_SESSION_ERROR(self, 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_SESSION_ERROR(self, 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_SESSION_ERROR(self, 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_SESSION_ERROR(self, 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 {
+ nsIRequestContext *requestContext = associatedStream->RequestContext();
+ if (requestContext) {
+ requestContext->GetSpdyPushCache(&cache);
+ if (!cache) {
+ cache = new SpdyPushCache();
+ if (!cache || NS_FAILED(requestContext->SetSpdyPushCache(cache))) {
+ delete cache;
+ cache = nullptr;
+ }
+ }
+ }
+ 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->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
+ LOG3(("Http2Session::RecvPushPromise not finishing processing for multi-frame push\n"));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // Create the buffering transaction and push stream
+ RefPtr<Http2PushTransactionBuffer> transactionBuffer =
+ new Http2PushTransactionBuffer();
+ transactionBuffer->SetConnection(self);
+ Http2PushedStream *pushedStream =
+ new Http2PushedStream(transactionBuffer, self, associatedStream, promisedID);
+
+ 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);
+ delete pushedStream;
+ 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);
+ delete pushedStream;
+ 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.
+ self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
+ self->mPushedStreams.AppendElement(pushedStream);
+
+ if (self->RegisterStreamID(pushedStream, 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;
+ pushedStream->ReadSegments(nullptr, 1, &notUsed);
+
+ nsAutoCString key;
+ if (!pushedStream->GetHashKey(key)) {
+ LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n"));
+ self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ RefPtr<nsStandardURL> associatedURL, pushedURL;
+ rv = Http2Stream::MakeOriginURL(associatedStream->Origin(), associatedURL);
+ if (NS_SUCCEEDED(rv)) {
+ rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedURL);
+ }
+ LOG3(("Http2Session::RecvPushPromise %p checking %s == %s", self,
+ associatedStream->Origin().get(), pushedStream->Origin().get()));
+ bool match = false;
+ if (NS_SUCCEEDED(rv)) {
+ rv = associatedURL->Equals(pushedURL, &match);
+ }
+ if (NS_FAILED(rv)) {
+ // Fallback to string equality of origins. This won't be guaranteed to be as
+ // liberal as we want it to be, but it will at least be safe
+ match = associatedStream->Origin().Equals(pushedStream->Origin());
+ }
+ if (!match) {
+ LOG3(("Http2Session::RecvPushPromise %p pushed stream mismatched origin "
+ "associated origin %s .. pushed origin %s\n", self,
+ associatedStream->Origin().get(), pushedStream->Origin().get()));
+ self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (pushedStream->TryOnPush()) {
+ LOG3(("Http2Session::RecvPushPromise %p channel implements nsIHttpPushListener "
+ "stream %p will not be placed into session cache.\n", self, pushedStream));
+ } else {
+ LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n", self));
+ if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
+ LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
+ self->CleanupStream(pushedStream, NS_ERROR_FAILURE, INTERNAL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ }
+
+ pushedStream->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
+ static_assert(Http2Stream::kWorstPriority >= 0,
+ "kWorstPriority out of range");
+ uint8_t priorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
+ (Http2Stream::kWorstPriority - Http2Stream::kNormalPriority);
+ pushedStream->SetPriority(Http2Stream::kWorstPriority);
+ self->GeneratePriority(promisedID, 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_SESSION_ERROR(self, FRAME_SIZE_ERROR);
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n",
+ self, self->mInputFrameID));
+ RETURN_SESSION_ERROR(self, 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_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
+ self, self->mInputFrameID));
+ RETURN_SESSION_ERROR(self, 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
+ nsAutoPtr<Http2Stream>& stream = iter.Data();
+ 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 =
+ static_cast<Http2Stream *>(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_SESSION_ERROR(self, 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 "
+ "%d increased by %d now %d.\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_SESSION_ERROR(self, 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_SESSION_ERROR(self, 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);
+
+ nsAutoPtr<Http2Stream>& stream = iter.Data();
+ if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
+ continue;
+ }
+
+ self->mReadyForWrite.Push(stream);
+ self->SetWriteCallbacks();
+ }
+ }
+ LOG3(("Http2Session::RecvWindowUpdate %p session window "
+ "%d increased by %d now %d.\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));
+
+ self->SetInputFrameDataStream(self->mInputFrameID);
+
+ if (!self->mInputFrameDataStream) {
+ LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
+ self->mInputFrameID));
+ RETURN_SESSION_ERROR(self, 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)
+ : 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);
+
+ AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
+ mCI->GetUsername(), mCI->GetPrivate(), mCallbacks,
+ 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->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;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
+// of these methods
+//-----------------------------------------------------------------------------
+
+void
+Http2Session::OnTransportStatus(nsITransport* aTransport,
+ nsresult aStatus, int64_t aProgress)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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:
+ {
+ Http2Stream *target = mStreamIDHash.Get(1);
+ nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
+ if (transaction)
+ transaction->OnTransportStatus(aTransport, aStatus, aProgress);
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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 %x",
+ this, 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 = static_cast<Http2Stream *>(mReadyForWrite.PopFront());
+ if (!stream) {
+ LOG3(("Http2Session %p could not identify a stream to write; suspending.",
+ this));
+ FlushOutputQueue();
+ SetWriteCallbacks();
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ 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);
+
+ // 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
+ 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 %X",
+ this, 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_SESSION_ERROR(this, 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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 =
+ static_cast<Http2Stream *>(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 =
+ static_cast<Http2Stream *>(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 %x\n",
+ this, 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_SESSION_ERROR(this, 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 = 0;
+ }
+
+ mPaddingLength = 0;
+
+ LOG3(("Http2Session::WriteSegments[%p::%x] 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_SESSION_ERROR(this, 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_SESSION_ERROR(this, PROTOCOL_ERROR);
+ }
+
+ if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
+ mInputFrameType != FRAME_TYPE_SETTINGS) {
+ LOG3(("First Frame Type Must Be Settings\n"));
+ RETURN_SESSION_ERROR(this, 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 %x\n",
+ this, 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_SESSION_ERROR(this, 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();
+ } 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 %x\n",
+ this, streamID, mNeedsCleanup, rv));
+ MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
+ CleanupStream(streamID, NS_OK, CANCEL_ERROR);
+ mNeedsCleanup = nullptr;
+ *again = false;
+ ResumeRecv();
+ 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 %x\n", this, 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 data",
+ this, discardCount));
+
+ 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();
+ ResumeRecv();
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = NetworkRead(writer, trash, discardCount, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p discard frame read failure %x\n", this, 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;
+ }
+
+ ResetDownstreamState();
+
+ if (streamToCleanup) {
+ 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 %x\n",
+ this, 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::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;
+ 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 %X %d\n",
+ this, slowConsumer->StreamID(), 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=%llu localWindow=%lld\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=%lld\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(PR_GetCurrentThread() == gSocketThread);
+
+ if (mClosed)
+ return;
+
+ LOG3(("Http2Session::Close %p %X", this, aReason));
+
+ mClosed = true;
+
+ Shutdown();
+
+ mStreamIDHash.Clear();
+ mStreamTransactionHash.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_ILLEGAL_VALUE) {
+ goAwayReason = PROTOCOL_ERROR;
+ } else {
+ goAwayReason = INTERNAL_ERROR;
+ }
+ 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(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::CloseTransaction %p %p %x", this, aTransaction, aResult));
+
+ // Generally this arrives as a cancel event from the connection manager.
+
+ // need to find the stream and call CleanupStream() on it.
+ Http2Stream *stream = mStreamTransactionHash.Get(aTransaction);
+ if (!stream) {
+ LOG3(("Http2Session::CloseTransaction %p %p %x - not found.",
+ this, aTransaction, aResult));
+ return;
+ }
+ LOG3(("Http2Session::CloseTransaction probably a cancel. "
+ "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p",
+ this, aTransaction, aResult, stream->StreamID(), stream));
+ CleanupStream(stream, aResult, CANCEL_ERROR);
+ ResumeRecv();
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult
+Http2Session::OnReadSegment(const char *buf,
+ uint32_t count, uint32_t *countRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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)
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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);
+ ForceRecv();
+}
+
+void
+Http2Session::ConnectSlowConsumer(Http2Stream *stream)
+{
+ LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n",
+ this, stream->StreamID()));
+ mSlowConsumersReadyForRead.Push(stream);
+ ForceRecv();
+}
+
+uint32_t
+Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ uint32_t rv = 0;
+ mTunnelHash.Get(aConnInfo->HashKey(), &rv);
+ return rv;
+}
+
+void
+Http2Session::RegisterTunnel(Http2Stream *aTunnel)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
+ uint32_t newcount = FindTunnelCount(ci) + 1;
+ mTunnelHash.Remove(ci->HashKey());
+ mTunnelHash.Put(ci->HashKey(), newcount);
+ LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]",
+ this, aTunnel, newcount, ci->HashKey().get()));
+}
+
+void
+Http2Session::UnRegisterTunnel(Http2Stream *aTunnel)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
+ MOZ_ASSERT(FindTunnelCount(ci));
+ uint32_t newcount = FindTunnelCount(ci) - 1;
+ mTunnelHash.Remove(ci->HashKey());
+ if (newcount) {
+ mTunnelHash.Put(ci->HashKey(), newcount);
+ }
+ LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]",
+ this, aTunnel, newcount, ci->HashKey().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<SpdyConnectTransaction> connectTrans =
+ new SpdyConnectTransaction(ci, aCallbacks, trans->Caps(), trans, this);
+ AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false, nullptr);
+ Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans);
+ MOZ_ASSERT(tunnel);
+ RegisterTunnel(tunnel);
+}
+
+void
+Http2Session::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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));
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ }
+}
+
+// From ASpdySession
+bool
+Http2Session::MaybeReTunnel(nsAHttpTransaction *aHttpTransaction)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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);
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ 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)
+{
+ nsAHttpSegmentReader *old = mSegmentReader;
+ mSegmentReader = nullptr;
+ nsresult rv = OnReadSegment(buf, count, countRead);
+ mSegmentReader = old;
+ return rv;
+}
+
+bool // static
+Http2Session::ALPNCallback(nsISupports *securityInfo)
+{
+ if (!gHttpHandler->IsH2MandatorySuiteEnabled()) {
+ LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
+ return false;
+ }
+
+ 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) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult
+Http2Session::ConfirmTLSProfile()
+{
+ if (mTLSProfileConfirmed)
+ return NS_OK;
+
+ LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
+ this, mConnection.get()));
+
+ 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_SESSION_ERROR(this, 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_SESSION_ERROR(this, 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_SESSION_ERROR(this, 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_SESSION_ERROR(this, 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_SESSION_ERROR(this, 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(PR_GetCurrentThread() == gSocketThread);
+ 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.
+
+ 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.
+ ForceSend();
+}
+
+void
+Http2Session::TransactionHasDataToRecv(nsAHttpTransaction *caller)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));
+
+ // a signal from the http transaction to the connection that it will consume more
+ 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(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
+ this, stream, stream->StreamID()));
+
+ mReadyForWrite.Push(stream);
+ SetWriteCallbacks();
+ ForceSend();
+}
+
+bool
+Http2Session::IsPersistent()
+{
+ return true;
+}
+
+nsresult
+Http2Session::TakeTransport(nsISocketTransport **,
+ nsIAsyncInputStream **, nsIAsyncOutputStream **)
+{
+ MOZ_ASSERT(false, "TakeTransport of Http2Session");
+ return NS_ERROR_UNEXPECTED;
+}
+
+already_AddRefed<nsHttpConnection>
+Http2Session::TakeHttpConnection()
+{
+ MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
+ return nullptr;
+}
+
+uint32_t
+Http2Session::CancelPipeline(nsresult reason)
+{
+ // we don't pipeline inside http/2, so this isn't an issue
+ return 0;
+}
+
+nsAHttpTransaction::Classifier
+Http2Session::Classification()
+{
+ if (!mConnection)
+ return nsAHttpTransaction::CLASS_GENERAL;
+ return mConnection->Classification();
+}
+
+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;
+}
+
+void
+Http2Session::SetDNSWasRefreshed()
+{
+ MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()");
+}
+
+uint64_t
+Http2Session::Available()
+{
+ MOZ_ASSERT(false, "Http2Session::Available()");
+ return 0;
+}
+
+nsHttpRequestHead *
+Http2Session::RequestHead()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(false,
+ "Http2Session::RequestHead() "
+ "should not be called after http/2 is setup");
+ return NULL;
+}
+
+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;
+}
+
+nsresult
+Http2Session::AddTransaction(nsAHttpTransaction *)
+{
+ // This API is meant for pipelining, Http2Session's should be
+ // extended with AddStream()
+
+ MOZ_ASSERT(false,
+ "Http2Session::AddTransaction() should not be called");
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+Http2Session::PipelineDepth()
+{
+ return IsDone() ? 0 : 1;
+}
+
+nsresult
+Http2Session::SetPipelinePosition(int32_t position)
+{
+ // This API is meant for pipelining, Http2Session's should be
+ // extended with AddStream()
+
+ MOZ_ASSERT(false,
+ "Http2Session::SetPipelinePosition() should not be called");
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+int32_t
+Http2Session::PipelinePosition()
+{
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Pass through methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsAHttpConnection *
+Http2Session::Connection()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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);
+ ResumeRecv();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h
new file mode 100644
index 0000000000..60986381b3
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.h
@@ -0,0 +1,508 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 "nsAHttpConnection.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsDeque.h"
+#include "nsHashKeys.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
+
+ Http2Session(nsISocketTransport *, uint32_t version);
+
+ bool AddStream(nsAHttpTransaction *, int32_t,
+ bool, nsIInterfaceRequestor *) override;
+ bool CanReuse() override { return !mShouldGoAway && !mClosed; }
+ bool RoomForMoreStreams() 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_LAST = 0xB
+ };
+
+ // 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
+ };
+
+ // 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 = 256 * 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
+ };
+
+ 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 *);
+
+ 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
+ virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment) override;
+ nsresult BufferOutput(const char *, uint32_t, uint32_t *);
+ void FlushOutputQueue();
+ uint32_t AmountOfOutputBuffered() { return mOutputQueueUsed - mOutputQueueSent; }
+
+ uint32_t GetServerInitialStreamWindow() { return mServerInitialStreamWindow; }
+
+ bool TryToActivate(Http2Stream *stream);
+ void ConnectPushedStream(Http2Stream *stream);
+ void ConnectSlowConsumer(Http2Stream *stream);
+
+ nsresult ConfirmTLSProfile();
+ 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;
+ bool MaybeReTunnel(nsAHttpTransaction *) override;
+ bool UseH2Deps() { return mUseH2Deps; }
+
+ // overload of nsAHttpTransaction
+ nsresult ReadSegmentsAgain(nsAHttpSegmentReader *, uint32_t, uint32_t *, bool *) override final;
+ nsresult WriteSegmentsAgain(nsAHttpSegmentWriter *, uint32_t , uint32_t *, bool *) override final;
+
+private:
+
+ // 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];
+
+ nsresult ResponseHeadersComplete();
+ uint32_t GetWriteQueueSize();
+ void ChangeDownstreamState(enum internalStateType);
+ void ResetDownstreamState();
+ nsresult ReadyToProcessDataFrame(enum internalStateType);
+ 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 *);
+ nsresult ParsePadding(uint8_t &, uint16_t &);
+
+ void SetWriteCallbacks();
+ void RealignOutputQueue();
+
+ void ProcessPending();
+ nsresult ProcessConnectedPush(Http2Stream *, nsAHttpSegmentWriter *,
+ uint32_t, uint32_t *);
+ nsresult ProcessSlowConsumer(Http2Stream *, nsAHttpSegmentWriter *,
+ uint32_t, uint32_t *);
+
+ nsresult SetInputFrameDataStream(uint32_t);
+ void CreatePriorityNode(uint32_t, uint32_t, uint8_t, const char *);
+ 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
+ nsresult NetworkRead(nsAHttpSegmentWriter *, char *, uint32_t, uint32_t *);
+
+ void Shutdown();
+
+ // 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.
+ 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.
+ // The objects are not ref counted - they get destroyed
+ // by the nsClassHashtable implementation when they are removed from
+ // the transaction hash.
+ nsDataHashtable<nsUint32HashKey, Http2Stream *> mStreamIDHash;
+ nsClassHashtable<nsPtrHashKey<nsAHttpTransaction>,
+ Http2Stream> mStreamTransactionHash;
+
+ nsDeque mReadyForWrite;
+ nsDeque mQueuedStreams;
+ nsDeque mPushesReadyForRead;
+ nsDeque 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 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 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;
+
+ // 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;
+
+private:
+/// connect tunnels
+ void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *);
+ void CreateTunnel(nsHttpTransaction *, nsHttpConnectionInfo *, nsIInterfaceRequestor *);
+ void RegisterTunnel(Http2Stream *);
+ void UnRegisterTunnel(Http2Stream *);
+ uint32_t FindTunnelCount(nsHttpConnectionInfo *);
+ nsDataHashtable<nsCStringHashKey, uint32_t> mTunnelHash;
+};
+
+} // 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..5c562557cd
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -0,0 +1,1472 @@
+/* -*- 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/Telemetry.h"
+#include "nsAlgorithm.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIClassOfService.h"
+#include "nsIPipe.h"
+#include "nsISocketTransport.h"
+#include "nsStandardURL.h"
+#include "prnetdb.h"
+
+namespace mozilla {
+namespace net {
+
+Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
+ Http2Session *session,
+ int32_t priority)
+ : mStreamID(0)
+ , mSession(session)
+ , mSegmentReader(nullptr)
+ , mSegmentWriter(nullptr)
+ , mUpstreamState(GENERATING_HEADERS)
+ , mState(IDLE)
+ , mRequestHeadersDone(0)
+ , mOpenGenerated(0)
+ , mAllHeadersReceived(0)
+ , mQueued(0)
+ , mTransaction(httpTransaction)
+ , mSocketTransport(session->SocketTransport())
+ , 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)
+ , mIsTunnel(false)
+ , mPlainTextTunnel(false)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG3(("Http2Stream::Http2Stream %p", this));
+
+ mServerReceiveWindow = session->GetServerInitialStreamWindow();
+ mClientReceiveWindow = session->PushAllowance();
+
+ mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize);
+
+ static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority,
+ "Lowest Priority should be less than kNormalPriority");
+
+ // values of priority closer to 0 are higher priority for the priority
+ // argument. This value is used as a group, which maps to a
+ // weight that is related to the nsISupportsPriority that we are given.
+ int32_t httpPriority;
+ if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
+ httpPriority = kWorstPriority;
+ } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
+ httpPriority = kBestPriority;
+ } else {
+ httpPriority = kNormalPriority + priority;
+ }
+ MOZ_ASSERT(httpPriority >= 0);
+ SetPriority(static_cast<uint32_t>(httpPriority));
+}
+
+Http2Stream::~Http2Stream()
+{
+ ClearTransactionsBlockedOnTunnel();
+ mStreamID = Http2Session::kDeadStreamID;
+}
+
+// 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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 %x read=%d\n",
+ this, 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)
+ 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.
+ if (mUpstreamState == GENERATING_HEADERS && NS_SUCCEEDED(rv)) {
+ LOG3(("Http2Stream %p ReadSegments forcing OnReadSegment call\n", this));
+ uint32_t wasted = 0;
+ mSegmentReader = reader;
+ OnReadSegment("", 0, &wasted);
+ mSegmentReader = nullptr;
+ }
+
+ // 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(PR_GetCurrentThread() == gSocketThread);
+ 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 %X %d\n", this, rv, *countWritten));
+ }
+ }
+ mSegmentWriter = nullptr;
+ return rv;
+}
+
+nsresult
+Http2Stream::MakeOriginURL(const nsACString &origin, RefPtr<nsStandardURL> &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,
+ RefPtr<nsStandardURL> &url)
+{
+ url = new nsStandardURL();
+ nsresult rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY,
+ scheme.EqualsLiteral("http") ?
+ NS_HTTP_DEFAULT_PORT :
+ NS_HTTPS_DEFAULT_PORT,
+ origin, nullptr, nullptr);
+ return rv;
+}
+
+void
+Http2Stream::CreatePushHashKey(const nsCString &scheme,
+ const nsCString &hostHeader,
+ uint64_t serial,
+ const nsCSubstring &pathInfo,
+ nsCString &outOrigin,
+ nsCString &outKey)
+{
+ nsCString fullOrigin = scheme;
+ fullOrigin.AppendLiteral("://");
+ fullOrigin.Append(hostHeader);
+
+ RefPtr<nsStandardURL> 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("/[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(PR_GetCurrentThread() == gSocketThread);
+ 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;
+ head->GetHeader(nsHttp::Host, authorityHeader);
+
+ nsAutoCString requestURI;
+ head->RequestURI(requestURI);
+ CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"),
+ authorityHeader, 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) {
+ requestContext->GetSpdyPushCache(&cache);
+ }
+
+ 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 && (pushedStream = trans->TakePushedStream())) {
+ 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 %d %d\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;
+ 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}
+
+ nsCString compressedData;
+ nsAutoCString authorityHeader;
+ head->GetHeader(nsHttp::Host, authorityHeader);
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+ if (head->IsConnect()) {
+ MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction());
+ mIsTunnel = true;
+ mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
+
+ // 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);
+ mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
+ method,
+ path,
+ authorityHeader,
+ scheme,
+ head->IsConnect(),
+ compressedData);
+
+ 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();
+ LOG3(("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;
+
+ 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->SetPriority(mPriority);
+ memset(packet + Http2Session::kFrameHeaderBytes, 0, 4);
+ memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1);
+
+ LOG3(("AdjustPushedPriority %p id 0x%X to weight %X\n", this, mPushSource->mStreamID,
+ 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 %x len=%d",
+ mSession, this, 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 %x len=%d",
+ mSession, this, 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;
+ }
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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)
+{
+ aHeadersOut.Truncate();
+ aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
+
+ 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);
+ if (mIsTunnel) {
+ LOG3(("Http2Stream %p Tunnel Response code %d", this, httpResponseCode));
+ if ((httpResponseCode / 100) != 2) {
+ MapStreamToPlainText();
+ }
+ }
+
+ if (httpResponseCode == 101) {
+ // 8.1.1 of h2 disallows 101.. throw PROTOCOL_ERROR on stream
+ LOG3(("Http2Stream::ConvertResponseHeaders %p Error - status == 101\n", this));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ 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.Append("X-Firefox-Spdy: h2");
+ aHeadersOut.Append("\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)
+{
+ aHeadersOut.Truncate();
+ aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
+ 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;
+}
+
+void
+Http2Stream::Close(nsresult reason)
+{
+ 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;
+ if (mIsTunnel) {
+ MapStreamToHttpConnection();
+ ClearTransactionsBlockedOnTunnel();
+ }
+ return;
+}
+
+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 newDependency, uint8_t newWeight,
+ bool exclusive)
+{
+ // undefined what it means when the server sends a priority frame. ignore it.
+ LOG3(("Http2Stream::SetPriorityDependency %p 0x%X received dependency=0x%X "
+ "weight=%u exclusive=%d", this, mStreamID, newDependency, newWeight,
+ exclusive));
+}
+
+void
+Http2Stream::UpdatePriorityDependency()
+{
+ if (!mSession->UseH2Deps()) {
+ return;
+ }
+
+ nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return;
+ }
+
+ // we create 5 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)
+ //
+ // 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
+
+ uint32_t classFlags = trans->ClassOfService();
+
+ if (classFlags & nsIClassOfService::Leader) {
+ mPriorityDependency = Http2Session::kLeaderGroupID;
+ } else if (classFlags & nsIClassOfService::Follower) {
+ mPriorityDependency = Http2Session::kFollowerGroupID;
+ } else if (classFlags & nsIClassOfService::Speculative) {
+ mPriorityDependency = Http2Session::kSpeculativeGroupID;
+ } else if (classFlags & nsIClassOfService::Background) {
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+ } else if (classFlags & nsIClassOfService::Unblocked) {
+ mPriorityDependency = Http2Session::kOtherGroupID;
+ } else {
+ mPriorityDependency = Http2Session::kFollowerGroupID; // unmarked followers
+ }
+
+ LOG3(("Http2Stream::UpdatePriorityDependency %p "
+ "classFlags %X depends on stream 0x%X\n",
+ this, classFlags, mPriorityDependency));
+}
+
+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(PR_GetCurrentThread() == gSocketThread);
+ 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=%ld session=%ld.\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);
+ MOZ_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=%x returning %d data bytes. "
+ "Header is %d Body is %d.",
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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
+
+void
+Http2Stream::ClearTransactionsBlockedOnTunnel()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (!mIsTunnel) {
+ return;
+ }
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
+}
+
+void
+Http2Stream::MapStreamToPlainText()
+{
+ RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
+ MOZ_ASSERT(qiTrans);
+ mPlainTextTunnel = true;
+ qiTrans->ForcePlainText();
+}
+
+void
+Http2Stream::MapStreamToHttpConnection()
+{
+ RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
+ MOZ_ASSERT(qiTrans);
+ qiTrans->MapStreamToHttpConnection(mSocketTransport,
+ mTransaction->ConnectionInfo());
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h
new file mode 100644
index 0000000000..452db5fe0d
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -0,0 +1,346 @@
+/* -*- 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"
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla {
+namespace net {
+
+class nsStandardURL;
+class Http2Session;
+class Http2Decompressor;
+
+class Http2Stream
+ : public nsAHttpSegmentReader
+ , public nsAHttpSegmentWriter
+{
+public:
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+
+ 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);
+
+ uint32_t StreamID() { return mStreamID; }
+ Http2PushedStream *PushSource() { return mPushSource; }
+
+ stateType HTTPState() { return mState; }
+ void SetHTTPState(stateType val) { mState = val; }
+
+ virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *);
+ 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 nsAFlatCString &Origin() const { return mOrigin; }
+ const nsAFlatCString &Host() const { return mHeaderHost; }
+ const nsAFlatCString &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
+ nsresult ConvertResponseHeaders(Http2Decompressor *, nsACString &,
+ nsACString &, int32_t &);
+ nsresult ConvertPushHeaders(Http2Decompressor *, nsACString &, 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; }
+ void SetPriority(uint32_t);
+ void SetPriorityDependency(uint32_t, uint8_t, bool);
+ void UpdatePriorityDependency();
+
+ // 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; }
+
+ virtual ~Http2Stream();
+
+ Http2Session *Session() { return mSession; }
+
+ static nsresult MakeOriginURL(const nsACString &origin,
+ RefPtr<nsStandardURL> &url);
+
+ static nsresult MakeOriginURL(const nsACString &scheme,
+ const nsACString &origin,
+ RefPtr<nsStandardURL> &url);
+
+protected:
+ static void CreatePushHashKey(const nsCString &scheme,
+ const nsCString &hostHeader,
+ uint64_t serial,
+ const nsCSubstring &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();
+ nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment);
+
+private:
+ friend class nsAutoPtr<Http2Stream>;
+
+ nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *);
+ nsresult GenerateOpen();
+
+ void AdjustPushedPriority();
+ void GenerateDataFrameHeader(uint32_t, bool);
+
+ 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 underlying socket transport object is needed to propogate some events
+ nsISocketTransport *mSocketTransport;
+
+ // 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
+ uint32_t mPriorityDependency; // h2 stream id 3 - 0xb
+ uint8_t mPriorityWeight; // h2 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;
+
+/// connect tunnels
+public:
+ bool IsTunnel() { return mIsTunnel; }
+private:
+ void ClearTransactionsBlockedOnTunnel();
+ void MapStreamToPlainText();
+ void MapStreamToHttpConnection();
+
+ bool mIsTunnel;
+ bool mPlainTextTunnel;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Stream_h
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
new file mode 100644
index 0000000000..66252b82f6
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -0,0 +1,3715 @@
+/* -*- 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/HttpBaseChannel.h"
+
+#include "nsHttpHandler.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+
+#include "nsICachingChannel.h"
+#include "nsIDOMDocument.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsISeekableStream.h"
+#include "nsIStorageStream.h"
+#include "nsITimedChannel.h"
+#include "nsIEncodedChannel.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIMutableArray.h"
+#include "nsEscape.h"
+#include "nsStreamListenerWrapper.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsURLHelper.h"
+#include "nsICookieService.h"
+#include "nsIStreamConverterService.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIObserverService.h"
+#include "nsProxyRelease.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsINetworkInterceptController.h"
+#include "mozilla/dom/Performance.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsStreamUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIChannelEventSink.h"
+#include "nsILoadGroupChild.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "LoadInfo.h"
+#include "nsNullPrincipal.h"
+#include "nsISSLSocketControl.h"
+#include "mozilla/Telemetry.h"
+#include "nsIURL.h"
+#include "nsIConsoleService.h"
+#include "mozilla/BinarySearch.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIXULRuntime.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIDOMWindowUtils.h"
+
+#include <algorithm>
+#include "HttpBaseChannel.h"
+
+namespace mozilla {
+namespace net {
+
+HttpBaseChannel::HttpBaseChannel()
+ : mStartPos(UINT64_MAX)
+ , mStatus(NS_OK)
+ , mLoadFlags(LOAD_NORMAL)
+ , mCaps(0)
+ , mClassOfService(0)
+ , mPriority(PRIORITY_NORMAL)
+ , mRedirectionLimit(gHttpHandler->RedirectionLimit())
+ , mApplyConversion(true)
+ , mCanceled(false)
+ , mIsPending(false)
+ , mWasOpened(false)
+ , mRequestObserversCalled(false)
+ , mResponseHeadersModified(false)
+ , mAllowPipelining(true)
+ , mAllowSTS(true)
+ , mThirdPartyFlags(0)
+ , mUploadStreamHasHeaders(false)
+ , mInheritApplicationCache(true)
+ , mChooseApplicationCache(false)
+ , mLoadedFromApplicationCache(false)
+ , mChannelIsForDownload(false)
+ , mTracingEnabled(true)
+ , mTimingEnabled(false)
+ , mAllowSpdy(true)
+ , mAllowAltSvc(true)
+ , mBeConservative(false)
+ , mResponseTimeoutEnabled(true)
+ , mAllRedirectsSameOrigin(true)
+ , mAllRedirectsPassTimingAllowCheck(true)
+ , mResponseCouldBeSynthesized(false)
+ , mBlockAuthPrompt(false)
+ , mAllowStaleCacheContent(false)
+ , mSuspendCount(0)
+ , mInitialRwin(0)
+ , mProxyResolveFlags(0)
+ , mProxyURI(nullptr)
+ , mContentDispositionHint(UINT32_MAX)
+ , mHttpHandler(gHttpHandler)
+ , mReferrerPolicy(REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE)
+ , mRedirectCount(0)
+ , mForcePending(false)
+ , mCorsIncludeCredentials(false)
+ , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS)
+ , mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW)
+ , mFetchCacheMode(nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT)
+ , mOnStartRequestCalled(false)
+ , mOnStopRequestCalled(false)
+ , mAfterOnStartRequestBegun(false)
+ , mTransferSize(0)
+ , mDecodedBodySize(0)
+ , mEncodedBodySize(0)
+ , mContentWindowId(0)
+ , mRequireCORSPreflight(false)
+ , mReportCollector(new ConsoleReportCollector())
+ , mForceMainDocumentChannel(false)
+{
+ LOG(("Creating HttpBaseChannel @%x\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;
+ mRequestContextID.Clear();
+}
+
+HttpBaseChannel::~HttpBaseChannel()
+{
+ LOG(("Destroying HttpBaseChannel @%x\n", this));
+
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+
+ // Make sure we don't leak
+ CleanRedirectCacheChainIfNecessary();
+}
+
+nsresult
+HttpBaseChannel::Init(nsIURI *aURI,
+ uint32_t aCaps,
+ nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI,
+ const nsID& aChannelId)
+{
+ LOG(("HttpBaseChannel::Init [this=%p]\n", this));
+
+ NS_PRECONDITION(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 = false;
+
+ nsresult rv = mURI->SchemeIs("https", &isHTTPS);
+ if (NS_FAILED(rv)) return rv;
+
+ 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;
+
+ LOG(("host=%s port=%d\n", host.get(), port));
+
+ rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) return rv;
+ LOG(("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);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
+ !type.EqualsLiteral("unknown"))
+ mProxyInfo = aProxyInfo;
+
+ 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(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_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 = mIsPending || mForcePending;
+ 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)
+{
+ bool synthesized = false;
+ nsresult rv = GetResponseSynthesized(&synthesized);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If this channel is marked as awaiting a synthesized response,
+ // modifying certain load flags can interfere with the implementation
+ // of the network interception logic. This takes care of a couple
+ // known cases that attempt to mark channels as anonymous due
+ // to cross-origin redirects; since the response is entirely synthesized
+ // this is an unnecessary precaution.
+ // This should be removed when bug 1201683 is fixed.
+ if (synthesized && aLoadFlags != mLoadFlags) {
+ aLoadFlags &= ~LOAD_ANONYMOUS;
+ }
+
+ mLoadFlags = aLoadFlags;
+ mForceMainDocumentChannel = (aLoadFlags & LOAD_DOCUMENT_URI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocshellUserAgentOverride()
+{
+ // This sets the docshell specific user agent override, it will be overwritten
+ // by UserAgentOverrides.jsm if site-specific user agent overrides are set.
+ nsresult rv;
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ if (!loadContext) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return NS_OK;
+ }
+
+ auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
+ nsIDocShell* docshell = pDomWindow->GetDocShell();
+ if (!docshell) {
+ return NS_OK;
+ }
+
+ nsString customUserAgent;
+ docshell->GetCustomUserAgent(customUserAgent);
+ if (customUserAgent.IsEmpty()) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
+ rv = SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), 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 = mOriginalURI;
+ NS_ADDREF(*aOriginalURI);
+ 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 = mURI;
+ NS_ADDREF(*aURI);
+ 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)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+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 || mWasOpened) {
+ 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)
+{
+ 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_FAILED(rv)) {
+ if (!mContentDispositionFilename)
+ return rv;
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+ }
+
+ return NS_GetFilenameFromDisposition(aContentDispositionFilename,
+ header, mURI);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
+{
+ mContentDispositionFilename = new nsString(aContentDispositionFilename);
+ 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;
+
+ *aContentLength = mResponseHead->ContentLength();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentLength(int64_t value)
+{
+ NS_NOTYETIMPLEMENTED("HttpBaseChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::Open(nsIInputStream **aResult)
+{
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
+
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_ImplementChannelOpen(this, aResult);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::Open2(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);
+ return Open(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;
+
+ // 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.
+ nsAutoCString contentType;
+ if (contentTypeArg.IsEmpty()) {
+ method = NS_LITERAL_CSTRING("POST");
+ hasHeaders = true;
+ contentType.SetIsVoid(true);
+ } else {
+ method = NS_LITERAL_CSTRING("PUT");
+ hasHeaders = false;
+ contentType = contentTypeArg;
+ }
+ return ExplicitSetUploadStream(stream, contentType, contentLength,
+ method, hasHeaders);
+ }
+
+ // if stream is null, ExplicitSetUploadStream returns error.
+ // So we need special case for GET method.
+ mUploadStreamHasHeaders = false;
+ mRequestHead.SetMethod(NS_LITERAL_CSTRING("GET")); // revert to GET request
+ mUploadStream = stream;
+ return NS_OK;
+}
+
+namespace {
+
+void
+CopyComplete(void* aClosure, nsresult aStatus) {
+ // Called on the STS thread by NS_AsyncCopy
+ auto channel = static_cast<HttpBaseChannel*>(aClosure);
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
+ channel, &HttpBaseChannel::EnsureUploadStreamIsCloneableComplete, aStatus);
+ NS_DispatchToMainThread(runnable.forget());
+}
+
+} // 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);
+
+ // If the CloneUploadStream() will succeed, then synchronously invoke
+ // the callback to indicate we're already cloneable.
+ if (!mUploadStream || 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, 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::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(nsIInputStream** aClonedStream)
+{
+ 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);
+
+ 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);
+
+ if (aContentLength < 0 && !aStreamHasHeaders) {
+ nsresult rv = aStream->Available(reinterpret_cast<uint64_t*>(&aContentLength));
+ if (NS_FAILED(rv) || aContentLength < 0) {
+ NS_ERROR("unable to determine content length");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsresult rv = SetRequestMethod(aMethod);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aStreamHasHeaders) {
+ // SetRequestHeader propagates headers to chrome if HttpChannelChild
+ nsAutoCString contentLengthStr;
+ contentLengthStr.AppendInt(aContentLength);
+ SetRequestHeader(NS_LITERAL_CSTRING("Content-Length"), contentLengthStr,
+ false);
+ if (!aContentType.IsVoid()) {
+ if (aContentType.IsEmpty()) {
+ SetEmptyRequestHeader(NS_LITERAL_CSTRING("Content-Type"));
+ } else {
+ SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), aContentType,
+ false);
+ }
+ }
+ }
+
+ mUploadStreamHasHeaders = aStreamHasHeaders;
+ mUploadStream = aStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStreamHasHeaders(bool *hasHeaders)
+{
+ NS_ENSURE_ARG(hasHeaders);
+
+ *hasHeaders = mUploadStreamHasHeaders;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIEncodedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApplyConversion(bool *value)
+{
+ *value = mApplyConversion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetApplyConversion(bool value)
+{
+ LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this, value));
+ mApplyConversion = value;
+ return NS_OK;
+}
+
+nsresult
+HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
+ nsIStreamListener** aNewNextListener)
+{
+ return DoApplyContentConversions(aNextListener,
+ aNewNextListener,
+ mListenerContext);
+}
+
+// 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() {}
+ nsCOMPtr<nsIStreamListener> mNext;
+ HttpBaseChannel *mChannel;
+
+public:
+ InterceptFailedOnStop(nsIStreamListener *arg, HttpBaseChannel *chan)
+ : mNext(arg)
+ , mChannel(chan) {}
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) override
+ {
+ return mNext->OnStartRequest(aRequest, aContext);
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatusCode) override
+ {
+ if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
+ LOG(("HttpBaseChannel::InterceptFailedOnStop %p seting status %x", mChannel, aStatusCode));
+ mChannel->mStatus = aStatusCode;
+ }
+ return mNext->OnStopRequest(aRequest, aContext, aStatusCode);
+ }
+
+ NS_IMETHOD OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
+ nsIInputStream *aInputStream, uint64_t aOffset,
+ uint32_t aCount) override
+ {
+ return mNext->OnDataAvailable(aRequest, aContext, 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 (!mApplyConversion) {
+ LOG(("not applying conversion per mApplyConversion\n"));
+ return NS_OK;
+ }
+
+ if (!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;
+ }
+
+ bool isHTTPS = false;
+ mURI->SchemeIs("https", &isHTTPS);
+ if (gHttpHandler->IsAcceptableEncoding(val, isHTTPS)) {
+ 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 (gHttpHandler->IsTelemetryEnabled()) {
+ int mode = 0;
+ if (from.Equals("gzip") || from.Equals("x-gzip")) {
+ mode = 1;
+ } else if (from.Equals("deflate") || from.Equals("x-deflate")) {
+ mode = 2;
+ } else if (from.Equals("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;
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, encoding);
+ if (encoding.IsEmpty()) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+ nsContentEncodings* enumerator = new nsContentEncodings(this,
+ encoding.get());
+ NS_ADDREF(*aEncodings = enumerator);
+ 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::~nsContentEncodings()
+{
+}
+
+//-----------------------------------------------------------------------------
+// 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(NS_LITERAL_CSTRING("gzip"), start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_GZIP);
+ haveType = true;
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("compress"), start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("deflate"), start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_ZIP);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("br"), 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)
+
+//-----------------------------------------------------------------------------
+// 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(nsACString& aChannelId)
+{
+ char id[NSID_LENGTH];
+ mChannelId.ToProvidedString(id);
+ aChannelId.AssignASCII(id);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelId(const nsACString& aChannelId)
+{
+ nsID newId;
+ nsAutoCString idStr(aChannelId);
+ if (newId.Parse(idStr.get())) {
+ mChannelId = newId;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+ if (!mContentWindowId) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ GetCallback(loadContext);
+ if (loadContext) {
+ nsCOMPtr<mozIDOMWindowProxy> topWindow;
+ loadContext->GetTopWindow(getter_AddRefs(topWindow));
+ nsCOMPtr<nsIDOMWindowUtils> windowUtils = do_GetInterface(topWindow);
+ if (windowUtils) {
+ windowUtils->GetCurrentInnerWindowID(&mContentWindowId);
+ }
+ }
+ }
+ *aWindowId = mContentWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+ mContentWindowId = aWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransferSize(uint64_t *aTransferSize)
+{
+ *aTransferSize = mTransferSize;
+ 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::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::GetNetworkInterfaceId(nsACString& aNetworkInterfaceId)
+{
+ aNetworkInterfaceId = mNetworkInterfaceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mNetworkInterfaceId = aNetworkInterfaceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReferrer(nsIURI **referrer)
+{
+ NS_ENSURE_ARG_POINTER(referrer);
+ *referrer = mReferrer;
+ NS_IF_ADDREF(*referrer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrer(nsIURI *referrer)
+{
+ return SetReferrerWithPolicy(referrer, REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReferrerPolicy(uint32_t *referrerPolicy)
+{
+ NS_ENSURE_ARG_POINTER(referrerPolicy);
+ *referrerPolicy = mReferrerPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrerWithPolicy(nsIURI *referrer,
+ uint32_t referrerPolicy)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ // clear existing referrer, if any
+ mReferrer = nullptr;
+ nsresult rv = mRequestHead.ClearHeader(nsHttp::Referer);
+ if(NS_FAILED(rv)) {
+ return rv;
+ }
+ mReferrerPolicy = REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE;
+
+ if (!referrer) {
+ return NS_OK;
+ }
+
+ // Don't send referrer at all when the meta referrer setting is "no-referrer"
+ if (referrerPolicy == REFERRER_POLICY_NO_REFERRER) {
+ mReferrerPolicy = REFERRER_POLICY_NO_REFERRER;
+ return NS_OK;
+ }
+
+ // 0: never send referer
+ // 1: send referer for direct user action
+ // 2: always send referer
+ uint32_t userReferrerLevel = gHttpHandler->ReferrerLevel();
+
+ // false: use real referrer
+ // true: spoof with URI of the current request
+ bool userSpoofReferrerSource = gHttpHandler->SpoofReferrerSource();
+
+ // 0: full URI
+ // 1: scheme+host+port+path
+ // 2: scheme+host+port
+ int userReferrerTrimmingPolicy = gHttpHandler->ReferrerTrimmingPolicy();
+
+ // 0: send referer no matter what
+ // 1: send referer ONLY when base domains match
+ // 2: send referer ONLY when hosts match
+ int userReferrerXOriginPolicy = gHttpHandler->ReferrerXOriginPolicy();
+
+ // check referrer blocking pref
+ uint32_t referrerLevel;
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ referrerLevel = 1; // user action
+ } else {
+ referrerLevel = 2; // inline content
+ }
+ if (userReferrerLevel < referrerLevel) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> referrerGrip;
+ bool match;
+
+ //
+ // Strip off "wyciwyg://123/" from wyciwyg referrers.
+ //
+ // XXX this really belongs elsewhere since wyciwyg URLs aren't part of necko.
+ // perhaps some sort of generic nsINestedURI could be used. then, if an URI
+ // fails the whitelist test, then we could check for an inner URI and try
+ // that instead. though, that might be too automatic.
+ //
+ rv = referrer->SchemeIs("wyciwyg", &match);
+ if (NS_FAILED(rv)) return rv;
+ if (match) {
+ nsAutoCString path;
+ rv = referrer->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t pathLength = path.Length();
+ if (pathLength <= 2) return NS_ERROR_FAILURE;
+
+ // Path is of the form "//123/http://foo/bar", with a variable number of
+ // digits. To figure out where the "real" URL starts, search path for a
+ // '/', starting at the third character.
+ int32_t slashIndex = path.FindChar('/', 2);
+ if (slashIndex == kNotFound) return NS_ERROR_FAILURE;
+
+ // Get charset of the original URI so we can pass it to our fixed up URI.
+ nsAutoCString charset;
+ referrer->GetOriginCharset(charset);
+
+ // Replace |referrer| with a URI without wyciwyg://123/.
+ rv = NS_NewURI(getter_AddRefs(referrerGrip),
+ Substring(path, slashIndex + 1, pathLength - slashIndex - 1),
+ charset.get());
+ if (NS_FAILED(rv)) return rv;
+
+ referrer = referrerGrip.get();
+ }
+
+ //
+ // block referrer if not on our white list...
+ //
+ static const char *const referrerWhiteList[] = {
+ "http",
+ "https",
+ "ftp",
+ nullptr
+ };
+ match = false;
+ const char *const *scheme = referrerWhiteList;
+ for (; *scheme && !match; ++scheme) {
+ rv = referrer->SchemeIs(*scheme, &match);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (!match) return NS_OK; // kick out....
+
+ //
+ // Handle secure referrals.
+ //
+ // Support referrals from a secure server if this is a secure site
+ // and (optionally) if the host names are the same.
+ //
+ rv = referrer->SchemeIs("https", &match);
+ if (NS_FAILED(rv)) return rv;
+
+ if (match) {
+ rv = mURI->SchemeIs("https", &match);
+ if (NS_FAILED(rv)) return rv;
+
+ // It's ok to send referrer for https-to-http scenarios if the referrer
+ // policy is "unsafe-url", "origin", or "origin-when-cross-origin".
+ if (referrerPolicy != REFERRER_POLICY_UNSAFE_URL &&
+ referrerPolicy != REFERRER_POLICY_ORIGIN_WHEN_XORIGIN &&
+ referrerPolicy != REFERRER_POLICY_ORIGIN) {
+
+ // in other referrer policies, https->http is not allowed...
+ if (!match) return NS_OK;
+ }
+ }
+
+ // for cross-origin-based referrer changes (not just host-based), figure out
+ // if the referrer is being sent cross-origin.
+ nsCOMPtr<nsIURI> triggeringURI;
+ bool isCrossOrigin = true;
+ if (mLoadInfo) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = mLoadInfo->TriggeringPrincipal();
+ if (triggeringPrincipal) {
+ triggeringPrincipal->GetURI(getter_AddRefs(triggeringURI));
+ }
+ }
+ if (triggeringURI) {
+ if (LOG_ENABLED()) {
+ nsAutoCString triggeringURISpec;
+ rv = triggeringURI->GetAsciiSpec(triggeringURISpec);
+ if (!NS_FAILED(rv)) {
+ LOG(("triggeringURI=%s\n", triggeringURISpec.get()));
+ }
+ }
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ rv = ssm->CheckSameOriginURI(triggeringURI, mURI, false);
+ isCrossOrigin = NS_FAILED(rv);
+ } else {
+ LOG(("no triggering principal available via loadInfo, assuming load is cross-origin"));
+ }
+
+ // Don't send referrer when the request is cross-origin and policy is "same-origin".
+ if (isCrossOrigin && referrerPolicy == REFERRER_POLICY_SAME_ORIGIN) {
+ mReferrerPolicy = REFERRER_POLICY_SAME_ORIGIN;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> clone;
+ //
+ // we need to clone the referrer, so we can:
+ // (1) modify it
+ // (2) keep a reference to it after returning from this function
+ //
+ // Use CloneIgnoringRef to strip away any fragment per RFC 2616 section 14.36
+ // and Referrer Policy section 6.3.5.
+ rv = referrer->CloneIgnoringRef(getter_AddRefs(clone));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString currentHost;
+ nsAutoCString referrerHost;
+
+ rv = mURI->GetAsciiHost(currentHost);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = clone->GetAsciiHost(referrerHost);
+ if (NS_FAILED(rv)) return rv;
+
+ // check policy for sending ref only when hosts match
+ if (userReferrerXOriginPolicy == 2 && !currentHost.Equals(referrerHost))
+ return NS_OK;
+
+ if (userReferrerXOriginPolicy == 1) {
+ nsAutoCString currentDomain = currentHost;
+ nsAutoCString referrerDomain = referrerHost;
+ uint32_t extraDomains = 0;
+ nsCOMPtr<nsIEffectiveTLDService> eTLDService = do_GetService(
+ NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (eTLDService) {
+ rv = eTLDService->GetBaseDomain(mURI, extraDomains, currentDomain);
+ if (NS_FAILED(rv)) return rv;
+ rv = eTLDService->GetBaseDomain(clone, extraDomains, referrerDomain);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // check policy for sending only when effective top level domain matches.
+ // this falls back on using host if eTLDService does not work
+ if (!currentDomain.Equals(referrerDomain))
+ return NS_OK;
+ }
+
+ // send spoofed referrer if desired
+ if (userSpoofReferrerSource) {
+ nsCOMPtr<nsIURI> mURIclone;
+ rv = mURI->CloneIgnoringRef(getter_AddRefs(mURIclone));
+ if (NS_FAILED(rv)) return rv;
+ clone = mURIclone;
+ currentHost = referrerHost;
+ }
+
+ // strip away any userpass; we don't want to be giving out passwords ;-)
+ // This is required by Referrer Policy stripping algorithm.
+ rv = clone->SetUserPass(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString spec;
+
+ // Apply the user cross-origin trimming policy if it's more
+ // restrictive than the general one.
+ if (isCrossOrigin) {
+ int userReferrerXOriginTrimmingPolicy =
+ gHttpHandler->ReferrerXOriginTrimmingPolicy();
+ userReferrerTrimmingPolicy =
+ std::max(userReferrerTrimmingPolicy, userReferrerXOriginTrimmingPolicy);
+ }
+
+ // site-specified referrer trimming may affect the trim level
+ // "unsafe-url" behaves like "origin" (send referrer in the same situations) but
+ // "unsafe-url" sends the whole referrer and origin removes the path.
+ // "origin-when-cross-origin" trims the referrer only when the request is
+ // cross-origin.
+ // "Strict" request from https->http case was bailed out, so here:
+ // "strict-origin" behaves the same as "origin".
+ // "strict-origin-when-cross-origin" behaves the same as "origin-when-cross-origin"
+ if (referrerPolicy == REFERRER_POLICY_ORIGIN ||
+ referrerPolicy == REFERRER_POLICY_STRICT_ORIGIN ||
+ (isCrossOrigin && (referrerPolicy == REFERRER_POLICY_ORIGIN_WHEN_XORIGIN ||
+ referrerPolicy == REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN))) {
+ // We can override the user trimming preference because "origin"
+ // (network.http.referer.trimmingPolicy = 2) is the strictest
+ // trimming policy that users can specify.
+ userReferrerTrimmingPolicy = 2;
+ }
+
+ // check how much referer to send
+ if (userReferrerTrimmingPolicy) {
+ // All output strings start with: scheme+host+port
+ // We want the IDN-normalized PrePath. That's not something currently
+ // available and there doesn't yet seem to be justification for adding it to
+ // the interfaces, so just build it up ourselves from scheme+AsciiHostPort
+ nsAutoCString scheme, asciiHostPort;
+ rv = clone->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+ spec = scheme;
+ spec.AppendLiteral("://");
+ // Note we explicitly cleared UserPass above, so do not need to build it.
+ rv = clone->GetAsciiHostPort(asciiHostPort);
+ if (NS_FAILED(rv)) return rv;
+ spec.Append(asciiHostPort);
+
+ switch (userReferrerTrimmingPolicy) {
+ case 1: { // scheme+host+port+path
+ nsCOMPtr<nsIURL> url(do_QueryInterface(clone));
+ if (url) {
+ nsAutoCString path;
+ rv = url->GetFilePath(path);
+ if (NS_FAILED(rv)) return rv;
+ spec.Append(path);
+ rv = url->SetQuery(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ rv = url->SetRef(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ break;
+ }
+ // No URL, so fall through to truncating the path and any query/ref off
+ // as well.
+ }
+ MOZ_FALLTHROUGH;
+ default: // (Pref limited to [0,2] enforced by clamp, MOZ_CRASH overkill.)
+ case 2: // scheme+host+port+/
+ spec.AppendLiteral("/");
+ // This nukes any query/ref present as well in the case of nsStandardURL
+ rv = clone->SetPath(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ break;
+ }
+ } else {
+ // use the full URI
+ rv = clone->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // finally, remember the referrer URI and set the Referer header.
+ rv = SetRequestHeader(NS_LITERAL_CSTRING("Referer"), spec, false);
+ if (NS_FAILED(rv)) return rv;
+
+ mReferrer = clone;
+ mReferrerPolicy = referrerPolicy;
+ return NS_OK;
+}
+
+// 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;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get());
+ if (!atom) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mRequestHead.SetHeader(atom, flatValue, aMerge);
+}
+
+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;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get());
+ if (!atom) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mRequestHead.SetEmptyHeader(atom);
+}
+
+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;
+
+ mResponseHeadersModified = true;
+
+ return mResponseHead->SetHeader(atom, 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 = mAllowPipelining;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowPipelining(bool value)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mAllowPipelining = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSTS(bool *value)
+{
+ NS_ENSURE_ARG_POINTER(value);
+ *value = mAllowSTS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSTS(bool value)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mAllowSTS = 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(mResponseCouldBeSynthesized,
+ "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 (!mResponseCouldBeSynthesized) {
+ 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)
+{
+ // 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(mOnStartRequestCalled, NS_ERROR_NOT_AVAILABLE);
+
+ mAPIRedirectToURI = targetURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestContextID(nsID *aRCID)
+{
+ NS_ENSURE_ARG_POINTER(aRCID);
+ *aRCID = mRequestContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestContextID(const nsID aRCID)
+{
+ mRequestContextID = aRCID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = mForceMainDocumentChannel || (mLoadFlags & LOAD_DOCUMENT_URI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsMainDocumentChannel(bool aValue)
+{
+ mForceMainDocumentChannel = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion)
+{
+ nsresult rv;
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(mSecurityInfo, &rv);
+ nsAutoCString protocol;
+ if (NS_SUCCEEDED(rv) && ssl &&
+ NS_SUCCEEDED(ssl->GetNegotiatedNPN(protocol)) &&
+ !protocol.IsEmpty()) {
+ // The negotiated protocol was not empty so we can use it.
+ aProtocolVersion = protocol;
+ return NS_OK;
+ }
+
+ if (mResponseHead) {
+ uint32_t version = mResponseHead->Version();
+ aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTopWindowURI(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 = do_GetService(THIRDPARTYUTIL_CONTRACTID);
+ if (!util) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = util->GetTopWindowForChannel(this, 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)
+{
+ nsHttpVersion version = mRequestHead.Version();
+
+ if (major) { *major = version / 10; }
+ if (minor) { *minor = 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;
+ }
+
+ nsHttpVersion version = mResponseHead->Version();
+
+ if (major) { *major = version / 10; }
+ if (minor) { *minor = version % 10; }
+
+ return NS_OK;
+}
+
+void
+HttpBaseChannel::NotifySetCookie(char const *aCookie)
+{
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ nsAutoString cookie;
+ CopyASCIItoUTF16(aCookie, cookie);
+ obs->NotifyObservers(static_cast<nsIChannel*>(this),
+ "http-on-response-set-cookie",
+ cookie.get());
+ }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCookie(const char *aCookieHeader)
+{
+ if (mLoadFlags & LOAD_ANONYMOUS)
+ return NS_OK;
+
+ // empty header isn't an error
+ if (!(aCookieHeader && *aCookieHeader))
+ return NS_OK;
+
+ nsICookieService *cs = gHttpHandler->GetCookieService();
+ NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
+
+ nsAutoCString date;
+ mResponseHead->GetHeader(nsHttp::Date, date);
+ nsresult rv =
+ cs->SetCookieStringFromHttp(mURI, nullptr, nullptr, aCookieHeader,
+ date.get(), this);
+ if (NS_SUCCEEDED(rv)) {
+ NotifySetCookie(aCookieHeader);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThirdPartyFlags(uint32_t *aFlags)
+{
+ *aFlags = mThirdPartyFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mThirdPartyFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetForceAllowThirdPartyCookie(bool *aForce)
+{
+ *aForce = !!(mThirdPartyFlags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ if (aForce)
+ mThirdPartyFlags |= nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
+ else
+ mThirdPartyFlags &= ~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 = mChannelIsForDownload;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload)
+{
+ mChannelIsForDownload = aChannelIsForDownload;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString> *cacheKeys)
+{
+ mRedirectedCachekeys = cacheKeys;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalAddress(nsACString& addr)
+{
+ if (mSelfAddr.raw.family == PR_AF_UNSPEC)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetCapacity(kIPv6CStrBufSize);
+ NetAddrToString(&mSelfAddr, addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::TakeAllSecurityMessages(
+ nsCOMArray<nsISecurityConsoleMessage> &aMessages)
+{
+ aMessages.Clear();
+ aMessages.SwapElements(mSecurityConsoleMessages);
+ 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)
+{
+ nsresult rv;
+ nsCOMPtr<nsISecurityConsoleMessage> message =
+ do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ message->SetTag(aMessageTag);
+ message->SetCategory(aMessageCategory);
+ mSecurityConsoleMessages.AppendElement(message);
+
+ nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (!console) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ GetLoadInfo(getter_AddRefs(loadInfo));
+ if (!loadInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t innerWindowID = loadInfo->GetInnerWindowID();
+
+ nsXPIDLString errorText;
+ rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eSECURITY_PROPERTIES,
+ NS_ConvertUTF16toUTF8(aMessageTag).get(),
+ errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ if (mURI) {
+ spec = mURI->GetSpecOrDefault();
+ }
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->InitWithWindowID(errorText, NS_ConvertUTF8toUTF16(spec),
+ EmptyString(), 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.SetCapacity(kIPv6CStrBufSize);
+ NetAddrToString(&mPeerAddr, 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::GetAllowSpdy(bool *aAllowSpdy)
+{
+ NS_ENSURE_ARG_POINTER(aAllowSpdy);
+
+ *aAllowSpdy = mAllowSpdy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy)
+{
+ mAllowSpdy = aAllowSpdy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowAltSvc(bool *aAllowAltSvc)
+{
+ NS_ENSURE_ARG_POINTER(aAllowAltSvc);
+
+ *aAllowAltSvc = mAllowAltSvc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc)
+{
+ mAllowAltSvc = aAllowAltSvc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBeConservative(bool *aBeConservative)
+{
+ NS_ENSURE_ARG_POINTER(aBeConservative);
+
+ *aBeConservative = mBeConservative;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBeConservative(bool aBeConservative)
+{
+ mBeConservative = aBeConservative;
+ 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 = mResponseTimeoutEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable)
+{
+ mResponseTimeoutEnabled = 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)
+{
+ mForcePending = aForcePending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ uint32_t lastMod;
+ mResponseHead->GetLastModifiedValue(&lastMod);
+ *lastModifiedTime = lastMod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCorsIncludeCredentials(bool* aInclude)
+{
+ *aInclude = mCorsIncludeCredentials;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude)
+{
+ mCorsIncludeCredentials = 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;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode)
+{
+ NS_ENSURE_ARG_POINTER(aFetchCacheMode);
+
+ // If the fetch cache mode is overriden, then use it directly.
+ if (mFetchCacheMode != nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT) {
+ *aFetchCacheMode = mFetchCacheMode;
+ return NS_OK;
+ }
+
+ // Otherwise try to guess an appropriate cache mode from the load flags.
+ if (mLoadFlags & (INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
+ } else if (mLoadFlags & LOAD_BYPASS_CACHE) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
+ } else if (mLoadFlags & VALIDATE_ALWAYS) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
+ } else if (mLoadFlags & (LOAD_FROM_CACHE | nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
+ } else if (mLoadFlags & LOAD_FROM_CACHE) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
+ } else {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+ MOZ_ASSERT(mFetchCacheMode == nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT,
+ "SetFetchCacheMode() should only be called once per channel");
+
+ mFetchCacheMode = aFetchCacheMode;
+
+ // Now, set the load flags that implement each cache mode.
+ switch (mFetchCacheMode) {
+ 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.
+ 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.
+ mLoadFlags |= LOAD_BYPASS_CACHE;
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
+ // no-cache means always validate what's in the cache.
+ mLoadFlags |= VALIDATE_ALWAYS;
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
+ // force-cache means don't validate unless if the response would vary.
+ mLoadFlags |= LOAD_FROM_CACHE;
+ 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.
+ mLoadFlags |= LOAD_FROM_CACHE | nsICachingChannel::LOAD_ONLY_FROM_CACHE;
+ break;
+ }
+
+ 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;
+}
+
+mozilla::net::nsHttpChannel*
+HttpBaseChannel::QueryHttpChannelImpl(void)
+{
+ return nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+ 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();
+ mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod);
+ 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);
+}
+
+void
+HttpBaseChannel::FlushConsoleReports(nsIDocument* aDocument,
+ ReportAction aAction)
+{
+ mReportCollector->FlushConsoleReports(aDocument, aAction);
+}
+
+void
+HttpBaseChannel::FlushConsoleReports(nsIConsoleReportCollector* aCollector)
+{
+ mReportCollector->FlushConsoleReports(aCollector);
+}
+
+void
+HttpBaseChannel::FlushReportsByWindowId(uint64_t aWindowId,
+ ReportAction aAction)
+{
+ mReportCollector->FlushReportsByWindowId(aWindowId, aAction);
+}
+
+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 mForceMainDocumentChannel;
+}
+
+bool
+HttpBaseChannel::BypassServiceWorker() const
+{
+ return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
+}
+
+bool
+HttpBaseChannel::ShouldIntercept(nsIURI* aURI)
+{
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+ bool shouldIntercept = false;
+ if (controller && !BypassServiceWorker() && mLoadInfo) {
+ nsresult rv = controller->ShouldPrepareForIntercept(aURI ? aURI : mURI.get(),
+ nsContentUtils::IsNonSubresourceRequest(this),
+ &shouldIntercept);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+ return shouldIntercept;
+}
+
+#ifdef DEBUG
+void HttpBaseChannel::AssertPrivateBrowsingId()
+{
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ // For addons it's possible that mLoadInfo is null.
+ if (!mLoadInfo) {
+ return;
+ }
+
+ 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 (nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal()) &&
+ mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return;
+ }
+
+ DocShellOriginAttributes docShellAttrs;
+ loadContext->GetOriginAttributes(docShellAttrs);
+ MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId == docShellAttrs.mPrivateBrowsingId,
+ "PrivateBrowsingId values are not the same between LoadInfo and LoadContext.");
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITraceableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNewListener(nsIStreamListener *aListener, nsIStreamListener **_retval)
+{
+ LOG(("HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
+ this, mListener.get(), aListener));
+
+ if (!mTracingEnabled)
+ return NS_ERROR_FAILURE;
+
+ NS_ENSURE_STATE(mListener);
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);
+
+ wrapper.forget(_retval);
+ mListener = aListener;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel helpers
+//-----------------------------------------------------------------------------
+
+void
+HttpBaseChannel::ReleaseListeners()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+ mCompressListener = nullptr;
+}
+
+void
+HttpBaseChannel::DoNotifyListener()
+{
+ if (mListener) {
+ MOZ_ASSERT(!mOnStartRequestCalled,
+ "We should not call OnStartRequest twice");
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ listener->OnStartRequest(this, mListenerContext);
+
+ mOnStartRequestCalled = true;
+ }
+
+ // Make sure mIsPending 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.
+ mIsPending = false;
+
+ if (mListener) {
+ MOZ_ASSERT(!mOnStopRequestCalled,
+ "We should not call OnStopRequest twice");
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ listener->OnStopRequest(this, mListenerContext, mStatus);
+
+ mOnStopRequestCalled = true;
+ }
+
+ // 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() && mLoadInfo) {
+ nsCOMPtr<nsIDOMDocument> dommyDoc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(dommyDoc));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(dommyDoc);
+ FlushConsoleReports(doc);
+ }
+}
+
+void
+HttpBaseChannel::AddCookiesToRequest()
+{
+ if (mLoadFlags & LOAD_ANONYMOUS) {
+ return;
+ }
+
+ bool useCookieService =
+ (XRE_IsParentProcess());
+ nsXPIDLCString cookie;
+ if (useCookieService) {
+ nsICookieService *cs = gHttpHandler->GetCookieService();
+ if (cs) {
+ cs->GetCookieStringFromHttp(mURI,
+ nullptr,
+ this, getter_Copies(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);
+}
+
+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;
+}
+
+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 SetupReplacementChannelHeaderVisitor final : public nsIHttpHeaderVisitor
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit SetupReplacementChannelHeaderVisitor(nsIHttpChannel *aChannel)
+ : mChannel(aChannel)
+ {
+ }
+
+ NS_IMETHOD VisitHeader(const nsACString& aHeader,
+ const nsACString& aValue) override
+ {
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
+ mChannel->SetRequestHeader(aHeader, aValue, false);
+ }
+ return NS_OK;
+ }
+private:
+ ~SetupReplacementChannelHeaderVisitor()
+ {
+ }
+
+ nsCOMPtr<nsIHttpChannel> mChannel;
+};
+
+NS_IMPL_ISUPPORTS(SetupReplacementChannelHeaderVisitor, nsIHttpHeaderVisitor)
+
+nsresult
+HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI,
+ nsIChannel *newChannel,
+ bool preserveMethod,
+ uint32_t redirectFlags)
+{
+ LOG(("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, newChannel, preserveMethod));
+
+ uint32_t newLoadFlags = mLoadFlags | 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.
+ bool usingSSL = false;
+ nsresult rv = mURI->SchemeIs("https", &usingSSL);
+ if (NS_SUCCEEDED(rv) && usingSSL)
+ newLoadFlags &= ~INHIBIT_PERSISTENT_CACHING;
+
+ // Do not pass along LOAD_CHECK_OFFLINE_CACHE
+ newLoadFlags &= ~nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ newChannel->SetLoadFlags(newLoadFlags);
+
+ // Try to preserve the privacy bit if it has been overridden
+ if (mPrivateBrowsingOverriden) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(mPrivateBrowsing);
+ }
+ }
+
+ // make a copy of the loadinfo, append to the redirectchain
+ // and set it on the new channel
+ if (mLoadInfo) {
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->Clone();
+
+ nsContentPolicyType contentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+ if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
+ contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
+ nsCOMPtr<nsIPrincipal> nullPrincipalToInherit = nsNullPrincipal::Create();
+ newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
+ }
+
+ // re-compute the origin attributes of the loadInfo if it's top-level load.
+ bool isTopLevelDoc =
+ newLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT;
+
+ if (isTopLevelDoc) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ DocShellOriginAttributes docShellAttrs;
+ if (loadContext) {
+ loadContext->GetOriginAttributes(docShellAttrs);
+ }
+ MOZ_ASSERT(docShellAttrs.mFirstPartyDomain.IsEmpty(),
+ "top-level docshell shouldn't have firstPartyDomain attribute.");
+
+ NeckoOriginAttributes attrs = newLoadInfo->GetOriginAttributes();
+
+ MOZ_ASSERT(docShellAttrs.mAppId == attrs.mAppId,
+ "docshell and necko should have the same appId attribute.");
+ MOZ_ASSERT(docShellAttrs.mUserContextId == attrs.mUserContextId,
+ "docshell and necko should have the same userContextId attribute.");
+ MOZ_ASSERT(docShellAttrs.mInIsolatedMozBrowser == attrs.mInIsolatedMozBrowser,
+ "docshell and necko should have the same inIsolatedMozBrowser attribute.");
+ MOZ_ASSERT(docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
+ "docshell and necko should have the same privateBrowsingId attribute.");
+
+ attrs.InheritFromDocShellToNecko(docShellAttrs, true, newURI);
+ newLoadInfo->SetOriginAttributes(attrs);
+ }
+
+ bool isInternalRedirect =
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+ newLoadInfo->AppendRedirectedPrincipal(GetURIPrincipal(), isInternalRedirect);
+ newChannel->SetLoadInfo(newLoadInfo);
+ }
+ else {
+ // the newChannel was created with a dummy loadInfo, we should clear
+ // it in case the original channel does not have a loadInfo
+ newChannel->SetLoadInfo(nullptr);
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel)
+ return NS_OK; // no other options to set
+
+ // Preserve the CORS preflight information.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
+ if (mRequireCORSPreflight && httpInternal) {
+ httpInternal->SetCorsPreflightParameters(mUnsafeHeaders);
+ }
+
+ if (preserveMethod) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel =
+ do_QueryInterface(httpChannel);
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 =
+ do_QueryInterface(httpChannel);
+ if (mUploadStream && (uploadChannel2 || uploadChannel)) {
+ // rewind upload stream
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+
+ // replicate original call to SetUploadStream...
+ if (uploadChannel2) {
+ nsAutoCString ctype;
+ // 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".
+ nsresult ctypeOK = mRequestHead.GetHeader(nsHttp::Content_Type, ctype);
+ if (NS_FAILED(ctypeOK)) {
+ ctype.SetIsVoid(true);
+ }
+ nsAutoCString clen;
+ mRequestHead.GetHeader(nsHttp::Content_Length, clen);
+ nsAutoCString method;
+ mRequestHead.Method(method);
+ int64_t len = clen.IsEmpty() ? -1 : nsCRT::atoll(clen.get());
+ uploadChannel2->ExplicitSetUploadStream(
+ mUploadStream, ctype, len,
+ method,
+ mUploadStreamHasHeaders);
+ } else {
+ if (mUploadStreamHasHeaders) {
+ uploadChannel->SetUploadStream(mUploadStream, EmptyCString(),
+ -1);
+ } else {
+ nsAutoCString ctype;
+ if (NS_FAILED(mRequestHead.GetHeader(nsHttp::Content_Type, ctype))) {
+ ctype = NS_LITERAL_CSTRING("application/octet-stream");
+ }
+ nsAutoCString clen;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Content_Length, clen))
+ &&
+ !clen.IsEmpty()) {
+ uploadChannel->SetUploadStream(mUploadStream,
+ ctype,
+ nsCRT::atoll(clen.get()));
+ }
+ }
+ }
+ }
+ // 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);
+ httpChannel->SetRequestMethod(method);
+ }
+ // convey the referrer if one was used for this channel to the next one
+ if (mReferrer)
+ httpChannel->SetReferrerWithPolicy(mReferrer, mReferrerPolicy);
+ // convey the mAllowPipelining and mAllowSTS flags
+ httpChannel->SetAllowPipelining(mAllowPipelining);
+ httpChannel->SetAllowSTS(mAllowSTS);
+ // convey the new redirection limit
+ // make sure we don't underflow
+ uint32_t redirectionLimit = mRedirectionLimit
+ ? mRedirectionLimit - 1
+ : 0;
+ httpChannel->SetRedirectionLimit(redirectionLimit);
+
+ // convey the Accept header value
+ {
+ nsAutoCString oldAcceptValue;
+ nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue);
+ if (NS_SUCCEEDED(hasHeader)) {
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ oldAcceptValue,
+ false);
+ }
+ }
+
+ // share the request context - see bug 1236650
+ httpChannel->SetRequestContextID(mRequestContextID);
+
+ if (httpInternal) {
+ // Convey third party cookie, conservative, and spdy flags.
+ httpInternal->SetThirdPartyFlags(mThirdPartyFlags);
+ httpInternal->SetAllowSpdy(mAllowSpdy);
+ httpInternal->SetAllowAltSvc(mAllowAltSvc);
+ httpInternal->SetBeConservative(mBeConservative);
+
+ RefPtr<nsHttpChannel> realChannel;
+ CallQueryInterface(newChannel, realChannel.StartAssignment());
+ if (realChannel) {
+ realChannel->SetTopWindowURI(mTopWindowURI);
+ }
+
+ // 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))
+ httpInternal->SetDocumentURI(newURI);
+ else
+ httpInternal->SetDocumentURI(mDocumentURI);
+
+ // 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));
+ httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget());
+ }
+
+ // Preserve CORS mode flag.
+ httpInternal->SetCorsMode(mCorsMode);
+
+ // Preserve Redirect mode flag.
+ httpInternal->SetRedirectMode(mRedirectMode);
+
+ // Preserve Cache mode flag.
+ httpInternal->SetFetchCacheMode(mFetchCacheMode);
+
+ // Preserve Integrity metadata.
+ httpInternal->SetIntegrityMetadata(mIntegrityMetadata);
+ }
+
+ // transfer application cache information
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(newChannel);
+ if (appCacheChannel) {
+ appCacheChannel->SetApplicationCache(mApplicationCache);
+ appCacheChannel->SetInheritApplicationCache(mInheritApplicationCache);
+ // We purposely avoid transfering mChooseApplicationCache.
+ }
+
+ // 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());
+ }
+ }
+
+ // Transfer the timing data (if we are dealing with an nsITimedChannel).
+ nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
+ nsCOMPtr<nsITimedChannel> oldTimedChannel(
+ do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
+ if (oldTimedChannel && newTimedChannel) {
+ newTimedChannel->SetTimingEnabled(mTimingEnabled);
+ newTimedChannel->SetRedirectCount(mRedirectCount + 1);
+
+ // 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 (mRedirectStartTimeStamp.IsNull()) {
+ TimeStamp asyncOpen;
+ oldTimedChannel->GetAsyncOpen(&asyncOpen);
+ newTimedChannel->SetRedirectStart(asyncOpen);
+ }
+ else {
+ newTimedChannel->SetRedirectStart(mRedirectStartTimeStamp);
+ }
+
+ // The RedirectEnd timestamp is equal to the previous channel response end.
+ TimeStamp prevResponseEnd;
+ oldTimedChannel->GetResponseEnd(&prevResponseEnd);
+ newTimedChannel->SetRedirectEnd(prevResponseEnd);
+
+ nsAutoString initiatorType;
+ oldTimedChannel->GetInitiatorType(initiatorType);
+ newTimedChannel->SetInitiatorType(initiatorType);
+
+ // Check whether or not this was a cross-domain redirect.
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ mAllRedirectsSameOrigin && SameOriginWithOriginalUri(newURI));
+
+ // Execute the timing allow check to determine whether
+ // to report the redirect timing info
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ GetLoadInfo(getter_AddRefs(loadInfo));
+ // TYPE_DOCUMENT loads don't have a loadingPrincipal, so we can't set
+ // AllRedirectsPassTimingAllowCheck on them.
+ if (loadInfo && loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->LoadingPrincipal();
+ newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
+ mAllRedirectsPassTimingAllowCheck &&
+ oldTimedChannel->TimingAllowCheck(principal));
+ }
+ }
+
+ // Pass the preferred alt-data type on to the new channel.
+ nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
+ if (cacheInfoChan) {
+ cacheInfoChan->PreferAlternativeDataType(mPreferredCachedAltDataType);
+ }
+
+ if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ // Copy non-origin related headers to the new channel.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new SetupReplacementChannelHeaderVisitor(httpChannel);
+ mRequestHead.VisitHeaders(visitor);
+ }
+
+ // This channel has been redirected. Don't report timing info.
+ mTimingEnabled = false;
+ return NS_OK;
+}
+
+// Redirect Tracking
+bool
+HttpBaseChannel::SameOriginWithOriginalUri(nsIURI *aURI)
+{
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsresult rv = ssm->CheckSameOriginURI(aURI, mOriginalURI, false);
+ return (NS_SUCCEEDED(rv));
+}
+
+
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTimingEnabled(bool enabled) {
+ mTimingEnabled = enabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTimingEnabled(bool* _retval) {
+ *_retval = mTimingEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelCreation(TimeStamp* _retval) {
+ *_retval = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
+ *_retval = mAsyncOpenTime;
+ 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(uint16_t *aRedirectCount)
+{
+ *aRedirectCount = mRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectCount(uint16_t aRedirectCount)
+{
+ mRedirectCount = 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 = mAllRedirectsSameOrigin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin)
+{
+ mAllRedirectsSameOrigin = aAllRedirectsSameOrigin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool *aPassesCheck)
+{
+ *aPassesCheck = mAllRedirectsPassTimingAllowCheck;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck)
+{
+ mAllRedirectsPassTimingAllowCheck = 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(NS_LITERAL_CSTRING("Timing-Allow-Origin"), headerValue);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ if (headerValue == "*") {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ nsContentUtils::GetASCIIOrigin(aOrigin, origin);
+
+ if (headerValue == origin) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ *_retval = false;
+ 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::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(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+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::Performance*
+HttpBaseChannel::GetPerformance()
+{
+ // If performance timing is disabled, there is no need for the Performance
+ // object anymore.
+ if (!mTimingEnabled) {
+ 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_IsParentProcess() && BrowserTabsRemoteAutostart()) {
+ return nullptr;
+ }
+
+ if (!mLoadInfo) {
+ return nullptr;
+ }
+
+ // We don't need to report the resource timing entry for a TYPE_DOCUMENT load.
+ if (mLoadInfo->GetExternalContentPolicyType() == nsIContentPolicyBase::TYPE_DOCUMENT) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(domDocument));
+ if (!domDocument) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> loadingDocument = do_QueryInterface(domDocument);
+ if (!loadingDocument) {
+ return nullptr;
+ }
+
+ // We only add to the document's performance object if it has the same
+ // principal as the one triggering the load. This is to prevent navigations
+ // triggered _by_ the iframe from showing up in the parent document's
+ // performance entries if they have different origins.
+ if (!mLoadInfo->TriggeringPrincipal()->Equals(loadingDocument->NodePrincipal())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow = loadingDocument->GetInnerWindow();
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ mozilla::dom::Performance* docPerformance = innerWindow->GetPerformance();
+ if (!docPerformance) {
+ return nullptr;
+ }
+
+ return docPerformance;
+}
+
+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)
+{
+ *aQueue = mThrottleQueue;
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+
+bool
+HttpBaseChannel::EnsureRequestContextID()
+{
+ nsID nullID;
+ nullID.Clear();
+ if (!mRequestContextID.Equals(nullID)) {
+ // Already have a request context ID, no need to do the rest of this work
+ 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 the transaction
+ rootLoadGroup->GetRequestContextID(&mRequestContextID);
+ return true;
+}
+
+void
+HttpBaseChannel::SetCorsPreflightParameters(const nsTArray<nsCString>& aUnsafeHeaders)
+{
+ MOZ_RELEASE_ASSERT(!mRequestObserversCalled);
+
+ mRequireCORSPreflight = true;
+ mUnsafeHeaders = aUnsafeHeaders;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBlockAuthPrompt(bool* aValue)
+{
+ if (!aValue) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aValue = mBlockAuthPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBlockAuthPrompt(bool aValue)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mBlockAuthPrompt = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey)
+{
+ if (!mConnectionInfo) {
+ return NS_ERROR_FAILURE;
+ }
+ aConnectionInfoHashKey.Assign(mConnectionInfo->HashKey());
+ 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..c8184a601b
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -0,0 +1,660 @@
+/* -*- 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 "nsHttp.h"
+#include "nsAutoPtr.h"
+#include "nsHashPropertyBag.h"
+#include "nsProxyInfo.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIEncodedChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIProgressEventSink.h"
+#include "nsIURI.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIStringEnumerator.h"
+#include "nsISupportsPriority.h"
+#include "nsIClassOfService.h"
+#include "nsIApplicationCache.h"
+#include "nsIResumableChannel.h"
+#include "nsITraceableChannel.h"
+#include "nsILoadContext.h"
+#include "nsILoadInfo.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "nsThreadUtils.h"
+#include "PrivateBrowsingChannel.h"
+#include "mozilla/net/DNS.h"
+#include "nsITimedChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsCOMArray.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "nsIThrottledInputChannel.h"
+
+class nsISecurityConsoleMessage;
+class nsIPrincipal;
+
+namespace mozilla {
+
+namespace dom {
+class Performance;
+}
+
+class LogCollector;
+
+namespace net {
+extern mozilla::LazyLogModule gHttpLog;
+
+/*
+ * 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
+{
+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
+
+ HttpBaseChannel();
+
+ virtual nsresult Init(nsIURI *aURI, uint32_t aCaps, nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI,
+ const nsID& aChannelId);
+
+ // 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 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 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 Open2(nsIInputStream **aResult) override;
+ NS_IMETHOD GetBlockAuthPrompt(bool* aValue) override;
+ NS_IMETHOD SetBlockAuthPrompt(bool aValue) 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 GetReferrer(nsIURI **referrer) override;
+ NS_IMETHOD SetReferrer(nsIURI *referrer) override;
+ NS_IMETHOD GetReferrerPolicy(uint32_t *referrerPolicy) override;
+ NS_IMETHOD SetReferrerWithPolicy(nsIURI *referrer, uint32_t referrerPolicy) override;
+ NS_IMETHOD GetRequestHeader(const nsACString& aHeader, nsACString& aValue) override;
+ NS_IMETHOD SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) override;
+ NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
+ NS_IMETHOD VisitRequestHeaders(nsIHttpHeaderVisitor *visitor) override;
+ NS_IMETHOD VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *visitor) 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;
+ NS_IMETHOD SetAllowPipelining(bool value) override;
+ NS_IMETHOD GetAllowSTS(bool *value) override;
+ NS_IMETHOD SetAllowSTS(bool value) override;
+ NS_IMETHOD GetRedirectionLimit(uint32_t *value) override;
+ NS_IMETHOD SetRedirectionLimit(uint32_t value) override;
+ NS_IMETHOD IsNoStoreResponse(bool *value) override;
+ NS_IMETHOD IsNoCacheResponse(bool *value) override;
+ NS_IMETHOD IsPrivateResponse(bool *value) override;
+ NS_IMETHOD GetResponseStatus(uint32_t *aValue) override;
+ NS_IMETHOD GetResponseStatusText(nsACString& aValue) override;
+ NS_IMETHOD GetRequestSucceeded(bool *aValue) override;
+ NS_IMETHOD RedirectTo(nsIURI *newURI) override;
+ NS_IMETHOD GetRequestContextID(nsID *aRCID) override;
+ NS_IMETHOD GetTransferSize(uint64_t *aTransferSize) override;
+ NS_IMETHOD GetDecodedBodySize(uint64_t *aDecodedBodySize) override;
+ NS_IMETHOD GetEncodedBodySize(uint64_t *aEncodedBodySize) override;
+ NS_IMETHOD SetRequestContextID(const nsID aRCID) override;
+ NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override;
+ NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override;
+ NS_IMETHOD GetProtocolVersion(nsACString & aProtocolVersion) override;
+ NS_IMETHOD GetChannelId(nsACString& aChannelId) override;
+ NS_IMETHOD SetChannelId(const nsACString& aChannelId) override;
+ NS_IMETHOD GetTopLevelContentWindowId(uint64_t *aContentWindowId) override;
+ NS_IMETHOD SetTopLevelContentWindowId(uint64_t aContentWindowId) override;
+
+ // 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 char *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 GetCanceled(bool *aCanceled) 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 GetAllowSpdy(bool *aAllowSpdy) override;
+ NS_IMETHOD SetAllowSpdy(bool aAllowSpdy) 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 GetApiRedirectToURI(nsIURI * *aApiRedirectToURI) override;
+ 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 GetNetworkInterfaceId(nsACString& aNetworkInterfaceId) override;
+ NS_IMETHOD SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId) 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 GetProxyURI(nsIURI **proxyURI) override;
+ virtual void SetCorsPreflightParameters(const nsTArray<nsCString>& unsafeHeaders) override;
+ NS_IMETHOD GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) override;
+ NS_IMETHOD GetIntegrityMetadata(nsAString& aIntegrityMetadata) override;
+ NS_IMETHOD SetIntegrityMetadata(const nsAString& aIntegrityMetadata) override;
+ virtual mozilla::net::nsHttpChannel * QueryHttpChannelImpl(void) override;
+
+ inline void CleanRedirectCacheChainIfNecessary()
+ {
+ mRedirectedCachekeys = nullptr;
+ }
+ NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName,
+ nsIHttpUpgradeListener *aListener) 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
+ FlushConsoleReports(nsIDocument* aDocument,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void
+ FlushConsoleReports(nsIConsoleReportCollector* aCollector) override;
+
+ void
+ FlushReportsByWindowId(uint64_t aWindowId,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void
+ ClearConsoleReports() override;
+
+ class nsContentEncodings : public nsIUTF8StringEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ nsContentEncodings(nsIHttpChannel* aChannel, const char* aEncodingHeader);
+
+ private:
+ virtual ~nsContentEncodings();
+
+ 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; }
+ nsHttpRequestHead * GetRequestHead() { return &mRequestHead; }
+
+ const NetAddr& GetSelfAddr() { return mSelfAddr; }
+ const NetAddr& GetPeerAddr() { return mPeerAddr; }
+
+ nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo);
+
+public: /* Necko internal use only... */
+ bool IsNavigation();
+
+ // 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.
+ nsresult DoApplyContentConversions(nsIStreamListener *aNextListener,
+ nsIStreamListener **aNewNextListener);
+
+ // Callback on main thread when NS_AsyncCopy() is finished populating
+ // the new mUploadStream.
+ void EnsureUploadStreamIsCloneableComplete(nsresult aStatus);
+
+protected:
+ nsCOMArray<nsISecurityConsoleMessage> mSecurityConsoleMessages;
+
+ // 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
+ void ReleaseListeners();
+
+ // 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(char const *aCookie);
+
+ mozilla::dom::Performance* GetPerformance();
+ nsIURI* GetReferringPage();
+ nsPIDOMWindowInner* GetInnerDOMWindow();
+
+ void AddCookiesToRequest();
+ 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);
+ mRequestObserversCalled = 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.
+ bool SameOriginWithOriginalUri(nsIURI *aURI);
+
+ // GetPrincipal Returns the channel's URI principal.
+ nsIPrincipal *GetURIPrincipal();
+
+ bool BypassServiceWorker() const;
+
+ // Returns true if this channel should intercept the network request and prepare
+ // for a possible synthesized response instead.
+ bool ShouldIntercept(nsIURI* aURI = nullptr);
+
+#ifdef DEBUG
+ // Check if mPrivateBrowsingId matches between LoadInfo and LoadContext.
+ void AssertPrivateBrowsingId();
+#endif
+
+ friend class PrivateBrowsingChannel<HttpBaseChannel>;
+ friend class InterceptFailedOnStop;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mReferrer;
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+
+ // An instance of nsHTTPCompressConv
+ nsCOMPtr<nsIStreamListener> mCompressListener;
+
+ nsHttpRequestHead mRequestHead;
+ // Upload throttling.
+ nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ nsCOMPtr<nsIRunnable> mUploadCloneableCallback;
+ nsAutoPtr<nsHttpResponseHead> mResponseHead;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+
+ nsCString mSpec; // ASCII encoded URL spec
+ nsCString mContentTypeHint;
+ nsCString mContentCharsetHint;
+ nsCString mUserSetCookieHeader;
+
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+
+ // HTTP Upgrade Data
+ nsCString mUpgradeProtocol;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeProtocolCallback;
+
+ // Resumable channel specific data
+ nsCString mEntityID;
+ uint64_t mStartPos;
+
+ nsresult mStatus;
+ uint32_t mLoadFlags;
+ uint32_t mCaps;
+ uint32_t mClassOfService;
+ int16_t mPriority;
+ uint8_t mRedirectionLimit;
+
+ uint32_t mApplyConversion : 1;
+ uint32_t mCanceled : 1;
+ uint32_t mIsPending : 1;
+ uint32_t mWasOpened : 1;
+ // if 1 all "http-on-{opening|modify|etc}-request" observers have been called
+ uint32_t mRequestObserversCalled : 1;
+ uint32_t mResponseHeadersModified : 1;
+ uint32_t mAllowPipelining : 1;
+ uint32_t mAllowSTS : 1;
+ uint32_t mThirdPartyFlags : 3;
+ uint32_t mUploadStreamHasHeaders : 1;
+ uint32_t mInheritApplicationCache : 1;
+ uint32_t mChooseApplicationCache : 1;
+ uint32_t mLoadedFromApplicationCache : 1;
+ uint32_t mChannelIsForDownload : 1;
+ uint32_t mTracingEnabled : 1;
+ // True if timing collection is enabled
+ uint32_t mTimingEnabled : 1;
+ uint32_t mAllowSpdy : 1;
+ uint32_t mAllowAltSvc : 1;
+ uint32_t mBeConservative : 1;
+ uint32_t mResponseTimeoutEnabled : 1;
+ // A flag that should be false only if a cross-domain redirect occurred
+ uint32_t mAllRedirectsSameOrigin : 1;
+
+ // Is 1 if no redirects have occured or if all redirects
+ // pass the Resource Timing timing-allow-check
+ uint32_t mAllRedirectsPassTimingAllowCheck : 1;
+
+ // True if this channel was intercepted and could receive a synthesized response.
+ uint32_t mResponseCouldBeSynthesized : 1;
+
+ uint32_t mBlockAuthPrompt : 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 mAllowStaleCacheContent : 1;
+
+ // Current suspension depth for this channel object
+ uint32_t mSuspendCount;
+
+ // Per channel transport window override (0 means no override)
+ uint32_t mInitialRwin;
+
+ nsCOMPtr<nsIURI> mAPIRedirectToURI;
+ nsAutoPtr<nsTArray<nsCString> > mRedirectedCachekeys;
+
+ uint32_t mProxyResolveFlags;
+ nsCOMPtr<nsIURI> mProxyURI;
+
+ uint32_t mContentDispositionHint;
+ nsAutoPtr<nsString> mContentDispositionFilename;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ uint32_t mReferrerPolicy;
+
+ // Performance tracking
+ // The initiator type (for this resource) - how was the resource referenced in
+ // the HTML file.
+ nsString mInitiatorType;
+ // Number of redirects that has occurred.
+ int16_t mRedirectCount;
+ // 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;
+ // copied from the transaction before we null out mTransaction
+ // so that the timing can still be queried from OnStopRequest
+ TimingStruct mTransactionTimings;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ bool mForcePending;
+ nsCOMPtr<nsIURI> mTopWindowURI;
+
+ bool mCorsIncludeCredentials;
+ uint32_t mCorsMode;
+ uint32_t mRedirectMode;
+ uint32_t mFetchCacheMode;
+
+ // These parameters are used to ensure that we do not call OnStartRequest and
+ // OnStopRequest more than once.
+ bool mOnStartRequestCalled;
+ bool mOnStopRequestCalled;
+
+ // Defaults to false. Is set to true at the begining of OnStartRequest.
+ // Used to ensure methods can't be called before OnStartRequest.
+ bool mAfterOnStartRequestBegun;
+
+ uint64_t mTransferSize;
+ uint64_t mDecodedBodySize;
+ uint64_t mEncodedBodySize;
+
+ // The network interface id that's associated with this channel.
+ nsCString mNetworkInterfaceId;
+
+ nsID mRequestContextID;
+ bool EnsureRequestContextID();
+
+ // ID of the top-level document's inner window this channel is being
+ // originated from.
+ uint64_t mContentWindowId;
+
+ bool mRequireCORSPreflight;
+ nsTArray<nsCString> mUnsafeHeaders;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
+
+ // Holds the name of the preferred alt-data type.
+ nsCString mPreferredCachedAltDataType;
+ // Holds the name of the alternative data type the channel returned.
+ nsCString mAvailableCachedAltDataType;
+
+ bool mForceMainDocumentChannel;
+
+ nsID mChannelId;
+
+ nsString mIntegrityMetadata;
+};
+
+// 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(0) {}
+
+ // Aborts channel: calls OnStart/Stop with provided status, removes channel
+ // from loadGroup.
+ 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
+ nsresult AsyncCall(void (T::*funcPtr)(),
+ nsRunnableMethod<T> **retval = nullptr);
+private:
+ T *mThis;
+
+protected:
+ // Function to be called at resume time
+ void (T::* mCallOnResume)(void);
+};
+
+template <class T>
+nsresult HttpAsyncAborter<T>::AsyncAbort(nsresult status)
+{
+ MOZ_LOG(gHttpLog, LogLevel::Debug,
+ ("HttpAsyncAborter::AsyncAbort [this=%p status=%x]\n", mThis, 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()
+{
+ NS_PRECONDITION(!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::HandleAsyncAbort;
+ 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(mThis, funcPtr);
+ rv = NS_DispatchToCurrentThread(event);
+ if (NS_SUCCEEDED(rv) && retval) {
+ *retval = event;
+ }
+
+ return rv;
+}
+
+} // 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..0de6095e16
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -0,0 +1,2849 @@
+/* -*- 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/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/ipc/FileDescriptorSetChild.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/HttpChannelChild.h"
+
+#include "nsISupportsPrimitives.h"
+#include "nsChannelClassifier.h"
+#include "nsStringStream.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/ChannelDiverterChild.h"
+#include "mozilla/net/DNS.h"
+#include "SerializedLoadContext.h"
+#include "nsInputStreamPump.h"
+#include "InterceptedChannel.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsContentSecurityManager.h"
+#include "nsIDeprecationWarner.h"
+#include "nsICompressConvStats.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsStreamUtils.h"
+
+#ifdef OS_POSIX
+#include "chrome/common/file_descriptor_set_posix.h"
+#endif
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+extern bool
+WillRedirect(nsHttpResponseHead * response);
+
+namespace {
+
+const uint32_t kMaxFileDescriptorsPerMessage = 250;
+
+#ifdef OS_POSIX
+// Keep this in sync with other platforms.
+static_assert(FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE == 250,
+ "MAX_DESCRIPTORS_PER_MESSAGE mismatch!");
+#endif
+
+} // namespace
+
+
+NS_IMPL_ISUPPORTS(InterceptStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIProgressEventSink)
+
+NS_IMETHODIMP
+InterceptStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ if (mOwner) {
+ mOwner->DoOnStartRequest(mOwner, mContext);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptStreamListener::OnStatus(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult status, const char16_t* aStatusArg)
+{
+ if (mOwner) {
+ mOwner->DoOnStatus(mOwner, status);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptStreamListener::OnProgress(nsIRequest* aRequest, nsISupports* aContext,
+ int64_t aProgress, int64_t aProgressMax)
+{
+ if (mOwner) {
+ mOwner->DoOnProgress(mOwner, aProgress, aProgressMax);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptStreamListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream* aInputStream, uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (!mOwner) {
+ return NS_OK;
+ }
+
+ uint32_t loadFlags;
+ mOwner->GetLoadFlags(&loadFlags);
+
+ if (!(loadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
+ nsCOMPtr<nsIURI> uri;
+ mOwner->GetURI(getter_AddRefs(uri));
+
+ nsAutoCString host;
+ uri->GetHost(host);
+
+ OnStatus(mOwner, aContext, NS_NET_STATUS_READING, NS_ConvertUTF8toUTF16(host).get());
+
+ int64_t progress = aOffset + aCount;
+ OnProgress(mOwner, aContext, progress, mOwner->mSynthesizedStreamLength);
+ }
+
+ mOwner->DoOnDataAvailable(mOwner, mContext, aInputStream, aOffset, aCount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode)
+{
+ if (mOwner) {
+ mOwner->DoPreOnStopRequest(aStatusCode);
+ mOwner->DoOnStopRequest(mOwner, aStatusCode, mContext);
+ }
+ Cleanup();
+ return NS_OK;
+}
+
+void
+InterceptStreamListener::Cleanup()
+{
+ mOwner = nullptr;
+ mContext = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild
+//-----------------------------------------------------------------------------
+
+HttpChannelChild::HttpChannelChild()
+ : HttpAsyncAborter<HttpChannelChild>(this)
+ , mSynthesizedStreamLength(0)
+ , mIsFromCache(false)
+ , mCacheEntryAvailable(false)
+ , mCacheExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
+ , mSendResumeAt(false)
+ , mIPCOpen(false)
+ , mKeptAlive(false)
+ , mUnknownDecoderInvolved(false)
+ , mDivertingToParent(false)
+ , mFlushedForDiversion(false)
+ , mSuspendSent(false)
+ , mSynthesizedResponse(false)
+ , mShouldInterceptSubsequentRedirect(false)
+ , mRedirectingForSubsequentSynthesizedResponse(false)
+ , mPostRedirectChannelShouldIntercept(false)
+ , mPostRedirectChannelShouldUpgrade(false)
+ , mShouldParentIntercept(false)
+ , mSuspendParentAfterSynthesizeResponse(false)
+{
+ LOG(("Creating HttpChannelChild @%x\n", this));
+
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+ mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this));
+}
+
+HttpChannelChild::~HttpChannelChild()
+{
+ LOG(("Destroying HttpChannelChild @%x\n", this));
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+// Override nsHashPropertyBag's AddRef: we don't need thread-safe refcnt
+NS_IMPL_ADDREF(HttpChannelChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) HttpChannelChild::Release()
+{
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ NS_ASSERT_OWNINGTHREAD(HttpChannelChild);
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "HttpChannelChild");
+
+ // 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 !mIPCOpen, then there's nobody to send
+ // to, so we fall through.
+ if (mKeptAlive && mRefCnt == 1 && mIPCOpen) {
+ 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.
+ SendDeletingChannel();
+ return 1;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+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(nsICacheInfoChannel)
+ 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(nsIApplicationCacheContainer)
+ 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(nsIAssociatedContentSecurity, GetAssociatedContentSecurity())
+ NS_INTERFACE_MAP_ENTRY(nsIDivertableChannel)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::PHttpChannelChild
+//-----------------------------------------------------------------------------
+
+void
+HttpChannelChild::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void
+HttpChannelChild::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+class AssociateApplicationCacheEvent : public ChannelEvent
+{
+ public:
+ AssociateApplicationCacheEvent(HttpChannelChild* aChild,
+ const nsCString &aGroupID,
+ const nsCString &aClientID)
+ : mChild(aChild)
+ , groupID(aGroupID)
+ , clientID(aClientID) {}
+
+ void Run() { mChild->AssociateApplicationCache(groupID, clientID); }
+ private:
+ HttpChannelChild* mChild;
+ nsCString groupID;
+ nsCString clientID;
+};
+
+bool
+HttpChannelChild::RecvAssociateApplicationCache(const nsCString &groupID,
+ const nsCString &clientID)
+{
+ LOG(("HttpChannelChild::RecvAssociateApplicationCache [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new AssociateApplicationCacheEvent(this, groupID,
+ clientID));
+ return true;
+}
+
+void
+HttpChannelChild::AssociateApplicationCache(const nsCString &groupID,
+ const nsCString &clientID)
+{
+ LOG(("HttpChannelChild::AssociateApplicationCache [this=%p]\n", this));
+ nsresult rv;
+ mApplicationCache = do_CreateInstance(NS_APPLICATIONCACHE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return;
+
+ mLoadedFromApplicationCache = true;
+ mApplicationCache->InitAsHandle(groupID, clientID);
+}
+
+class StartRequestEvent : public ChannelEvent
+{
+ public:
+ StartRequestEvent(HttpChannelChild* aChild,
+ const nsresult& aChannelStatus,
+ const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const bool& aIsFromCache,
+ const bool& aCacheEntryAvailable,
+ const uint32_t& aCacheExpirationTime,
+ const nsCString& aCachedCharset,
+ const nsCString& aSecurityInfoSerialization,
+ const NetAddr& aSelfAddr,
+ const NetAddr& aPeerAddr,
+ const uint32_t& aCacheKey,
+ const nsCString& altDataType)
+ : mChild(aChild)
+ , mChannelStatus(aChannelStatus)
+ , mResponseHead(aResponseHead)
+ , mRequestHeaders(aRequestHeaders)
+ , mUseResponseHead(aUseResponseHead)
+ , mIsFromCache(aIsFromCache)
+ , mCacheEntryAvailable(aCacheEntryAvailable)
+ , mCacheExpirationTime(aCacheExpirationTime)
+ , mCachedCharset(aCachedCharset)
+ , mSecurityInfoSerialization(aSecurityInfoSerialization)
+ , mSelfAddr(aSelfAddr)
+ , mPeerAddr(aPeerAddr)
+ , mCacheKey(aCacheKey)
+ , mAltDataType(altDataType)
+ {}
+
+ void Run()
+ {
+ LOG(("StartRequestEvent [this=%p]\n", mChild));
+ mChild->OnStartRequest(mChannelStatus, mResponseHead, mUseResponseHead,
+ mRequestHeaders, mIsFromCache, mCacheEntryAvailable,
+ mCacheExpirationTime, mCachedCharset,
+ mSecurityInfoSerialization, mSelfAddr, mPeerAddr,
+ mCacheKey, mAltDataType);
+ }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mChannelStatus;
+ nsHttpResponseHead mResponseHead;
+ nsHttpHeaderArray mRequestHeaders;
+ bool mUseResponseHead;
+ bool mIsFromCache;
+ bool mCacheEntryAvailable;
+ uint32_t mCacheExpirationTime;
+ nsCString mCachedCharset;
+ nsCString mSecurityInfoSerialization;
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+ uint32_t mCacheKey;
+ nsCString mAltDataType;
+};
+
+bool
+HttpChannelChild::RecvOnStartRequest(const nsresult& channelStatus,
+ const nsHttpResponseHead& responseHead,
+ const bool& useResponseHead,
+ const nsHttpHeaderArray& requestHeaders,
+ const bool& isFromCache,
+ const bool& cacheEntryAvailable,
+ const uint32_t& cacheExpirationTime,
+ const nsCString& cachedCharset,
+ const nsCString& securityInfoSerialization,
+ const NetAddr& selfAddr,
+ const NetAddr& peerAddr,
+ const int16_t& redirectCount,
+ const uint32_t& cacheKey,
+ const nsCString& altDataType)
+{
+ LOG(("HttpChannelChild::RecvOnStartRequest [this=%p]\n", this));
+ // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+ // stage, as they are set in the listener's OnStartRequest.
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "mFlushedForDiversion should be unset before OnStartRequest!");
+ MOZ_RELEASE_ASSERT(!mDivertingToParent,
+ "mDivertingToParent should be unset before OnStartRequest!");
+
+
+ mRedirectCount = redirectCount;
+
+ mEventQ->RunOrEnqueue(new StartRequestEvent(this, channelStatus, responseHead,
+ useResponseHead, requestHeaders,
+ isFromCache, cacheEntryAvailable,
+ cacheExpirationTime,
+ cachedCharset,
+ securityInfoSerialization,
+ selfAddr, peerAddr, cacheKey,
+ altDataType));
+ return true;
+}
+
+void
+HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
+ const nsHttpResponseHead& responseHead,
+ const bool& useResponseHead,
+ const nsHttpHeaderArray& requestHeaders,
+ const bool& isFromCache,
+ const bool& cacheEntryAvailable,
+ const uint32_t& cacheExpirationTime,
+ const nsCString& cachedCharset,
+ const nsCString& securityInfoSerialization,
+ const NetAddr& selfAddr,
+ const NetAddr& peerAddr,
+ const uint32_t& cacheKey,
+ const nsCString& altDataType)
+{
+ LOG(("HttpChannelChild::OnStartRequest [this=%p]\n", this));
+
+ // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+ // stage, as they are set in the listener's OnStartRequest.
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "mFlushedForDiversion should be unset before OnStartRequest!");
+ MOZ_RELEASE_ASSERT(!mDivertingToParent,
+ "mDivertingToParent should be unset before OnStartRequest!");
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = channelStatus;
+ }
+
+ if (useResponseHead && !mCanceled)
+ mResponseHead = new nsHttpResponseHead(responseHead);
+
+ if (!securityInfoSerialization.IsEmpty()) {
+ NS_DeserializeObject(securityInfoSerialization,
+ getter_AddRefs(mSecurityInfo));
+ }
+
+ mIsFromCache = isFromCache;
+ mCacheEntryAvailable = cacheEntryAvailable;
+ mCacheExpirationTime = cacheExpirationTime;
+ mCachedCharset = cachedCharset;
+ mSelfAddr = selfAddr;
+ mPeerAddr = peerAddr;
+
+ mAvailableCachedAltDataType = altDataType;
+
+ mAfterOnStartRequestBegun = true;
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ nsresult rv;
+ nsCOMPtr<nsISupportsPRUint32> container =
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ rv = container->SetData(cacheKey);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+ mCacheKey = container;
+
+ // replace our request headers with what actually got sent in the parent
+ mRequestHead.SetHeaders(requestHeaders);
+
+ // 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);
+
+ mTracingEnabled = false;
+
+ DoOnStartRequest(this, mListenerContext);
+}
+
+namespace {
+
+class SyntheticDiversionListener final : public nsIStreamListener
+{
+ RefPtr<HttpChannelChild> mChannel;
+
+ ~SyntheticDiversionListener()
+ {
+ }
+
+public:
+ explicit SyntheticDiversionListener(HttpChannelChild* aChannel)
+ : mChannel(aChannel)
+ {
+ MOZ_ASSERT(mChannel);
+ }
+
+ NS_IMETHOD
+ OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override
+ {
+ MOZ_ASSERT_UNREACHABLE("SyntheticDiversionListener should never see OnStartRequest");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult aStatus) override
+ {
+ mChannel->SendDivertOnStopRequest(aStatus);
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream* aInputStream, uint64_t aOffset,
+ uint32_t aCount) override
+ {
+ nsAutoCString data;
+ nsresult rv = NS_ConsumeStream(aInputStream, aCount, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->Cancel(rv);
+ return rv;
+ }
+
+ mChannel->SendDivertOnDataAvailable(data, aOffset, aCount);
+ return NS_OK;
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(SyntheticDiversionListener, nsIStreamListener);
+
+} // anonymous namespace
+
+void
+HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ LOG(("HttpChannelChild::DoOnStartRequest [this=%p]\n", this));
+
+ // In theory mListener should not be null, but in practice sometimes it is.
+ MOZ_ASSERT(mListener);
+ if (!mListener) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+ nsresult rv = mListener->OnStartRequest(aRequest, aContext);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ if (mDivertingToParent) {
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ mCompressListener = nullptr;
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+
+ // If the response has been synthesized in the child, then we are going
+ // be getting OnDataAvailable and OnStopRequest from the synthetic
+ // stream pump. We need to forward these back to the parent diversion
+ // listener.
+ if (mSynthesizedResponse) {
+ mListener = new SyntheticDiversionListener(this);
+ }
+
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = DoApplyContentConversions(mListener, getter_AddRefs(listener),
+ mListenerContext);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ } else if (listener) {
+ mListener = listener;
+ mCompressListener = listener;
+ }
+}
+
+class TransportAndDataEvent : public ChannelEvent
+{
+ public:
+ TransportAndDataEvent(HttpChannelChild* child,
+ const nsresult& channelStatus,
+ const nsresult& transportStatus,
+ const uint64_t& progress,
+ const uint64_t& progressMax,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mChild(child)
+ , mChannelStatus(channelStatus)
+ , mTransportStatus(transportStatus)
+ , mProgress(progress)
+ , mProgressMax(progressMax)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count) {}
+
+ void Run()
+ {
+ mChild->OnTransportAndData(mChannelStatus, mTransportStatus, mProgress,
+ mProgressMax, mOffset, mCount, mData);
+ }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mChannelStatus;
+ nsresult mTransportStatus;
+ uint64_t mProgress;
+ uint64_t mProgressMax;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+bool
+HttpChannelChild::RecvOnTransportAndData(const nsresult& channelStatus,
+ const nsresult& transportStatus,
+ const uint64_t& progress,
+ const uint64_t& progressMax,
+ const uint64_t& offset,
+ const uint32_t& count,
+ const nsCString& data)
+{
+ LOG(("HttpChannelChild::RecvOnTransportAndData [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be receiving any more callbacks from parent!");
+
+ mEventQ->RunOrEnqueue(new TransportAndDataEvent(this, channelStatus,
+ transportStatus, progress,
+ progressMax, data, offset,
+ count),
+ mDivertingToParent);
+ return true;
+}
+
+class MaybeDivertOnDataHttpEvent : public ChannelEvent
+{
+ public:
+ MaybeDivertOnDataHttpEvent(HttpChannelChild* child,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mChild(child)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count) {}
+
+ void Run()
+ {
+ mChild->MaybeDivertOnData(mData, mOffset, mCount);
+ }
+
+ private:
+ HttpChannelChild* mChild;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+void
+HttpChannelChild::MaybeDivertOnData(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("HttpChannelChild::MaybeDivertOnData [this=%p]", this));
+
+ if (mDivertingToParent) {
+ SendDivertOnDataAvailable(data, offset, count);
+ }
+}
+
+void
+HttpChannelChild::OnTransportAndData(const nsresult& channelStatus,
+ const nsresult& transportStatus,
+ const uint64_t progress,
+ const uint64_t& progressMax,
+ const uint64_t& offset,
+ const uint32_t& count,
+ const nsCString& data)
+{
+ LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = channelStatus;
+ }
+
+ // For diversion to parent, just SendDivertOnDataAvailable.
+ if (mDivertingToParent) {
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be processing any more callbacks from parent!");
+
+ SendDivertOnDataAvailable(data, offset, count);
+ return;
+ }
+
+ if (mCanceled)
+ return;
+
+ if (mUnknownDecoderInvolved) {
+ LOG(("UnknownDecoder is involved queue OnDataAvailable call. [this=%p]",
+ this));
+ mUnknownDecoderEventQ.AppendElement(
+ MakeUnique<MaybeDivertOnDataHttpEvent>(this, data, offset, count));
+ }
+
+ // Hold queue lock throughout all three calls, else we might process a later
+ // necko msg in between them.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ DoOnStatus(this, transportStatus);
+ DoOnProgress(this, progress, progressMax);
+
+ // 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), data.get(),
+ count, NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ DoOnDataAvailable(this, mListenerContext, stringStream, offset, count);
+ stringStream->Close();
+}
+
+void
+HttpChannelChild::DoOnStatus(nsIRequest* aRequest, nsresult status)
+{
+ LOG(("HttpChannelChild::DoOnStatus [this=%p]\n", this));
+ if (mCanceled)
+ return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink)
+ GetCallback(mProgressSink);
+
+ // Temporary fix for bug 1116124
+ // See 1124971 - Child removes LOAD_BACKGROUND flag from channel
+ if (status == NS_OK)
+ return;
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
+ !(mLoadFlags & LOAD_BACKGROUND))
+ {
+ // OnStatus
+ //
+ MOZ_ASSERT(status == NS_NET_STATUS_RECEIVING_FROM ||
+ status == NS_NET_STATUS_READING);
+
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(aRequest, nullptr, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+}
+
+void
+HttpChannelChild::DoOnProgress(nsIRequest* aRequest, int64_t progress, int64_t progressMax)
+{
+ LOG(("HttpChannelChild::DoOnProgress [this=%p]\n", this));
+ 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) && mIsPending &&
+ !(mLoadFlags & LOAD_BACKGROUND))
+ {
+ // OnProgress
+ //
+ if (progress > 0) {
+ mProgressSink->OnProgress(aRequest, nullptr, progress, progressMax);
+ }
+ }
+}
+
+void
+HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t offset, uint32_t count)
+{
+ LOG(("HttpChannelChild::DoOnDataAvailable [this=%p]\n", this));
+ if (mCanceled)
+ return;
+
+ nsresult rv = mListener->OnDataAvailable(aRequest, aContext, aStream, offset, count);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+}
+
+class StopRequestEvent : public ChannelEvent
+{
+ public:
+ StopRequestEvent(HttpChannelChild* child,
+ const nsresult& channelStatus,
+ const ResourceTimingStruct& timing)
+ : mChild(child)
+ , mChannelStatus(channelStatus)
+ , mTiming(timing) {}
+
+ void Run() { mChild->OnStopRequest(mChannelStatus, mTiming); }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mChannelStatus;
+ ResourceTimingStruct mTiming;
+};
+
+bool
+HttpChannelChild::RecvOnStopRequest(const nsresult& channelStatus,
+ const ResourceTimingStruct& timing)
+{
+ LOG(("HttpChannelChild::RecvOnStopRequest [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be receiving any more callbacks from parent!");
+
+ mEventQ->RunOrEnqueue(new StopRequestEvent(this, channelStatus, timing),
+ mDivertingToParent);
+ return true;
+}
+
+class MaybeDivertOnStopHttpEvent : public ChannelEvent
+{
+ public:
+ MaybeDivertOnStopHttpEvent(HttpChannelChild* child,
+ const nsresult& channelStatus)
+ : mChild(child)
+ , mChannelStatus(channelStatus)
+ {}
+
+ void Run()
+ {
+ mChild->MaybeDivertOnStop(mChannelStatus);
+ }
+
+ private:
+ HttpChannelChild* mChild;
+ nsresult mChannelStatus;
+};
+
+void
+HttpChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
+{
+ LOG(("HttpChannelChild::MaybeDivertOnStop [this=%p, "
+ "mDivertingToParent=%d status=%x]", this, mDivertingToParent,
+ aChannelStatus));
+ if (mDivertingToParent) {
+ SendDivertOnStopRequest(aChannelStatus);
+ }
+}
+
+void
+HttpChannelChild::OnStopRequest(const nsresult& channelStatus,
+ const ResourceTimingStruct& timing)
+{
+ LOG(("HttpChannelChild::OnStopRequest [this=%p status=%x]\n",
+ this, channelStatus));
+
+ if (mDivertingToParent) {
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be processing any more callbacks from parent!");
+
+ SendDivertOnStopRequest(channelStatus);
+ return;
+ }
+
+ if (mUnknownDecoderInvolved) {
+ LOG(("UnknownDecoder is involved queue OnStopRequest call. [this=%p]",
+ this));
+ mUnknownDecoderEventQ.AppendElement(
+ MakeUnique<MaybeDivertOnStopHttpEvent>(this, channelStatus));
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ mTransactionTimings.domainLookupStart = timing.domainLookupStart;
+ mTransactionTimings.domainLookupEnd = timing.domainLookupEnd;
+ mTransactionTimings.connectStart = timing.connectStart;
+ mTransactionTimings.connectEnd = timing.connectEnd;
+ mTransactionTimings.requestStart = timing.requestStart;
+ mTransactionTimings.responseStart = timing.responseStart;
+ mTransactionTimings.responseEnd = timing.responseEnd;
+ mAsyncOpenTime = timing.fetchStart;
+ mRedirectStartTimeStamp = timing.redirectStart;
+ mRedirectEndTimeStamp = timing.redirectEnd;
+ mTransferSize = timing.transferSize;
+ mEncodedBodySize = timing.encodedBodySize;
+ mProtocolVersion = timing.protocolVersion;
+
+ mCacheReadStart = timing.cacheReadStart;
+ mCacheReadEnd = timing.cacheReadEnd;
+
+ Performance* documentPerformance = GetPerformance();
+ if (documentPerformance) {
+ documentPerformance->AddEntry(this, this);
+ }
+
+ DoPreOnStopRequest(channelStatus);
+
+ { // 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, channelStatus, mListenerContext);
+ }
+
+ ReleaseListeners();
+
+ // DocumentChannelCleanup actually nulls out mCacheEntry in the parent, which
+ // we might need later to open the Alt-Data output stream, so just return here
+ if (!mPreferredCachedAltDataType.IsEmpty()) {
+ mKeptAlive = true;
+ return;
+ }
+
+ if (mLoadFlags & LOAD_DOCUMENT_URI) {
+ // Keep IPDL channel open, but only for updating security info.
+ mKeptAlive = true;
+ SendDocumentChannelCleanup();
+ } else {
+ // The parent process will respond by sending a DeleteSelf message and
+ // making sure not to send any more messages after that.
+ SendDeletingChannel();
+ }
+}
+
+void
+HttpChannelChild::DoPreOnStopRequest(nsresult aStatus)
+{
+ LOG(("HttpChannelChild::DoPreOnStopRequest [this=%p status=%x]\n",
+ this, aStatus));
+ mIsPending = false;
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+}
+
+void
+HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus, nsISupports* aContext)
+{
+ LOG(("HttpChannelChild::DoOnStopRequest [this=%p]\n", this));
+ MOZ_ASSERT(!mIsPending);
+
+ // 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 (aChannelStatus == NS_ERROR_TRACKING_URI) {
+ nsChannelClassifier::SetBlockedTrackingContent(this);
+ }
+
+ MOZ_ASSERT(!mOnStopRequestCalled,
+ "We should not call OnStopRequest twice");
+
+ // In theory mListener should not be null, but in practice sometimes it is.
+ MOZ_ASSERT(mListener);
+ if (mListener) {
+ mListener->OnStopRequest(aRequest, aContext, mStatus);
+ }
+ mOnStopRequestCalled = true;
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ mCacheEntryAvailable = false;
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+class ProgressEvent : public ChannelEvent
+{
+ public:
+ ProgressEvent(HttpChannelChild* child,
+ const int64_t& progress,
+ const int64_t& progressMax)
+ : mChild(child)
+ , mProgress(progress)
+ , mProgressMax(progressMax) {}
+
+ void Run() { mChild->OnProgress(mProgress, mProgressMax); }
+ private:
+ HttpChannelChild* mChild;
+ int64_t mProgress, mProgressMax;
+};
+
+bool
+HttpChannelChild::RecvOnProgress(const int64_t& progress,
+ const int64_t& progressMax)
+{
+ mEventQ->RunOrEnqueue(new ProgressEvent(this, progress, progressMax));
+ return true;
+}
+
+void
+HttpChannelChild::OnProgress(const int64_t& progress,
+ const int64_t& progressMax)
+{
+ LOG(("HttpChannelChild::OnProgress [this=%p progress=%lld/%lld]\n",
+ this, progress, progressMax));
+
+ if (mCanceled)
+ return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ // Block socket status event after Cancel or OnStopRequest has been called.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending)
+ {
+ if (progress > 0) {
+ mProgressSink->OnProgress(this, nullptr, progress, progressMax);
+ }
+ }
+}
+
+class StatusEvent : public ChannelEvent
+{
+ public:
+ StatusEvent(HttpChannelChild* child,
+ const nsresult& status)
+ : mChild(child)
+ , mStatus(status) {}
+
+ void Run() { mChild->OnStatus(mStatus); }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mStatus;
+};
+
+bool
+HttpChannelChild::RecvOnStatus(const nsresult& status)
+{
+ mEventQ->RunOrEnqueue(new StatusEvent(this, status));
+ return true;
+}
+
+void
+HttpChannelChild::OnStatus(const nsresult& status)
+{
+ LOG(("HttpChannelChild::OnStatus [this=%p status=%x]\n", this, status));
+
+ if (mCanceled)
+ return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink)
+ GetCallback(mProgressSink);
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ // block socket status event after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
+ !(mLoadFlags & LOAD_BACKGROUND))
+ {
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(this, nullptr, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+}
+
+class FailedAsyncOpenEvent : public ChannelEvent
+{
+ public:
+ FailedAsyncOpenEvent(HttpChannelChild* child, const nsresult& status)
+ : mChild(child)
+ , mStatus(status) {}
+
+ void Run() { mChild->FailedAsyncOpen(mStatus); }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mStatus;
+};
+
+bool
+HttpChannelChild::RecvFailedAsyncOpen(const nsresult& status)
+{
+ LOG(("HttpChannelChild::RecvFailedAsyncOpen [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new FailedAsyncOpenEvent(this, status));
+ return true;
+}
+
+// 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();
+}
+
+void
+HttpChannelChild::FailedAsyncOpen(const nsresult& status)
+{
+ LOG(("HttpChannelChild::FailedAsyncOpen [this=%p status=%x]\n", this, status));
+
+ mStatus = status;
+
+ // We're already being called from IPDL, therefore already "async"
+ HandleAsyncAbort();
+
+ if (mIPCOpen) {
+ SendDeletingChannel();
+ }
+}
+
+void
+HttpChannelChild::DoNotifyListenerCleanup()
+{
+ LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this));
+
+ if (mInterceptListener) {
+ mInterceptListener->Cleanup();
+ mInterceptListener = nullptr;
+ }
+}
+
+class DeleteSelfEvent : public ChannelEvent
+{
+ public:
+ explicit DeleteSelfEvent(HttpChannelChild* child) : mChild(child) {}
+ void Run() { mChild->DeleteSelf(); }
+ private:
+ HttpChannelChild* mChild;
+};
+
+bool
+HttpChannelChild::RecvDeleteSelf()
+{
+ LOG(("HttpChannelChild::RecvDeleteSelf [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new DeleteSelfEvent(this));
+ return true;
+}
+
+HttpChannelChild::OverrideRunnable::OverrideRunnable(HttpChannelChild* aChannel,
+ HttpChannelChild* aNewChannel,
+ InterceptStreamListener* aListener,
+ nsIInputStream* aInput,
+ nsAutoPtr<nsHttpResponseHead>& aHead)
+{
+ mChannel = aChannel;
+ mNewChannel = aNewChannel;
+ mListener = aListener;
+ mInput = aInput;
+ mHead = aHead;
+}
+
+void
+HttpChannelChild::OverrideRunnable::OverrideWithSynthesizedResponse()
+{
+ if (mNewChannel) {
+ mNewChannel->OverrideWithSynthesizedResponse(mHead, mInput, mListener);
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::OverrideRunnable::Run()
+{
+ bool ret = mChannel->Redirect3Complete(this);
+
+ // If the method returns false, it means the IPDL connection is being
+ // asyncly torn down and reopened, and OverrideWithSynthesizedResponse
+ // will be called later from FinishInterceptedRedirect. This object will
+ // be assigned to HttpChannelChild::mOverrideRunnable in order to do so.
+ // If it is true, we can call the method right now.
+ if (ret) {
+ OverrideWithSynthesizedResponse();
+ }
+
+ return NS_OK;
+}
+
+bool
+HttpChannelChild::RecvFinishInterceptedRedirect()
+{
+ // Hold a ref to this to keep it from being deleted by Send__delete__()
+ RefPtr<HttpChannelChild> self(this);
+ Send__delete__(this);
+
+ // The IPDL connection was torn down by a interception logic in
+ // CompleteRedirectSetup, and we need to call FinishInterceptedRedirect.
+ NS_DispatchToMainThread(NewRunnableMethod(this, &HttpChannelChild::FinishInterceptedRedirect));
+
+ return true;
+}
+
+void
+HttpChannelChild::DeleteSelf()
+{
+ Send__delete__(this);
+}
+
+void HttpChannelChild::FinishInterceptedRedirect()
+{
+ nsresult rv;
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mInterceptedRedirectContext, "the context should be null!");
+ rv = AsyncOpen2(mInterceptedRedirectListener);
+ } else {
+ rv = AsyncOpen(mInterceptedRedirectListener, mInterceptedRedirectContext);
+ }
+ mInterceptedRedirectListener = nullptr;
+ mInterceptedRedirectContext = nullptr;
+
+ if (mInterceptingChannel) {
+ mInterceptingChannel->CleanupRedirectingChannel(rv);
+ mInterceptingChannel = nullptr;
+ }
+
+ if (mOverrideRunnable) {
+ mOverrideRunnable->OverrideWithSynthesizedResponse();
+ mOverrideRunnable = nullptr;
+ }
+}
+
+bool
+HttpChannelChild::RecvReportSecurityMessage(const nsString& messageTag,
+ const nsString& messageCategory)
+{
+ AddSecurityMessage(messageTag, messageCategory);
+ return true;
+}
+
+class Redirect1Event : public ChannelEvent
+{
+ public:
+ Redirect1Event(HttpChannelChild* child,
+ const uint32_t& registrarId,
+ const URIParams& newURI,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsACString& securityInfoSerialization,
+ const nsACString& channelId)
+ : mChild(child)
+ , mRegistrarId(registrarId)
+ , mNewURI(newURI)
+ , mRedirectFlags(redirectFlags)
+ , mResponseHead(responseHead)
+ , mSecurityInfoSerialization(securityInfoSerialization)
+ , mChannelId(channelId) {}
+
+ void Run()
+ {
+ mChild->Redirect1Begin(mRegistrarId, mNewURI, mRedirectFlags,
+ mResponseHead, mSecurityInfoSerialization,
+ mChannelId);
+ }
+ private:
+ HttpChannelChild* mChild;
+ uint32_t mRegistrarId;
+ URIParams mNewURI;
+ uint32_t mRedirectFlags;
+ nsHttpResponseHead mResponseHead;
+ nsCString mSecurityInfoSerialization;
+ nsCString mChannelId;
+};
+
+bool
+HttpChannelChild::RecvRedirect1Begin(const uint32_t& registrarId,
+ const URIParams& newUri,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsCString& securityInfoSerialization,
+ const nsCString& channelId)
+{
+ // TODO: handle security info
+ LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new Redirect1Event(this, registrarId, newUri,
+ redirectFlags, responseHead,
+ securityInfoSerialization,
+ channelId));
+ return true;
+}
+
+nsresult
+HttpChannelChild::SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel)
+{
+ LOG(("HttpChannelChild::SetupRedirect [this=%p]\n", this));
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
+ uri,
+ mLoadInfo,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We won't get OnStartRequest, set cookies here.
+ mResponseHead = new nsHttpResponseHead(*responseHead);
+
+ bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(mResponseHead->Status(),
+ mRequestHead.ParsedMethod());
+
+ rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET, redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild = do_QueryInterface(newChannel);
+ if (httpChannelChild) {
+ bool shouldUpgrade = false;
+ auto channelChild = static_cast<HttpChannelChild*>(httpChannelChild.get());
+ if (mShouldInterceptSubsequentRedirect) {
+ // In the case where there was a synthesized response that caused a redirection,
+ // we must force the new channel to intercept the request in the parent before a
+ // network transaction is initiated.
+ httpChannelChild->ForceIntercepted(false, false);
+ } else if (mRedirectMode == nsIHttpChannelInternal::REDIRECT_MODE_MANUAL &&
+ ((redirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
+ nsIChannelEventSink::REDIRECT_PERMANENT)) != 0) &&
+ channelChild->ShouldInterceptURI(uri, shouldUpgrade)) {
+ // In the case where the redirect mode is manual, we need to check whether
+ // the post-redirect channel needs to be intercepted. If that is the
+ // case, force the new channel to intercept the request in the parent
+ // similar to the case above, but also remember that ShouldInterceptURI()
+ // returned true to avoid calling it a second time.
+ httpChannelChild->ForceIntercepted(true, shouldUpgrade);
+ }
+ }
+
+ mRedirectChannelChild = do_QueryInterface(newChannel);
+ newChannel.forget(outChannel);
+
+ return NS_OK;
+}
+
+void
+HttpChannelChild::Redirect1Begin(const uint32_t& registrarId,
+ const URIParams& newUri,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsACString& securityInfoSerialization,
+ const nsACString& channelId)
+{
+ LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this));
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(newUri);
+
+ if (!securityInfoSerialization.IsEmpty()) {
+ NS_DeserializeObject(securityInfoSerialization,
+ getter_AddRefs(mSecurityInfo));
+ }
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsresult rv = SetupRedirect(uri,
+ &responseHead,
+ redirectFlags,
+ getter_AddRefs(newChannel));
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mRedirectChannelChild) {
+ // Set the channelId allocated in parent to the child instance
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mRedirectChannelChild);
+ if (httpChannel) {
+ httpChannel->SetChannelId(channelId);
+ }
+ mRedirectChannelChild->ConnectParent(registrarId);
+ }
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
+ }
+
+ if (NS_FAILED(rv))
+ OnRedirectVerifyCallback(rv);
+}
+
+void
+HttpChannelChild::BeginNonIPCRedirect(nsIURI* responseURI,
+ const nsHttpResponseHead* responseHead)
+{
+ LOG(("HttpChannelChild::BeginNonIPCRedirect [this=%p]\n", this));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsresult rv = SetupRedirect(responseURI,
+ responseHead,
+ nsIChannelEventSink::REDIRECT_INTERNAL,
+ getter_AddRefs(newChannel));
+
+ if (NS_SUCCEEDED(rv)) {
+ // Ensure that the new channel shares the original channel's security information,
+ // since it won't be provided via IPC. In particular, if the target of this redirect
+ // is a synthesized response that has its own security info, the pre-redirect channel
+ // has already received it and it must be propagated to the post-redirect channel.
+ nsCOMPtr<nsIHttpChannelChild> channelChild = do_QueryInterface(newChannel);
+ if (mSecurityInfo && channelChild) {
+ HttpChannelChild* httpChannelChild = static_cast<HttpChannelChild*>(channelChild.get());
+ httpChannelChild->OverrideSecurityInfoForNonIPCRedirect(mSecurityInfo);
+ }
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this,
+ newChannel,
+ nsIChannelEventSink::REDIRECT_INTERNAL);
+ }
+
+ if (NS_FAILED(rv))
+ OnRedirectVerifyCallback(rv);
+}
+
+void
+HttpChannelChild::OverrideSecurityInfoForNonIPCRedirect(nsISupports* securityInfo)
+{
+ mResponseCouldBeSynthesized = true;
+ OverrideSecurityInfo(securityInfo);
+}
+
+class Redirect3Event : public ChannelEvent
+{
+ public:
+ explicit Redirect3Event(HttpChannelChild* child) : mChild(child) {}
+ void Run() { mChild->Redirect3Complete(nullptr); }
+ private:
+ HttpChannelChild* mChild;
+};
+
+bool
+HttpChannelChild::RecvRedirect3Complete()
+{
+ LOG(("HttpChannelChild::RecvRedirect3Complete [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new Redirect3Event(this));
+ return true;
+}
+
+class HttpFlushedForDiversionEvent : public ChannelEvent
+{
+ public:
+ explicit HttpFlushedForDiversionEvent(HttpChannelChild* aChild)
+ : mChild(aChild)
+ {
+ MOZ_RELEASE_ASSERT(aChild);
+ }
+
+ void Run()
+ {
+ mChild->FlushedForDiversion();
+ }
+ private:
+ HttpChannelChild* mChild;
+};
+
+bool
+HttpChannelChild::RecvFlushedForDiversion()
+{
+ LOG(("HttpChannelChild::RecvFlushedForDiversion [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+
+ mEventQ->RunOrEnqueue(new HttpFlushedForDiversionEvent(this), true);
+
+ return true;
+}
+
+bool
+HttpChannelChild::RecvNotifyTrackingProtectionDisabled()
+{
+ nsChannelClassifier::NotifyTrackingProtectionDisabled(this);
+ return true;
+}
+
+void
+HttpChannelChild::FlushedForDiversion()
+{
+ LOG(("HttpChannelChild::FlushedForDiversion [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+
+ // Once this is set, it should not be unset before HttpChannelChild is taken
+ // down. After it is set, no OnStart/OnData/OnStop callbacks should be
+ // received from the parent channel, nor dequeued from the ChannelEventQueue.
+ mFlushedForDiversion = true;
+
+ SendDivertComplete();
+}
+
+bool
+HttpChannelChild::RecvDivertMessages()
+{
+ LOG(("HttpChannelChild::RecvDivertMessages [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+ MOZ_RELEASE_ASSERT(mSuspendCount > 0);
+
+ // DivertTo() has been called on parent, so we can now start sending queued
+ // IPDL messages back to parent listener.
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(Resume()));
+
+ return true;
+}
+
+// Returns true if has actually completed the redirect and cleaned up the
+// channel, or false the interception logic kicked in and we need to asyncly
+// call FinishInterceptedRedirect and CleanupRedirectingChannel.
+// The argument is an optional OverrideRunnable that we pass to the redirected
+// channel.
+bool
+HttpChannelChild::Redirect3Complete(OverrideRunnable* aRunnable)
+{
+ LOG(("HttpChannelChild::Redirect3Complete [this=%p]\n", this));
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIHttpChannelChild> chan = do_QueryInterface(mRedirectChannelChild);
+ RefPtr<HttpChannelChild> httpChannelChild = static_cast<HttpChannelChild*>(chan.get());
+ // Chrome channel has been AsyncOpen'd. Reflect this in child.
+ if (mRedirectChannelChild) {
+ if (httpChannelChild) {
+ httpChannelChild->mOverrideRunnable = aRunnable;
+ httpChannelChild->mInterceptingChannel = this;
+ }
+ rv = mRedirectChannelChild->CompleteRedirectSetup(mListener,
+ mListenerContext);
+ }
+
+ if (!httpChannelChild || !httpChannelChild->mShouldParentIntercept) {
+ // The redirect channel either isn't a HttpChannelChild, or the interception
+ // logic wasn't triggered, so we can clean it up right here.
+ CleanupRedirectingChannel(rv);
+ if (httpChannelChild) {
+ httpChannelChild->mOverrideRunnable = nullptr;
+ httpChannelChild->mInterceptingChannel = nullptr;
+ }
+ return true;
+ }
+ return false;
+}
+
+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)) {
+ if (mLoadInfo) {
+ mLoadInfo->AppendRedirectedPrincipal(GetURIPrincipal(), false);
+ }
+ }
+ else {
+ NS_WARNING("CompleteRedirectSetup failed, HttpChannelChild already open?");
+ }
+
+ // Release ref to new channel.
+ mRedirectChannelChild = nullptr;
+
+ if (mInterceptListener) {
+ mInterceptListener->Cleanup();
+ mInterceptListener = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ConnectParent(uint32_t registrarId)
+{
+ LOG(("HttpChannelChild::ConnectParent [this=%p]\n", this));
+ mozilla::dom::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ GetCallback(iTabChild);
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+ if (MissingRequiredTabChild(tabChild, "http")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (tabChild && !tabChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ // 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();
+
+ HttpChannelConnectArgs connectArgs(registrarId, mShouldParentIntercept);
+ PBrowserOrId browser = static_cast<ContentChild*>(gNeckoChild->Manager())
+ ->GetBrowserOrId(tabChild);
+ if (!gNeckoChild->
+ SendPHttpChannelConstructor(this, browser,
+ IPC::SerializedLoadContext(this),
+ connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::CompleteRedirectSetup(nsIStreamListener *listener,
+ nsISupports *aContext)
+{
+ LOG(("HttpChannelChild::FinishRedirectSetup [this=%p]\n", this));
+
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ if (mShouldParentIntercept) {
+ // This is a redirected channel, and the corresponding parent channel has started
+ // AsyncOpen but was intercepted and suspended. We must tear it down and start
+ // fresh - we will intercept the child channel this time, before creating a new
+ // parent channel unnecessarily.
+
+ // Since this method is called from RecvRedirect3Complete which itself is
+ // called from either OnRedirectVerifyCallback via OverrideRunnable, or from
+ // RecvRedirect3Complete. The order of events must always be:
+ // 1. Teardown the IPDL connection
+ // 2. AsyncOpen the connection again
+ // 3. Cleanup the redirecting channel (the one calling Redirect3Complete)
+ // 4. [optional] Call OverrideWithSynthesizedResponse on the redirected
+ // channel if the call came from OverrideRunnable.
+ mInterceptedRedirectListener = listener;
+ mInterceptedRedirectContext = aContext;
+
+ // This will send a message to the parent notifying it that we are closing
+ // down. After closing the IPC channel, we will proceed to execute
+ // FinishInterceptedRedirect() which AsyncOpen's the channel again.
+ SendFinishInterceptedRedirect();
+
+ // XXX valentin: The interception logic should be rewritten to avoid
+ // calling AsyncOpen on the channel _after_ we call Send__delete__()
+ return NS_OK;
+ }
+
+ /*
+ * 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.
+ */
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = listener;
+ mListenerContext = aContext;
+
+ // 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 result)
+{
+ LOG(("HttpChannelChild::OnRedirectVerifyCallback [this=%p]\n", this));
+ nsresult rv;
+ OptionalURIParams redirectURI;
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannelChild);
+
+ if (NS_SUCCEEDED(result) && !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"));
+ result = NS_ERROR_DOM_BAD_URI;
+ }
+
+ bool forceHSTSPriming = false;
+ bool mixedContentWouldBlock = false;
+ if (newHttpChannel) {
+ // Must not be called until after redirect observers called.
+ newHttpChannel->SetOriginalURI(mOriginalURI);
+
+ nsCOMPtr<nsILoadInfo> newLoadInfo;
+ rv = newHttpChannel->GetLoadInfo(getter_AddRefs(newLoadInfo));
+ if (NS_SUCCEEDED(rv) && newLoadInfo) {
+ forceHSTSPriming = newLoadInfo->GetForceHSTSPriming();
+ mixedContentWouldBlock = newLoadInfo->GetMixedContentWouldBlock();
+ }
+ }
+
+ if (mRedirectingForSubsequentSynthesizedResponse) {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild = do_QueryInterface(mRedirectChannelChild);
+ RefPtr<HttpChannelChild> redirectedChannel =
+ static_cast<HttpChannelChild*>(httpChannelChild.get());
+ // redirectChannel will be NULL if mRedirectChannelChild isn't a
+ // nsIHttpChannelChild (it could be a DataChannelChild).
+
+ RefPtr<InterceptStreamListener> streamListener =
+ new InterceptStreamListener(redirectedChannel, mListenerContext);
+
+ NS_DispatchToMainThread(new OverrideRunnable(this, redirectedChannel,
+ streamListener, mSynthesizedInput,
+ mResponseHead));
+ return NS_OK;
+ }
+
+ RequestHeaderTuples emptyHeaders;
+ RequestHeaderTuples* headerTuples = &emptyHeaders;
+ nsLoadFlags loadFlags = 0;
+ OptionalCorsPreflightArgs corsPreflightArgs = mozilla::void_t();
+
+ nsCOMPtr<nsIHttpChannelChild> newHttpChannelChild =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelChild && NS_SUCCEEDED(result)) {
+ newHttpChannelChild->AddCookiesToRequest();
+ newHttpChannelChild->GetClientSetRequestHeaders(&headerTuples);
+ newHttpChannelChild->GetClientSetCorsPreflightParameters(corsPreflightArgs);
+ }
+
+ /* If the redirect was canceled, bypass OMR and send an empty API
+ * redirect URI */
+ SerializeURI(nullptr, redirectURI);
+
+ if (NS_SUCCEEDED(result)) {
+ // 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;
+ nsresult 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);
+ }
+
+ if (mIPCOpen)
+ SendRedirect2Verify(result, *headerTuples, loadFlags, redirectURI,
+ corsPreflightArgs, forceHSTSPriming,
+ mixedContentWouldBlock, chooseAppcache);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::Cancel(nsresult status)
+{
+ LOG(("HttpChannelChild::Cancel [this=%p]\n", 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 = status;
+ if (RemoteChannelExists())
+ SendCancel(status);
+ if (mSynthesizedResponsePump) {
+ mSynthesizedResponsePump->Cancel(status);
+ }
+ mInterceptListener = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Suspend()
+{
+ LOG(("HttpChannelChild::Suspend [this=%p, mSuspendCount=%lu, "
+ "mDivertingToParent=%d]\n", this, mSuspendCount+1, mDivertingToParent));
+ NS_ENSURE_TRUE(RemoteChannelExists() || mInterceptListener,
+ NS_ERROR_NOT_AVAILABLE);
+
+ // 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++ && !mDivertingToParent) {
+ if (RemoteChannelExists()) {
+ SendSuspend();
+ mSuspendSent = true;
+ }
+ }
+ if (mSynthesizedResponsePump) {
+ mSynthesizedResponsePump->Suspend();
+ }
+ mEventQ->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Resume()
+{
+ LOG(("HttpChannelChild::Resume [this=%p, mSuspendCount=%lu, "
+ "mDivertingToParent=%d]\n", this, mSuspendCount-1, mDivertingToParent));
+ NS_ENSURE_TRUE(RemoteChannelExists() || mInterceptListener,
+ NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ 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 && (!mDivertingToParent || mSuspendSent)) {
+ if (RemoteChannelExists()) {
+ SendResume();
+ }
+ if (mCallOnResume) {
+ AsyncCall(mCallOnResume);
+ mCallOnResume = nullptr;
+ }
+ }
+ if (mSynthesizedResponsePump) {
+ mSynthesizedResponsePump->Resume();
+ }
+ 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 *listener, nsISupports *aContext)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get()));
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ if (mCanceled)
+ return mStatus;
+
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ mAsyncOpenTime = TimeStamp::Now();
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately
+ nsresult rv;
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString cookie;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) {
+ mUserSetCookieHeader = cookie;
+ }
+
+ AddCookiesToRequest();
+
+ //
+ // 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);
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = listener;
+ mListenerContext = aContext;
+
+ // 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().
+ AsyncAbort(mStatus);
+ return NS_OK;
+ }
+
+ // Set user agent override from docshell
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ MOZ_ASSERT_IF(mPostRedirectChannelShouldUpgrade,
+ mPostRedirectChannelShouldIntercept);
+ bool shouldUpgrade = mPostRedirectChannelShouldUpgrade;
+ if (mPostRedirectChannelShouldIntercept ||
+ ShouldInterceptURI(mURI, shouldUpgrade)) {
+ mResponseCouldBeSynthesized = true;
+
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ mInterceptListener = new InterceptStreamListener(this, mListenerContext);
+
+ RefPtr<InterceptedChannelContent> intercepted =
+ new InterceptedChannelContent(this, controller,
+ mInterceptListener, shouldUpgrade);
+ intercepted->NotifyController();
+ return NS_OK;
+ }
+
+ return ContinueAsyncOpen();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult
+HttpChannelChild::ContinueAsyncOpen()
+{
+ nsCString appCacheClientId;
+ if (mInheritApplicationCache) {
+ // 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::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ GetCallback(iTabChild);
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+ if (MissingRequiredTabChild(tabChild, "http")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // This id identifies the inner window's top-level document,
+ // which changes on every new load or navigation.
+ uint64_t contentWindowId = 0;
+ if (tabChild) {
+ MOZ_ASSERT(tabChild->WebNavigation());
+ nsCOMPtr<nsIDocument> document = tabChild->GetDocument();
+ if (document) {
+ contentWindowId = document->InnerWindowID();
+ }
+ }
+ 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(mReferrer, openArgs.referrer());
+ openArgs.referrerPolicy() = mReferrerPolicy;
+ SerializeURI(mAPIRedirectToURI, openArgs.apiRedirectTo());
+ openArgs.loadFlags() = mLoadFlags;
+ openArgs.requestHeaders() = mClientSetRequestHeaders;
+ mRequestHead.Method(openArgs.requestMethod());
+ openArgs.preferredAlternativeType() = mPreferredCachedAltDataType;
+
+ AutoIPCStream autoStream(openArgs.uploadStream());
+ if (mUploadStream) {
+ autoStream.Serialize(mUploadStream, ContentChild::GetSingleton());
+ autoStream.TakeOptionalValue();
+ }
+
+ if (mResponseHead) {
+ openArgs.synthesizedResponseHead() = *mResponseHead;
+ openArgs.suspendAfterSynthesizeResponse() =
+ mSuspendParentAfterSynthesizeResponse;
+ } else {
+ openArgs.synthesizedResponseHead() = mozilla::void_t();
+ openArgs.suspendAfterSynthesizeResponse() = false;
+ }
+
+ nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(mSecurityInfo);
+ if (secInfoSer) {
+ NS_SerializeToString(secInfoSer, openArgs.synthesizedSecurityInfoSerialization());
+ }
+
+ OptionalCorsPreflightArgs optionalCorsPreflightArgs;
+ GetClientSetCorsPreflightParameters(optionalCorsPreflightArgs);
+
+ // NB: This call forces us to cache mTopWindowURI if we haven't already.
+ nsCOMPtr<nsIURI> uri;
+ GetTopWindowURI(getter_AddRefs(uri));
+
+ SerializeURI(mTopWindowURI, openArgs.topWindowURI());
+
+ openArgs.preflightArgs() = optionalCorsPreflightArgs;
+
+ openArgs.uploadStreamHasHeaders() = mUploadStreamHasHeaders;
+ openArgs.priority() = mPriority;
+ openArgs.classOfService() = mClassOfService;
+ openArgs.redirectionLimit() = mRedirectionLimit;
+ openArgs.allowPipelining() = mAllowPipelining;
+ openArgs.allowSTS() = mAllowSTS;
+ openArgs.thirdPartyFlags() = mThirdPartyFlags;
+ openArgs.resumeAt() = mSendResumeAt;
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.chooseApplicationCache() = mChooseApplicationCache;
+ openArgs.appCacheClientID() = appCacheClientId;
+ openArgs.allowSpdy() = mAllowSpdy;
+ openArgs.allowAltSvc() = mAllowAltSvc;
+ openArgs.beConservative() = mBeConservative;
+ openArgs.initialRwin() = mInitialRwin;
+
+ uint32_t cacheKey = 0;
+ if (mCacheKey) {
+ nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(mCacheKey);
+ if (!container) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv = container->GetData(&cacheKey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ openArgs.cacheKey() = cacheKey;
+
+ openArgs.blockAuthPrompt() = mBlockAuthPrompt;
+
+ openArgs.allowStaleCacheContent() = mAllowStaleCacheContent;
+
+ openArgs.contentTypeHint() = mContentTypeHint;
+
+ nsresult rv = mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ EnsureRequestContextID();
+ char rcid[NSID_LENGTH];
+ mRequestContextID.ToProvidedString(rcid);
+ openArgs.requestContextID().AssignASCII(rcid);
+
+ char chid[NSID_LENGTH];
+ mChannelId.ToProvidedString(chid);
+ openArgs.channelId().AssignASCII(chid);
+
+ openArgs.contentWindowId() = contentWindowId;
+
+ if (tabChild && !tabChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // 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();
+
+ PBrowserOrId browser = cc->GetBrowserOrId(tabChild);
+ if (!gNeckoChild->SendPHttpChannelConstructor(this, browser,
+ IPC::SerializedLoadContext(this),
+ openArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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::GetProtocolVersion(nsACString& aProtocolVersion)
+{
+ aProtocolVersion = mProtocolVersion;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetupFallbackChannel(const char *aFallbackKey)
+{
+ DROP_DEAD();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenExpirationTime(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ if (!mCacheEntryAvailable)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = mCacheExpirationTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenCachedCharset(nsACString &_retval)
+{
+ if (!mCacheEntryAvailable)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ _retval = mCachedCharset;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCacheTokenCachedCharset(const nsACString &aCharset)
+{
+ if (!mCacheEntryAvailable || !RemoteChannelExists())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mCachedCharset = aCharset;
+ if (!SendSetCacheTokenCachedCharset(PromiseFlatCString(aCharset))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::IsFromCache(bool *value)
+{
+ if (!mIsPending)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *value = mIsFromCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheKey(nsISupports **cacheKey)
+{
+ NS_IF_ADDREF(*cacheKey = mCacheKey);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCacheKey(nsISupports *cacheKey)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mCacheKey = cacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetAllowStaleCacheContent(bool aAllowStaleCacheContent)
+{
+ mAllowStaleCacheContent = aAllowStaleCacheContent;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent)
+{
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = mAllowStaleCacheContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::PreferAlternativeDataType(const nsACString & aType)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataType = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataType(nsACString & aType)
+{
+ // Must be called during or after OnStartRequest
+ if (!mAfterOnStartRequestBegun) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::OpenAlternativeOutputStream(const nsACString & aType, nsIOutputStream * *_retval)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+ RefPtr<AltDataOutputStreamChild> stream =
+ static_cast<AltDataOutputStreamChild*>(gNeckoChild->SendPAltDataOutputStreamConstructor(nsCString(aType), this));
+ stream.forget(_retval);
+ return NS_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)
+{
+ 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;
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AddClassFlags(uint32_t inFlags)
+{
+ mClassOfService |= inFlags;
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ClearClassFlags(uint32_t inFlags)
+{
+ mClassOfService &= ~inFlags;
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIProxiedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetProxyInfo(nsIProxyInfo **aProxyInfo)
+{
+ 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(!mWasOpened, 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(!mWasOpened, 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 = mLoadedFromApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetInheritApplicationCache(bool *aInherit)
+{
+ *aInherit = mInheritApplicationCache;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetInheritApplicationCache(bool aInherit)
+{
+ mInheritApplicationCache = aInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetChooseApplicationCache(bool *aChoose)
+{
+ *aChoose = mChooseApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetChooseApplicationCache(bool aChoose)
+{
+ mChooseApplicationCache = aChoose;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::MarkOfflineCacheEntryAsForeign()
+{
+ SendMarkOfflineCacheEntryAsForeign();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIAssociatedContentSecurity
+//-----------------------------------------------------------------------------
+
+bool
+HttpChannelChild::GetAssociatedContentSecurity(
+ nsIAssociatedContentSecurity** _result)
+{
+ if (!mSecurityInfo)
+ return false;
+
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc =
+ do_QueryInterface(mSecurityInfo);
+ if (!assoc)
+ return false;
+
+ if (_result)
+ assoc.forget(_result);
+ return true;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCountSubRequestsBrokenSecurity(
+ int32_t *aSubRequestsBrokenSecurity)
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ return assoc->GetCountSubRequestsBrokenSecurity(aSubRequestsBrokenSecurity);
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCountSubRequestsBrokenSecurity(
+ int32_t aSubRequestsBrokenSecurity)
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ return assoc->SetCountSubRequestsBrokenSecurity(aSubRequestsBrokenSecurity);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCountSubRequestsNoSecurity(int32_t *aSubRequestsNoSecurity)
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ return assoc->GetCountSubRequestsNoSecurity(aSubRequestsNoSecurity);
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCountSubRequestsNoSecurity(int32_t aSubRequestsNoSecurity)
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ return assoc->SetCountSubRequestsNoSecurity(aSubRequestsNoSecurity);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Flush()
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ nsresult rv;
+ int32_t broken, no;
+
+ rv = assoc->GetCountSubRequestsBrokenSecurity(&broken);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = assoc->GetCountSubRequestsNoSecurity(&no);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mIPCOpen)
+ SendUpdateAssociatedContentSecurity(broken, no);
+
+ 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(OptionalCorsPreflightArgs& aArgs)
+{
+ if (mRequireCORSPreflight) {
+ CorsPreflightArgs args;
+ args.unsafeHeaders() = mUnsafeHeaders;
+ aArgs = args;
+ } else {
+ aArgs = mozilla::void_t();
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RemoveCorsPreflightCacheEntry(nsIURI* aURI,
+ nsIPrincipal* aPrincipal)
+{
+ 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 (mIPCOpen) {
+ result = SendRemoveCorsPreflightCacheEntry(uri, principalInfo);
+ }
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIDivertableChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild)
+{
+ LOG(("HttpChannelChild::DivertToParent [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(aChild);
+ MOZ_RELEASE_ASSERT(gNeckoChild);
+ MOZ_RELEASE_ASSERT(!mDivertingToParent);
+
+ nsresult rv = NS_OK;
+
+ // If the channel was intercepted, then we likely do not have an IPC actor
+ // yet. We need one, though, in order to have a parent side to divert to.
+ // Therefore, create the actor just in time for us to suspend and divert it.
+ if (mSynthesizedResponse && !RemoteChannelExists()) {
+ mSuspendParentAfterSynthesizeResponse = true;
+ rv = ContinueAsyncOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We must fail DivertToParent() if there's no parent end of the channel (and
+ // won't be!) due to early failure.
+ if (NS_FAILED(mStatus) && !RemoteChannelExists()) {
+ return mStatus;
+ }
+
+ // Once this is set, it should not be unset before the child is taken down.
+ mDivertingToParent = true;
+
+ rv = Suspend();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ HttpChannelDiverterArgs args;
+ args.mChannelChild() = this;
+ args.mApplyConversion() = mApplyConversion;
+
+ PChannelDiverterChild* diverter =
+ gNeckoChild->SendPChannelDiverterConstructor(args);
+ MOZ_RELEASE_ASSERT(diverter);
+
+ *aChild = static_cast<ChannelDiverterChild*>(diverter);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::UnknownDecoderInvolvedKeepData()
+{
+ LOG(("HttpChannelChild::UnknownDecoderInvolvedKeepData [this=%p]",
+ this));
+ mUnknownDecoderInvolved = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::UnknownDecoderInvolvedOnStartRequestCalled()
+{
+ LOG(("HttpChannelChild::UnknownDecoderInvolvedOnStartRequestCalled "
+ "[this=%p, mDivertingToParent=%d]", this, mDivertingToParent));
+ mUnknownDecoderInvolved = false;
+
+ nsresult rv = NS_OK;
+
+ if (mDivertingToParent) {
+ rv = mEventQ->PrependEvents(mUnknownDecoderEventQ);
+ }
+ mUnknownDecoderEventQ.Clear();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetDivertingToParent(bool* aDiverting)
+{
+ NS_ENSURE_ARG_POINTER(aDiverting);
+ *aDiverting = mDivertingToParent;
+ return NS_OK;
+}
+
+
+void
+HttpChannelChild::ResetInterception()
+{
+ NS_ENSURE_TRUE_VOID(gNeckoChild != nullptr);
+
+ if (mInterceptListener) {
+ mInterceptListener->Cleanup();
+ }
+ mInterceptListener = nullptr;
+
+ // The chance to intercept any further requests associated with this channel
+ // (such as redirects) has passed.
+ mLoadFlags |= LOAD_BYPASS_SERVICE_WORKER;
+
+ // Continue with the original cross-process request
+ nsresult rv = ContinueAsyncOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ AsyncAbort(rv);
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetResponseSynthesized(bool* aSynthesized)
+{
+ NS_ENSURE_ARG_POINTER(aSynthesized);
+ *aSynthesized = mSynthesizedResponse;
+ return NS_OK;
+}
+
+void
+HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
+ nsIInputStream* aSynthesizedInput,
+ InterceptStreamListener* aStreamListener)
+{
+ mInterceptListener = aStreamListener;
+
+ // Intercepted responses should already be decoded. If its a redirect,
+ // however, we want to respect the encoding of the final result instead.
+ if (!WillRedirect(aResponseHead)) {
+ SetApplyConversion(false);
+ }
+
+ mResponseHead = aResponseHead;
+ mSynthesizedResponse = true;
+
+ if (WillRedirect(mResponseHead)) {
+ mShouldInterceptSubsequentRedirect = true;
+ // Continue with the original cross-process request
+ nsresult rv = ContinueAsyncOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ AsyncAbort(rv);
+ }
+ return;
+ }
+
+ // In our current implementation, the FetchEvent handler will copy the
+ // response stream completely into the pipe backing the input stream so we
+ // can treat the available as the length of the stream.
+ uint64_t available;
+ nsresult rv = aSynthesizedInput->Available(&available);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mSynthesizedStreamLength = -1;
+ } else {
+ mSynthesizedStreamLength = int64_t(available);
+ }
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mSynthesizedResponsePump),
+ aSynthesizedInput,
+ int64_t(-1), int64_t(-1), 0, 0, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aSynthesizedInput->Close();
+ return;
+ }
+
+ rv = mSynthesizedResponsePump->AsyncRead(aStreamListener, nullptr);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // if this channel has been suspended previously, the pump needs to be
+ // correspondingly suspended now that it exists.
+ for (uint32_t i = 0; i < mSuspendCount; i++) {
+ rv = mSynthesizedResponsePump->Suspend();
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+
+ if (mCanceled) {
+ mSynthesizedResponsePump->Cancel(mStatus);
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ForceIntercepted(bool aPostRedirectChannelShouldIntercept,
+ bool aPostRedirectChannelShouldUpgrade)
+{
+ mShouldParentIntercept = true;
+ mPostRedirectChannelShouldIntercept = aPostRedirectChannelShouldIntercept;
+ mPostRedirectChannelShouldUpgrade = aPostRedirectChannelShouldUpgrade;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ForceIntercepted(uint64_t aInterceptionID)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+HttpChannelChild::ForceIntercepted(nsIInputStream* aSynthesizedInput)
+{
+ mSynthesizedInput = aSynthesizedInput;
+ mSynthesizedResponse = true;
+ mRedirectingForSubsequentSynthesizedResponse = true;
+}
+
+bool
+HttpChannelChild::RecvIssueDeprecationWarning(const uint32_t& warning,
+ const bool& asError)
+{
+ nsCOMPtr<nsIDeprecationWarner> warner;
+ GetCallback(warner);
+ if (warner) {
+ warner->IssueWarning(warning, asError);
+ }
+ return true;
+}
+
+bool
+HttpChannelChild::ShouldInterceptURI(nsIURI* aURI,
+ bool& aShouldUpgrade)
+{
+ bool isHttps = false;
+ nsresult rv = aURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ if (!isHttps && mLoadInfo) {
+ nsContentUtils::GetSecurityManager()->
+ GetChannelResultPrincipal(this, getter_AddRefs(resultPrincipal));
+ }
+ rv = NS_ShouldSecureUpgrade(aURI,
+ mLoadInfo,
+ resultPrincipal,
+ mPrivateBrowsing,
+ mAllowSTS,
+ aShouldUpgrade);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ if (aShouldUpgrade) {
+ rv = NS_GetSecureUpgradedURI(aURI, getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ return ShouldIntercept(upgradedURI ? upgradedURI.get() : aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h
new file mode 100644
index 0000000000..edd209a9f1
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -0,0 +1,390 @@
+/* -*- 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/UniquePtr.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/PHttpChannelChild.h"
+#include "mozilla/net/ChannelEventQueue.h"
+
+#include "nsIStreamListener.h"
+#include "nsILoadGroup.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProgressEventSink.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIResumableChannel.h"
+#include "nsIProxiedChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIAssociatedContentSecurity.h"
+#include "nsIChildChannel.h"
+#include "nsIHttpChannelChild.h"
+#include "nsIDivertableChannel.h"
+#include "mozilla/net/DNS.h"
+
+class nsInputStreamPump;
+
+namespace mozilla {
+namespace net {
+
+class InterceptedChannelContent;
+class InterceptStreamListener;
+
+class HttpChannelChild final : public PHttpChannelChild
+ , public HttpBaseChannel
+ , public HttpAsyncAborter<HttpChannelChild>
+ , public nsICacheInfoChannel
+ , public nsIProxiedChannel
+ , public nsIApplicationCacheChannel
+ , public nsIAsyncVerifyRedirectCallback
+ , public nsIAssociatedContentSecurity
+ , public nsIChildChannel
+ , public nsIHttpChannelChild
+ , public nsIDivertableChannel
+{
+ virtual ~HttpChannelChild();
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIAPPLICATIONCACHECONTAINER
+ NS_DECL_NSIAPPLICATIONCACHECHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSIASSOCIATEDCONTENTSECURITY
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIHTTPCHANNELCHILD
+ NS_DECL_NSIDIVERTABLECHANNEL
+
+ 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 *listener, nsISupports *aContext) override;
+ NS_IMETHOD AsyncOpen2(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 GetProtocolVersion(nsACString& aProtocolVersion) override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override;
+ NS_IMETHOD ForceIntercepted(uint64_t aInterceptionID) 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;
+
+ // IPDL holds a reference while the PHttpChannel protocol is live (starting at
+ // AsyncOpen, and ending at either OnStopRequest or any IPDL error, either of
+ // which call NeckoChild::DeallocPHttpChannelChild()).
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ bool IsSuspended();
+
+ bool RecvNotifyTrackingProtectionDisabled() override;
+ void FlushedForDiversion();
+
+protected:
+ bool RecvOnStartRequest(const nsresult& channelStatus,
+ const nsHttpResponseHead& responseHead,
+ const bool& useResponseHead,
+ const nsHttpHeaderArray& requestHeaders,
+ const bool& isFromCache,
+ const bool& cacheEntryAvailable,
+ const uint32_t& cacheExpirationTime,
+ const nsCString& cachedCharset,
+ const nsCString& securityInfoSerialization,
+ const NetAddr& selfAddr,
+ const NetAddr& peerAddr,
+ const int16_t& redirectCount,
+ const uint32_t& cacheKey,
+ const nsCString& altDataType) override;
+ bool RecvOnTransportAndData(const nsresult& channelStatus,
+ const nsresult& status,
+ const uint64_t& progress,
+ const uint64_t& progressMax,
+ const uint64_t& offset,
+ const uint32_t& count,
+ const nsCString& data) override;
+ bool RecvOnStopRequest(const nsresult& statusCode, const ResourceTimingStruct& timing) override;
+ bool RecvOnProgress(const int64_t& progress, const int64_t& progressMax) override;
+ bool RecvOnStatus(const nsresult& status) override;
+ bool RecvFailedAsyncOpen(const nsresult& status) override;
+ bool RecvRedirect1Begin(const uint32_t& registrarId,
+ const URIParams& newURI,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsCString& securityInfoSerialization,
+ const nsCString& channelId) override;
+ bool RecvRedirect3Complete() override;
+ bool RecvAssociateApplicationCache(const nsCString& groupID,
+ const nsCString& clientID) override;
+ bool RecvFlushedForDiversion() override;
+ bool RecvDivertMessages() override;
+ bool RecvDeleteSelf() override;
+ bool RecvFinishInterceptedRedirect() override;
+
+ bool RecvReportSecurityMessage(const nsString& messageTag,
+ const nsString& messageCategory) override;
+
+ bool RecvIssueDeprecationWarning(const uint32_t& warning,
+ const bool& asError) override;
+
+ bool GetAssociatedContentSecurity(nsIAssociatedContentSecurity** res = nullptr);
+ virtual void DoNotifyListenerCleanup() override;
+
+ NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override;
+
+private:
+
+ class OverrideRunnable : public Runnable {
+ public:
+ OverrideRunnable(HttpChannelChild* aChannel,
+ HttpChannelChild* aNewChannel,
+ InterceptStreamListener* aListener,
+ nsIInputStream* aInput,
+ nsAutoPtr<nsHttpResponseHead>& aHead);
+
+ NS_IMETHOD Run() override;
+ void OverrideWithSynthesizedResponse();
+ private:
+ RefPtr<HttpChannelChild> mChannel;
+ RefPtr<HttpChannelChild> mNewChannel;
+ RefPtr<InterceptStreamListener> mListener;
+ nsCOMPtr<nsIInputStream> mInput;
+ nsAutoPtr<nsHttpResponseHead> mHead;
+ };
+
+ nsresult ContinueAsyncOpen();
+
+ 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);
+
+ bool ShouldInterceptURI(nsIURI* aURI, bool& aShouldUpgrade);
+
+ // Discard the prior interception and continue with the original network request.
+ void ResetInterception();
+
+ // Override this channel's pending response with a synthesized one. The content will be
+ // asynchronously read from the pump.
+ void OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
+ nsIInputStream* aSynthesizedInput,
+ InterceptStreamListener* aStreamListener);
+
+ void ForceIntercepted(nsIInputStream* aSynthesizedInput);
+
+ RequestHeaderTuples mClientSetRequestHeaders;
+ nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
+ RefPtr<InterceptStreamListener> mInterceptListener;
+ RefPtr<nsInputStreamPump> mSynthesizedResponsePump;
+ nsCOMPtr<nsIInputStream> mSynthesizedInput;
+ int64_t mSynthesizedStreamLength;
+
+ bool mIsFromCache;
+ bool mCacheEntryAvailable;
+ uint32_t mCacheExpirationTime;
+ nsCString mCachedCharset;
+ nsCOMPtr<nsISupports> mCacheKey;
+
+ nsCString mProtocolVersion;
+
+ // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
+ bool mSendResumeAt;
+
+ bool mIPCOpen;
+ bool mKeptAlive; // IPC kept open, but only for security info
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ // If nsUnknownDecoder is involved OnStartRequest call will be delayed and
+ // this queue keeps OnDataAvailable data until OnStartRequest is finally
+ // called.
+ nsTArray<UniquePtr<ChannelEvent>> mUnknownDecoderEventQ;
+ bool mUnknownDecoderInvolved;
+
+ // Once set, OnData and possibly OnStop will be diverted to the parent.
+ bool mDivertingToParent;
+ // Once set, no OnStart/OnData/OnStop callbacks should be received from the
+ // parent channel, nor dequeued from the ChannelEventQueue.
+ bool mFlushedForDiversion;
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ bool mSuspendSent;
+
+ // Set if a response was synthesized, indicating that any forthcoming redirects
+ // should be intercepted.
+ bool mSynthesizedResponse;
+
+ // Set if a synthesized response should cause us to explictly allows intercepting
+ // an expected forthcoming redirect.
+ bool mShouldInterceptSubsequentRedirect;
+ // Set if a redirection is being initiated to facilitate providing a synthesized
+ // response to a channel using a different principal than the current one.
+ bool mRedirectingForSubsequentSynthesizedResponse;
+
+ // Set if a manual redirect mode channel needs to be intercepted in the
+ // parent.
+ bool mPostRedirectChannelShouldIntercept;
+ // Set if a manual redirect mode channel needs to be upgraded to a secure URI
+ // when it's being considered for interception. Can only be true if
+ // mPostRedirectChannelShouldIntercept is true.
+ bool mPostRedirectChannelShouldUpgrade;
+
+ // Set if the corresponding parent channel should force an interception to occur
+ // before the network transaction is initiated.
+ bool mShouldParentIntercept;
+
+ // Set if the corresponding parent channel should suspend after a response
+ // is synthesized.
+ bool mSuspendParentAfterSynthesizeResponse;
+
+ // Needed to call AsyncOpen in FinishInterceptedRedirect
+ nsCOMPtr<nsIStreamListener> mInterceptedRedirectListener;
+ nsCOMPtr<nsISupports> mInterceptedRedirectContext;
+ // Needed to call CleanupRedirectingChannel in FinishInterceptedRedirect
+ RefPtr<HttpChannelChild> mInterceptingChannel;
+ // Used to call OverrideWithSynthesizedResponse in FinishInterceptedRedirect
+ RefPtr<OverrideRunnable> mOverrideRunnable;
+
+ void FinishInterceptedRedirect();
+ void CleanupRedirectingChannel(nsresult rv);
+
+ // true after successful AsyncOpen until OnStopRequest completes.
+ bool RemoteChannelExists() { return mIPCOpen && !mKeptAlive; }
+
+ void AssociateApplicationCache(const nsCString &groupID,
+ const nsCString &clientID);
+ void OnStartRequest(const nsresult& channelStatus,
+ const nsHttpResponseHead& responseHead,
+ const bool& useResponseHead,
+ const nsHttpHeaderArray& requestHeaders,
+ const bool& isFromCache,
+ const bool& cacheEntryAvailable,
+ const uint32_t& cacheExpirationTime,
+ const nsCString& cachedCharset,
+ const nsCString& securityInfoSerialization,
+ const NetAddr& selfAddr,
+ const NetAddr& peerAddr,
+ const uint32_t& cacheKey,
+ const nsCString& altDataType);
+ void MaybeDivertOnData(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void OnTransportAndData(const nsresult& channelStatus,
+ const nsresult& status,
+ const uint64_t progress,
+ const uint64_t& progressMax,
+ const uint64_t& offset,
+ const uint32_t& count,
+ const nsCString& data);
+ void OnStopRequest(const nsresult& channelStatus, const ResourceTimingStruct& timing);
+ void MaybeDivertOnStop(const nsresult& aChannelStatus);
+ void OnProgress(const int64_t& progress, const int64_t& progressMax);
+ void OnStatus(const nsresult& status);
+ void FailedAsyncOpen(const nsresult& status);
+ void HandleAsyncAbort();
+ void Redirect1Begin(const uint32_t& registrarId,
+ const URIParams& newUri,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsACString& securityInfoSerialization,
+ const nsACString& channelId);
+ bool Redirect3Complete(OverrideRunnable* aRunnable);
+ void DeleteSelf();
+
+ // Create a a new channel to be used in a redirection, based on the provided
+ // response headers.
+ nsresult SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel);
+
+ // Perform a redirection without communicating with the parent process at all.
+ void BeginNonIPCRedirect(nsIURI* responseURI,
+ const nsHttpResponseHead* responseHead);
+
+ // Override the default security info pointer during a non-IPC redirection.
+ void OverrideSecurityInfoForNonIPCRedirect(nsISupports* securityInfo);
+
+ friend class AssociateApplicationCacheEvent;
+ friend class StartRequestEvent;
+ friend class StopRequestEvent;
+ friend class TransportAndDataEvent;
+ friend class MaybeDivertOnDataHttpEvent;
+ friend class MaybeDivertOnStopHttpEvent;
+ friend class ProgressEvent;
+ friend class StatusEvent;
+ friend class FailedAsyncOpenEvent;
+ friend class Redirect1Event;
+ friend class Redirect3Event;
+ friend class DeleteSelfEvent;
+ friend class HttpAsyncAborter<HttpChannelChild>;
+ friend class InterceptStreamListener;
+ friend class InterceptedChannelContent;
+};
+
+// A stream listener interposed between the nsInputStreamPump used for intercepted channels
+// and this channel's original listener. This is only used to ensure the original listener
+// sees the channel as the request object, and to synthesize OnStatus and OnProgress notifications.
+class InterceptStreamListener : public nsIStreamListener
+ , public nsIProgressEventSink
+{
+ RefPtr<HttpChannelChild> mOwner;
+ nsCOMPtr<nsISupports> mContext;
+ virtual ~InterceptStreamListener() {}
+ public:
+ InterceptStreamListener(HttpChannelChild* aOwner, nsISupports* aContext)
+ : mOwner(aOwner)
+ , mContext(aContext)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+ void Cleanup();
+};
+
+//-----------------------------------------------------------------------------
+// inline functions
+//-----------------------------------------------------------------------------
+
+inline bool
+HttpChannelChild::IsSuspended()
+{
+ return mSuspendCount != 0;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpChannelChild_h
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
new file mode 100644
index 0000000000..51da1ec8ca
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -0,0 +1,1821 @@
+/* -*- 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/ipc/FileDescriptorSetParent.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "HttpChannelParentListener.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPriority.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsSerializationHelper.h"
+#include "nsISerializable.h"
+#include "nsIAssociatedContentSecurity.h"
+#include "nsIApplicationCacheService.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "nsIAuthInformation.h"
+#include "nsIAuthPromptCallback.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsICachingChannel.h"
+#include "mozilla/LoadInfo.h"
+#include "nsQueryObject.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsCORSListenerProxy.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsIDocument.h"
+#include "nsStringStream.h"
+
+using mozilla::BasePrincipal;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mIPCClosed(false)
+ , mStoredStatus(NS_OK)
+ , mStoredProgress(0)
+ , mStoredProgressMax(0)
+ , mSentRedirect1Begin(false)
+ , mSentRedirect1BeginFailed(false)
+ , mReceivedRedirect2Verify(false)
+ , mPBOverride(aOverrideStatus)
+ , mLoadContext(aLoadContext)
+ , mStatus(NS_OK)
+ , mPendingDiversion(false)
+ , mDivertingFromChild(false)
+ , mDivertedOnStartRequest(false)
+ , mSuspendedForDiversion(false)
+ , mSuspendAfterSynthesizeResponse(false)
+ , mWillSynthesizeResponse(false)
+ , mNestedFrameId(0)
+{
+ 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;
+
+ if (iframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
+ mTabParent = static_cast<dom::TabParent*>(iframeEmbedding.get_PBrowserParent());
+ } else {
+ mNestedFrameId = iframeEmbedding.get_TabId();
+ }
+
+ mEventQ = new ChannelEventQueue(static_cast<nsIParentRedirectingChannel*>(this));
+}
+
+HttpChannelParent::~HttpChannelParent()
+{
+ LOG(("Destroying HttpChannelParent [this=%p]\n", this));
+}
+
+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;
+
+ // If this is an intercepted channel, we need to make sure that any resources are
+ // cleaned up to avoid leaks.
+ if (mParentListener) {
+ mParentListener->ClearInterceptedChannel();
+ }
+}
+
+bool
+HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs)
+{
+ LOG(("HttpChannelParent::Init [this=%p]\n", this));
+ switch (aArgs.type()) {
+ case HttpChannelCreationArgs::THttpChannelOpenArgs:
+ {
+ const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
+ return DoAsyncOpen(a.uri(), a.original(), a.doc(), a.referrer(),
+ a.referrerPolicy(), a.apiRedirectTo(), a.topWindowURI(),
+ a.loadFlags(), a.requestHeaders(),
+ a.requestMethod(), a.uploadStream(),
+ a.uploadStreamHasHeaders(), a.priority(), a.classOfService(),
+ a.redirectionLimit(), a.allowPipelining(), a.allowSTS(),
+ a.thirdPartyFlags(), a.resumeAt(), a.startPos(),
+ a.entityID(), a.chooseApplicationCache(),
+ a.appCacheClientID(), a.allowSpdy(), a.allowAltSvc(), a.beConservative(),
+ a.loadInfo(), a.synthesizedResponseHead(),
+ a.synthesizedSecurityInfoSerialization(),
+ a.cacheKey(), a.requestContextID(), a.preflightArgs(),
+ a.initialRwin(), a.blockAuthPrompt(),
+ a.suspendAfterSynthesizeResponse(),
+ a.allowStaleCacheContent(), a.contentTypeHint(),
+ a.channelId(), a.contentWindowId(), a.preferredAlternativeType());
+ }
+ case HttpChannelCreationArgs::THttpChannelConnectArgs:
+ {
+ const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
+ return ConnectChannel(cArgs.registrarId(), cArgs.shouldIntercept());
+ }
+ default:
+ NS_NOTREACHED("unknown open type");
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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_AMBIGUOUS(nsISupports, nsIParentRedirectingChannel)
+ if (aIID.Equals(NS_GET_IID(HttpChannelParent))) {
+ foundInterface = static_cast<nsIInterfaceRequestor*>(this);
+ } else
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::GetInterface(const nsIID& aIID, void **result)
+{
+ if (aIID.Equals(NS_GET_IID(nsIAuthPromptProvider)) ||
+ aIID.Equals(NS_GET_IID(nsISecureBrowserUI))) {
+ if (mTabParent) {
+ return mTabParent->QueryInterface(aIID, result);
+ }
+ }
+
+ // Only support nsIAuthPromptProvider in Content process
+ if (XRE_IsParentProcess() &&
+ aIID.Equals(NS_GET_IID(nsIAuthPromptProvider))) {
+ *result = nullptr;
+ 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;
+ }
+
+ if (mTabParent && aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ nsCOMPtr<Element> frameElement = mTabParent->GetOwnerElement();
+ if (frameElement) {
+ nsCOMPtr<nsPIDOMWindowOuter> win =frameElement->OwnerDoc()->GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+
+ if (NS_WARN_IF(!NS_SUCCEEDED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrompt> prompt;
+ rv = wwatch->GetNewPrompter(win, getter_AddRefs(prompt));
+ if (NS_WARN_IF(!NS_SUCCEEDED(rv))) {
+ return rv;
+ }
+
+ prompt.forget(result);
+ return NS_OK;
+ }
+ }
+
+ return QueryInterface(aIID, result);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::PHttpChannelParent
+//-----------------------------------------------------------------------------
+
+void
+HttpChannelParent::InvokeAsyncOpen(nsresult rv)
+{
+ if (NS_FAILED(rv)) {
+ Unused << SendFailedAsyncOpen(rv);
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ Unused << SendFailedAsyncOpen(rv);
+ return;
+ }
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ rv = mChannel->AsyncOpen2(mParentListener);
+ }
+ else {
+ rv = mChannel->AsyncOpen(mParentListener, nullptr);
+ }
+ if (NS_FAILED(rv)) {
+ Unused << SendFailedAsyncOpen(rv);
+ }
+}
+
+namespace {
+class InvokeAsyncOpen : public Runnable
+{
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mChannel;
+ nsresult mStatus;
+public:
+ InvokeAsyncOpen(const nsMainThreadPtrHandle<nsIInterfaceRequestor>& aChannel,
+ nsresult aStatus)
+ : mChannel(aChannel)
+ , mStatus(aStatus)
+ {
+ }
+
+ NS_IMETHOD Run()
+ {
+ RefPtr<HttpChannelParent> channel = do_QueryObject(mChannel.get());
+ channel->InvokeAsyncOpen(mStatus);
+ return NS_OK;
+ }
+};
+
+struct UploadStreamClosure {
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mChannel;
+
+ explicit UploadStreamClosure(const nsMainThreadPtrHandle<nsIInterfaceRequestor>& aChannel)
+ : mChannel(aChannel)
+ {
+ }
+};
+
+void
+UploadCopyComplete(void* aClosure, nsresult aStatus) {
+ // Called on the Stream Transport Service thread by NS_AsyncCopy
+ MOZ_ASSERT(!NS_IsMainThread());
+ UniquePtr<UploadStreamClosure> closure(static_cast<UploadStreamClosure*>(aClosure));
+ nsCOMPtr<nsIRunnable> event = new InvokeAsyncOpen(closure->mChannel, aStatus);
+ NS_DispatchToMainThread(event);
+}
+} // anonymous namespace
+
+bool
+HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
+ const OptionalURIParams& aOriginalURI,
+ const OptionalURIParams& aDocURI,
+ const OptionalURIParams& aReferrerURI,
+ const uint32_t& aReferrerPolicy,
+ const OptionalURIParams& aAPIRedirectToURI,
+ const OptionalURIParams& aTopWindowURI,
+ const uint32_t& aLoadFlags,
+ const RequestHeaderTuples& requestHeaders,
+ const nsCString& requestMethod,
+ const OptionalIPCStream& uploadStream,
+ const bool& uploadStreamHasHeaders,
+ const uint16_t& priority,
+ const uint32_t& classOfService,
+ const uint8_t& redirectionLimit,
+ const bool& allowPipelining,
+ 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& allowAltSvc,
+ const bool& beConservative,
+ const OptionalLoadInfoArgs& aLoadInfoArgs,
+ const OptionalHttpResponseHead& aSynthesizedResponseHead,
+ const nsCString& aSecurityInfoSerialization,
+ const uint32_t& aCacheKey,
+ const nsCString& aRequestContextID,
+ const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+ const uint32_t& aInitialRwin,
+ const bool& aBlockAuthPrompt,
+ const bool& aSuspendAfterSynthesizeResponse,
+ const bool& aAllowStaleCacheContent,
+ const nsCString& aContentTypeHint,
+ const nsCString& aChannelId,
+ const uint64_t& aContentWindowId,
+ const nsCString& aPreferredAlternativeType)
+{
+ 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> referrerUri = DeserializeURI(aReferrerURI);
+ nsCOMPtr<nsIURI> apiRedirectToUri = DeserializeURI(aAPIRedirectToURI);
+ nsCOMPtr<nsIURI> topWindowUri = DeserializeURI(aTopWindowURI);
+
+ LOG(("HttpChannelParent RecvAsyncOpen [this=%p uri=%s]\n",
+ this, uri->GetSpecOrDefault().get()));
+
+ 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);
+ }
+
+ NeckoOriginAttributes attrs;
+ rv = loadInfo->GetOriginAttributes(&attrs);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannelInternal(getter_AddRefs(channel), uri, loadInfo,
+ nullptr, nullptr, aLoadFlags, ios);
+
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ // This cast is safe since this is AsyncOpen specific to http. channel
+ // is ensured to be nsHttpChannel.
+ mChannel = static_cast<nsHttpChannel *>(channel.get());
+
+ // Set the channelId allocated in child to the parent instance
+ mChannel->SetChannelId(aChannelId);
+ mChannel->SetTopLevelContentWindowId(aContentWindowId);
+
+ mChannel->SetWarningReporter(this);
+ mChannel->SetTimingEnabled(true);
+ if (mPBOverride != kPBOverride_Unset) {
+ mChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
+ }
+
+ if (doResumeAt)
+ mChannel->ResumeAt(startPos, entityID);
+
+ if (originalUri)
+ mChannel->SetOriginalURI(originalUri);
+ if (docUri)
+ mChannel->SetDocumentURI(docUri);
+ if (referrerUri)
+ mChannel->SetReferrerWithPolicyInternal(referrerUri, aReferrerPolicy);
+ if (apiRedirectToUri)
+ mChannel->RedirectTo(apiRedirectToUri);
+ if (topWindowUri)
+ mChannel->SetTopWindowURI(topWindowUri);
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL)
+ mChannel->SetLoadFlags(aLoadFlags);
+
+ for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
+ if (requestHeaders[i].mEmpty) {
+ mChannel->SetEmptyRequestHeader(requestHeaders[i].mHeader);
+ } else {
+ mChannel->SetRequestHeader(requestHeaders[i].mHeader,
+ requestHeaders[i].mValue,
+ requestHeaders[i].mMerge);
+ }
+ }
+
+ mParentListener = new HttpChannelParentListener(this);
+
+ mChannel->SetNotificationCallbacks(mParentListener);
+
+ mChannel->SetRequestMethod(nsDependentCString(requestMethod.get()));
+
+ if (aCorsPreflightArgs.type() == OptionalCorsPreflightArgs::TCorsPreflightArgs) {
+ const CorsPreflightArgs& args = aCorsPreflightArgs.get_CorsPreflightArgs();
+ mChannel->SetCorsPreflightParameters(args.unsafeHeaders());
+ }
+
+ bool delayAsyncOpen = false;
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream);
+ if (stream) {
+ // FIXME: The fast path of using the existing stream currently only applies to streams
+ // that have had their entire contents serialized from the child at this point.
+ // Once bug 1294446 and bug 1294450 are fixed it is worth revisiting this heuristic.
+ nsCOMPtr<nsIIPCSerializableInputStream> completeStream = do_QueryInterface(stream);
+ if (!completeStream) {
+ delayAsyncOpen = true;
+
+ // buffer size matches PSendStream transfer size.
+ const uint32_t kBufferSize = 32768;
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ nsresult rv = NS_NewStorageStream(kBufferSize, UINT32_MAX,
+ getter_AddRefs(storageStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIInputStream> newUploadStream;
+ rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIOutputStream> sink;
+ rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !target) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> iir = static_cast<nsIInterfaceRequestor*>(this);
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> handle =
+ nsMainThreadPtrHandle<nsIInterfaceRequestor>(
+ new nsMainThreadPtrHolder<nsIInterfaceRequestor>(iir));
+ UniquePtr<UploadStreamClosure> closure(new UploadStreamClosure(handle));
+
+ // Accumulate the stream contents as the child sends it. We will continue with
+ // the AsyncOpen process once the full stream has been received.
+ rv = NS_AsyncCopy(stream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ kBufferSize, // copy segment size
+ UploadCopyComplete, closure.release());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ mChannel->InternalSetUploadStream(newUploadStream);
+ } else {
+ mChannel->InternalSetUploadStream(stream);
+ }
+ mChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
+ }
+
+ if (aSynthesizedResponseHead.type() == OptionalHttpResponseHead::TnsHttpResponseHead) {
+ mParentListener->SetupInterception(aSynthesizedResponseHead.get_nsHttpResponseHead());
+ mWillSynthesizeResponse = true;
+ mChannel->SetCouldBeSynthesized();
+
+ if (!aSecurityInfoSerialization.IsEmpty()) {
+ nsCOMPtr<nsISupports> secInfo;
+ NS_DeserializeObject(aSecurityInfoSerialization, getter_AddRefs(secInfo));
+ mChannel->OverrideSecurityInfo(secInfo);
+ }
+ } else {
+ nsLoadFlags newLoadFlags;
+ mChannel->GetLoadFlags(&newLoadFlags);
+ newLoadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ mChannel->SetLoadFlags(newLoadFlags);
+ }
+
+ nsCOMPtr<nsISupportsPRUint32> cacheKey =
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ rv = cacheKey->SetData(aCacheKey);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ mChannel->SetCacheKey(cacheKey);
+ mChannel->PreferAlternativeDataType(aPreferredAlternativeType);
+
+ mChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent);
+
+ mChannel->SetContentType(aContentTypeHint);
+
+ if (priority != nsISupportsPriority::PRIORITY_NORMAL) {
+ mChannel->SetPriority(priority);
+ }
+ if (classOfService) {
+ mChannel->SetClassFlags(classOfService);
+ }
+ mChannel->SetRedirectionLimit(redirectionLimit);
+ mChannel->SetAllowPipelining(allowPipelining);
+ mChannel->SetAllowSTS(allowSTS);
+ mChannel->SetThirdPartyFlags(thirdPartyFlags);
+ mChannel->SetAllowSpdy(allowSpdy);
+ mChannel->SetAllowAltSvc(allowAltSvc);
+ mChannel->SetBeConservative(beConservative);
+ mChannel->SetInitialRwin(aInitialRwin);
+ mChannel->SetBlockAuthPrompt(aBlockAuthPrompt);
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
+ do_QueryObject(mChannel);
+ 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) {
+ NeckoOriginAttributes neckoAttrs;
+ NS_GetOriginAttributes(mChannel, neckoAttrs);
+
+ PrincipalOriginAttributes attrs;
+ attrs.InheritFromNecko(neckoAttrs);
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+
+ bool chooseAppCache = false;
+ // This works because we've already called SetNotificationCallbacks and
+ // done mPBOverride logic by this point.
+ chooseAppCache = NS_ShouldCheckAppCache(principal, NS_UsePrivateBrowsing(mChannel));
+
+ appCacheChan->SetChooseApplicationCache(chooseAppCache);
+ }
+ }
+
+ nsID requestContextID;
+ requestContextID.Parse(aRequestContextID.BeginReading());
+ mChannel->SetRequestContextID(requestContextID);
+
+ mSuspendAfterSynthesizeResponse = aSuspendAfterSynthesizeResponse;
+
+ if (!delayAsyncOpen) {
+ InvokeAsyncOpen(NS_OK);
+ }
+
+ return true;
+}
+
+bool
+HttpChannelParent::ConnectChannel(const uint32_t& registrarId, const bool& shouldIntercept)
+{
+ nsresult rv;
+
+ LOG(("HttpChannelParent::ConnectChannel: Looking for a registered channel "
+ "[this=%p, id=%lu]\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;
+ }
+
+ // It's safe to cast here since the found parent-side real channel is ensured
+ // to be http (nsHttpChannel). ConnectChannel called from HttpChannelParent::Init
+ // can only be called for http channels. It's bound by ipdl.
+ mChannel = static_cast<nsHttpChannel*>(channel.get());
+ LOG((" found channel %p, rv=%08x", mChannel.get(), rv));
+
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ NS_QueryNotificationCallbacks(channel, controller);
+ RefPtr<HttpChannelParentListener> parentListener = do_QueryObject(controller);
+ MOZ_ASSERT(parentListener);
+ parentListener->SetupInterceptionAfterRedirect(shouldIntercept);
+
+ 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);
+ }
+ }
+
+ return true;
+}
+
+bool
+HttpChannelParent::RecvSetPriority(const uint16_t& priority)
+{
+ LOG(("HttpChannelParent::RecvSetPriority [this=%p, priority=%u]\n",
+ this, priority));
+
+ if (mChannel) {
+ mChannel->SetPriority(priority);
+ }
+
+ nsCOMPtr<nsISupportsPriority> priorityRedirectChannel =
+ do_QueryInterface(mRedirectChannel);
+ if (priorityRedirectChannel)
+ priorityRedirectChannel->SetPriority(priority);
+
+ return true;
+}
+
+bool
+HttpChannelParent::RecvSetClassOfService(const uint32_t& cos)
+{
+ if (mChannel) {
+ mChannel->SetClassFlags(cos);
+ }
+ return true;
+}
+
+bool
+HttpChannelParent::RecvSuspend()
+{
+ LOG(("HttpChannelParent::RecvSuspend [this=%p]\n", this));
+
+ if (mChannel) {
+ mChannel->Suspend();
+ }
+ return true;
+}
+
+bool
+HttpChannelParent::RecvResume()
+{
+ LOG(("HttpChannelParent::RecvResume [this=%p]\n", this));
+
+ if (mChannel) {
+ mChannel->Resume();
+ }
+ return true;
+}
+
+bool
+HttpChannelParent::RecvCancel(const nsresult& status)
+{
+ LOG(("HttpChannelParent::RecvCancel [this=%p]\n", this));
+
+ // May receive cancel before channel has been constructed!
+ if (mChannel) {
+ mChannel->Cancel(status);
+ }
+ return true;
+}
+
+
+bool
+HttpChannelParent::RecvSetCacheTokenCachedCharset(const nsCString& charset)
+{
+ if (mCacheEntry)
+ mCacheEntry->SetMetaDataElement("charset", charset.get());
+ return true;
+}
+
+bool
+HttpChannelParent::RecvUpdateAssociatedContentSecurity(const int32_t& broken,
+ const int32_t& no)
+{
+ if (mAssociatedContentSecurity) {
+ mAssociatedContentSecurity->SetCountSubRequestsBrokenSecurity(broken);
+ mAssociatedContentSecurity->SetCountSubRequestsNoSecurity(no);
+ }
+ return true;
+}
+
+bool
+HttpChannelParent::RecvRedirect2Verify(const nsresult& result,
+ const RequestHeaderTuples& changedHeaders,
+ const uint32_t& loadFlags,
+ const OptionalURIParams& aAPIRedirectURI,
+ const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+ const bool& aForceHSTSPriming,
+ const bool& aMixedContentWouldBlock,
+ const bool& aChooseAppcache)
+{
+ LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%x]\n",
+ this, result));
+ nsresult rv;
+ if (NS_SUCCEEDED(result)) {
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannel);
+
+ if (newHttpChannel) {
+ nsCOMPtr<nsIURI> apiRedirectUri = DeserializeURI(aAPIRedirectURI);
+
+ if (apiRedirectUri)
+ newHttpChannel->RedirectTo(apiRedirectUri);
+
+ for (uint32_t i = 0; i < changedHeaders.Length(); i++) {
+ if (changedHeaders[i].mEmpty) {
+ newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader);
+ } else {
+ newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader,
+ changedHeaders[i].mValue,
+ changedHeaders[i].mMerge);
+ }
+ }
+
+ // 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.type() == OptionalCorsPreflightArgs::TCorsPreflightArgs) {
+ nsCOMPtr<nsIHttpChannelInternal> newInternalChannel =
+ do_QueryInterface(newHttpChannel);
+ MOZ_RELEASE_ASSERT(newInternalChannel);
+ const CorsPreflightArgs& args = aCorsPreflightArgs.get_CorsPreflightArgs();
+ newInternalChannel->SetCorsPreflightParameters(args.unsafeHeaders());
+ }
+
+ if (aForceHSTSPriming) {
+ nsCOMPtr<nsILoadInfo> newLoadInfo;
+ rv = newHttpChannel->GetLoadInfo(getter_AddRefs(newLoadInfo));
+ if (NS_SUCCEEDED(rv) && newLoadInfo) {
+ newLoadInfo->SetHSTSPriming(aMixedContentWouldBlock);
+ }
+ }
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(newHttpChannel);
+ if (appCacheChannel) {
+ appCacheChannel->SetChooseApplicationCache(aChooseAppcache);
+ }
+ }
+ }
+
+ 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 (mSentRedirect1Begin && NS_FAILED(result))
+ LOG(("RecvRedirect2Verify[%p]: Redirect failed", this));
+ if (mSentRedirect1Begin && NS_SUCCEEDED(result))
+ 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::RecvRedirect2Verify call OnRedirectVerifyCallback"
+ " [this=%p result=%x, mRedirectCallback=%p]\n",
+ this, result, mRedirectCallback.get()));
+ mRedirectCallback->OnRedirectVerifyCallback(result);
+ mRedirectCallback = nullptr;
+ }
+
+ return true;
+}
+
+bool
+HttpChannelParent::RecvDocumentChannelCleanup()
+{
+ // From now on only using mAssociatedContentSecurity. Free everything else.
+ mChannel = nullptr; // Reclaim some memory sooner.
+ mCacheEntry = nullptr; // Else we'll block other channels reading same URI
+ return true;
+}
+
+bool
+HttpChannelParent::RecvMarkOfflineCacheEntryAsForeign()
+{
+ if (mOfflineForeignMarker) {
+ mOfflineForeignMarker->MarkAsForeign();
+ mOfflineForeignMarker = 0;
+ }
+
+ return true;
+}
+
+class DivertDataAvailableEvent : public ChannelEvent
+{
+public:
+ DivertDataAvailableEvent(HttpChannelParent* aParent,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mParent(aParent)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count)
+ {
+ }
+
+ void Run()
+ {
+ mParent->DivertOnDataAvailable(mData, mOffset, mCount);
+ }
+
+private:
+ HttpChannelParent* mParent;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+bool
+HttpChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("HttpChannelParent::RecvDivertOnDataAvailable [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertOnDataAvailable if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ // Drop OnDataAvailables if the parent was canceled already.
+ if (NS_FAILED(mStatus)) {
+ return true;
+ }
+
+ mEventQ->RunOrEnqueue(new DivertDataAvailableEvent(this, data, offset,
+ count));
+ return true;
+}
+
+void
+HttpChannelParent::DivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("HttpChannelParent::DivertOnDataAvailable [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertOnDataAvailable if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Drop OnDataAvailables if the parent was canceled already.
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
+ count, NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ rv = mParentListener->OnDataAvailable(mChannel, nullptr, stringStream,
+ offset, count);
+ stringStream->Close();
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ }
+}
+
+class DivertStopRequestEvent : public ChannelEvent
+{
+public:
+ DivertStopRequestEvent(HttpChannelParent* aParent,
+ const nsresult& statusCode)
+ : mParent(aParent)
+ , mStatusCode(statusCode)
+ {
+ }
+
+ void Run() {
+ mParent->DivertOnStopRequest(mStatusCode);
+ }
+
+private:
+ HttpChannelParent* mParent;
+ nsresult mStatusCode;
+};
+
+bool
+HttpChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
+{
+ LOG(("HttpChannelParent::RecvDivertOnStopRequest [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertOnStopRequest if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new DivertStopRequestEvent(this, statusCode));
+ return true;
+}
+
+void
+HttpChannelParent::DivertOnStopRequest(const nsresult& statusCode)
+{
+ LOG(("HttpChannelParent::DivertOnStopRequest [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertOnStopRequest if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Honor the channel's status even if the underlying transaction completed.
+ nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
+
+ // Reset fake pending status in case OnStopRequest has already been called.
+ if (mChannel) {
+ mChannel->ForcePending(false);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mParentListener->OnStopRequest(mChannel, nullptr, status);
+}
+
+class DivertCompleteEvent : public ChannelEvent
+{
+public:
+ explicit DivertCompleteEvent(HttpChannelParent* aParent)
+ : mParent(aParent)
+ {
+ }
+
+ void Run() {
+ mParent->DivertComplete();
+ }
+
+private:
+ HttpChannelParent* mParent;
+};
+
+bool
+HttpChannelParent::RecvDivertComplete()
+{
+ LOG(("HttpChannelParent::RecvDivertComplete [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertComplete if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new DivertCompleteEvent(this));
+ return true;
+}
+
+void
+HttpChannelParent::DivertComplete()
+{
+ LOG(("HttpChannelParent::DivertComplete [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertComplete if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ nsresult rv = ResumeForDiversion();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ mParentListener = nullptr;
+}
+
+void
+HttpChannelParent::MaybeFlushPendingDiversion()
+{
+ if (!mPendingDiversion) {
+ return;
+ }
+
+ mPendingDiversion = false;
+
+ nsresult rv = SuspendForDiversion();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (mDivertListener) {
+ DivertTo(mDivertListener);
+ }
+
+ return;
+}
+
+void
+HttpChannelParent::ResponseSynthesized()
+{
+ // Suspend now even though the FinishSynthesizeResponse runnable has
+ // not executed. We want to suspend after we get far enough to trigger
+ // the synthesis, but not actually allow the nsHttpChannel to trigger
+ // any OnStartRequests().
+ if (mSuspendAfterSynthesizeResponse) {
+ mChannel->Suspend();
+ }
+
+ mWillSynthesizeResponse = false;
+
+ MaybeFlushPendingDiversion();
+}
+
+bool
+HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(const URIParams& uri,
+ const mozilla::ipc::PrincipalInfo& requestingPrincipal)
+{
+ nsCOMPtr<nsIURI> deserializedURI = DeserializeURI(uri);
+ if (!deserializedURI) {
+ return false;
+ }
+ nsCOMPtr<nsIPrincipal> principal =
+ PrincipalInfoToPrincipal(requestingPrincipal);
+ if (!principal) {
+ return false;
+ }
+ nsCORSListenerProxy::RemoveFromCorsPreflightCache(deserializedURI,
+ principal);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n",
+ this, aRequest));
+
+ MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+ "Cannot call OnStartRequest if diverting is set!");
+
+ // We can't cast here since the new channel can be a redirect to a different
+ // schema. We must query the channel implementation through a special method.
+ nsHttpChannel *chan = nullptr;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(do_QueryInterface(aRequest));
+ if (httpChannelInternal) {
+ chan = httpChannelInternal->QueryHttpChannelImpl();
+ }
+
+ if (!chan) {
+ LOG((" aRequest is not nsHttpChannel"));
+ NS_ERROR("Expecting only nsHttpChannel as aRequest in HttpChannelParent::OnStartRequest");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mChannel == chan,
+ "HttpChannelParent getting OnStartRequest from a different nsHttpChannel instance");
+
+ nsHttpResponseHead *responseHead = chan->GetResponseHead();
+ nsHttpRequestHead *requestHead = chan->GetRequestHead();
+ bool isFromCache = false;
+ chan->IsFromCache(&isFromCache);
+ uint32_t expirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
+ chan->GetCacheTokenExpirationTime(&expirationTime);
+ nsCString cachedCharset;
+ chan->GetCacheTokenCachedCharset(cachedCharset);
+
+ bool loadedFromApplicationCache;
+ chan->GetLoadedFromApplicationCache(&loadedFromApplicationCache);
+ if (loadedFromApplicationCache) {
+ mOfflineForeignMarker = chan->GetOfflineCacheEntryAsForeignMarker();
+ nsCOMPtr<nsIApplicationCache> appCache;
+ chan->GetApplicationCache(getter_AddRefs(appCache));
+ nsCString appCacheGroupId;
+ nsCString appCacheClientId;
+ appCache->GetGroupID(appCacheGroupId);
+ appCache->GetClientID(appCacheClientId);
+ if (mIPCClosed ||
+ !SendAssociateApplicationCache(appCacheGroupId, appCacheClientId))
+ {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(aRequest);
+ if (encodedChannel)
+ encodedChannel->SetApplyConversion(false);
+
+ // Keep the cache entry for future use in RecvSetCacheTokenCachedCharset().
+ // It could be already released by nsHttpChannel at that time.
+ nsCOMPtr<nsISupports> cacheEntry;
+ chan->GetCacheToken(getter_AddRefs(cacheEntry));
+ mCacheEntry = do_QueryInterface(cacheEntry);
+
+ nsresult channelStatus = NS_OK;
+ chan->GetStatus(&channelStatus);
+
+ nsCString secInfoSerialization;
+ UpdateAndSerializeSecurityInfo(secInfoSerialization);
+
+ uint16_t redirectCount = 0;
+ chan->GetRedirectCount(&redirectCount);
+
+ nsCOMPtr<nsISupports> cacheKey;
+ chan->GetCacheKey(getter_AddRefs(cacheKey));
+ uint32_t cacheKeyValue = 0;
+ if (cacheKey) {
+ nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(cacheKey);
+ if (!container) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv = container->GetData(&cacheKeyValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ nsAutoCString altDataType;
+ chan->GetAlternativeDataType(altDataType);
+
+ // !!! We need to lock headers and please don't forget to unlock them !!!
+ requestHead->Enter();
+ nsresult rv = NS_OK;
+ if (mIPCClosed ||
+ !SendOnStartRequest(channelStatus,
+ responseHead ? *responseHead : nsHttpResponseHead(),
+ !!responseHead,
+ requestHead->Headers(),
+ isFromCache,
+ mCacheEntry ? true : false,
+ expirationTime, cachedCharset, secInfoSerialization,
+ chan->GetSelfAddr(), chan->GetPeerAddr(),
+ redirectCount,
+ cacheKeyValue,
+ altDataType))
+ {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ requestHead->Exit();
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ LOG(("HttpChannelParent::OnStopRequest: [this=%p aRequest=%p status=%x]\n",
+ this, aRequest, aStatusCode));
+
+ MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+ "Cannot call OnStopRequest if diverting is set!");
+ ResourceTimingStruct timing;
+ mChannel->GetDomainLookupStart(&timing.domainLookupStart);
+ mChannel->GetDomainLookupEnd(&timing.domainLookupEnd);
+ mChannel->GetConnectStart(&timing.connectStart);
+ mChannel->GetConnectEnd(&timing.connectEnd);
+ mChannel->GetRequestStart(&timing.requestStart);
+ mChannel->GetResponseStart(&timing.responseStart);
+ mChannel->GetResponseEnd(&timing.responseEnd);
+ mChannel->GetAsyncOpen(&timing.fetchStart);
+ mChannel->GetRedirectStart(&timing.redirectStart);
+ mChannel->GetRedirectEnd(&timing.redirectEnd);
+ mChannel->GetTransferSize(&timing.transferSize);
+ mChannel->GetEncodedBodySize(&timing.encodedBodySize);
+ // decodedBodySize can be computed in the child process so it doesn't need
+ // to be passed down.
+ mChannel->GetProtocolVersion(timing.protocolVersion);
+
+ mChannel->GetCacheReadStart(&timing.cacheReadStart);
+ mChannel->GetCacheReadEnd(&timing.cacheReadEnd);
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode, timing))
+ return NS_ERROR_UNEXPECTED;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ LOG(("HttpChannelParent::OnDataAvailable [this=%p aRequest=%p]\n",
+ this, aRequest));
+
+ MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+ "Cannot call OnDataAvailable if diverting is set!");
+
+ nsresult channelStatus = NS_OK;
+ mChannel->GetStatus(&channelStatus);
+
+ static uint32_t const kCopyChunkSize = 128 * 1024;
+ uint32_t toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+
+ nsCString data;
+ if (!data.SetCapacity(toRead, fallible)) {
+ LOG((" out of memory!"));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ while (aCount) {
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, toRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // OnDataAvailable is always preceded by OnStatus/OnProgress calls that set
+ // mStoredStatus/mStoredProgress(Max) to appropriate values, unless
+ // LOAD_BACKGROUND set. In that case, they'll have garbage values, but
+ // child doesn't use them.
+ if (mIPCClosed || !SendOnTransportAndData(channelStatus, mStoredStatus,
+ mStoredProgress, mStoredProgressMax,
+ aOffset, toRead, data)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ aOffset += toRead;
+ aCount -= toRead;
+ toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIProgressEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnProgress(nsIRequest *aRequest,
+ nsISupports *aContext,
+ int64_t aProgress,
+ int64_t aProgressMax)
+{
+ // OnStatus has always just set mStoredStatus. If it indicates this precedes
+ // OnDataAvailable, store and ODA will send to child.
+ if (mStoredStatus == NS_NET_STATUS_RECEIVING_FROM ||
+ mStoredStatus == NS_NET_STATUS_READING)
+ {
+ mStoredProgress = aProgress;
+ mStoredProgressMax = aProgressMax;
+ } else {
+ // 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 (mIPCClosed || !SendOnProgress(aProgress, aProgressMax))
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStatus(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus,
+ const char16_t *aStatusArg)
+{
+ // If this precedes OnDataAvailable, store and ODA will send to child.
+ if (aStatus == NS_NET_STATUS_RECEIVING_FROM ||
+ aStatus == NS_NET_STATUS_READING)
+ {
+ mStoredStatus = aStatus;
+ return NS_OK;
+ }
+ // Otherwise, send to child now
+ if (mIPCClosed || !SendOnStatus(aStatus))
+ return NS_ERROR_UNEXPECTED;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::SetParentListener(HttpChannelParentListener* 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::NotifyTrackingProtectionDisabled()
+{
+ if (!mIPCClosed)
+ Unused << SendNotifyTrackingProtectionDisabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::Delete()
+{
+ if (!mIPCClosed)
+ Unused << DoSendDeleteSelf();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIParentRedirectingChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::StartRedirect(uint32_t registrarId,
+ nsIChannel* newChannel,
+ uint32_t redirectFlags,
+ nsIAsyncVerifyRedirectCallback* callback)
+{
+ LOG(("HttpChannelParent::StartRedirect [this=%p, registrarId=%lu "
+ "newChannel=%p callback=%p]\n", this, registrarId, newChannel,
+ callback));
+
+ if (mIPCClosed)
+ return NS_BINDING_ABORTED;
+
+ nsCOMPtr<nsIURI> newURI;
+ newChannel->GetURI(getter_AddRefs(newURI));
+
+ URIParams uriParams;
+ SerializeURI(newURI, uriParams);
+
+ 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.
+ nsAutoCString channelId;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (httpChannel) {
+ nsresult rv = httpChannel->GetChannelId(channelId);
+ NS_ENSURE_SUCCESS(rv, NS_BINDING_ABORTED);
+ }
+
+ nsHttpResponseHead *responseHead = mChannel->GetResponseHead();
+ bool result = false;
+ if (!mIPCClosed) {
+ result = SendRedirect1Begin(registrarId, uriParams, redirectFlags,
+ responseHead ? *responseHead
+ : nsHttpResponseHead(),
+ secInfoSerialization,
+ channelId);
+ }
+ if (!result) {
+ // Bug 621446 investigation
+ mSentRedirect1BeginFailed = true;
+ return NS_BINDING_ABORTED;
+ }
+
+ // Bug 621446 investigation
+ mSentRedirect1Begin = true;
+
+ // 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 (succeeded && !mIPCClosed) {
+ // TODO: check return value: assume child dead if failed
+ Unused << SendRedirect3Complete();
+ }
+
+ mRedirectChannel = nullptr;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::ADivertableParentChannel
+//-----------------------------------------------------------------------------
+nsresult
+HttpChannelParent::SuspendForDiversion()
+{
+ LOG(("HttpChannelParent::SuspendForDiversion [this=%p]\n", this));
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mParentListener);
+
+ // If we're in the process of opening a synthesized response, we must wait
+ // to perform the diversion. Some of our diversion listeners clear callbacks
+ // which breaks the synthesis process.
+ if (mWillSynthesizeResponse) {
+ mPendingDiversion = true;
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(mDivertingFromChild)) {
+ MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // MessageDiversionStarted call will suspend mEventQ as many times as the
+ // channel has been suspended, so that channel and this queue are in sync.
+ mChannel->MessageDiversionStarted(this);
+
+ nsresult rv = NS_OK;
+
+ // Try suspending the channel. Allow it to fail, since OnStopRequest may have
+ // been called and thus the channel may not be pending. If we've already
+ // automatically suspended after synthesizing the response, then we don't
+ // need to suspend again here.
+ if (!mSuspendAfterSynthesizeResponse) {
+ // We need to suspend only nsHttpChannel (i.e. we should not suspend
+ // mEventQ). Therefore we call mChannel->SuspendInternal() and not
+ // mChannel->Suspend().
+ // We are suspending only nsHttpChannel here because we want to stop
+ // OnDataAvailable until diversion is over. At the same time we should
+ // send the diverted OnDataAvailable-s to the listeners and not queue them
+ // in mEventQ.
+ rv = mChannel->SuspendInternal();
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
+ mSuspendedForDiversion = NS_SUCCEEDED(rv);
+ } else {
+ // Take ownership of the automatic suspend that occurred after synthesizing
+ // the response.
+ mSuspendedForDiversion = true;
+
+ // If mSuspendAfterSynthesizeResponse is true channel has been already
+ // suspended. From comment above mSuspendedForDiversion takes the ownership
+ // of this suspend, therefore mEventQ should not be suspened so we need to
+ // resume it once.
+ mEventQ->Resume();
+ }
+
+ rv = mParentListener->SuspendForDiversion();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent
+ // to the child.
+ mDivertingFromChild = true;
+
+ return NS_OK;
+}
+
+nsresult
+HttpChannelParent::SuspendMessageDiversion()
+{
+ LOG(("HttpChannelParent::SuspendMessageDiversion [this=%p]", this));
+ // This only needs to suspend message queue.
+ mEventQ->Suspend();
+ return NS_OK;
+}
+
+nsresult
+HttpChannelParent::ResumeMessageDiversion()
+{
+ LOG(("HttpChannelParent::SuspendMessageDiversion [this=%p]", this));
+ // This only needs to resumes message queue.
+ mEventQ->Resume();
+ return NS_OK;
+}
+
+/* private, supporting function for ADivertableParentChannel */
+nsresult
+HttpChannelParent::ResumeForDiversion()
+{
+ LOG(("HttpChannelParent::ResumeForDiversion [this=%p]\n", this));
+ MOZ_ASSERT(mChannel);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot ResumeForDiversion if not diverting!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mChannel->MessageDiversionStop();
+
+ if (mSuspendedForDiversion) {
+ // The nsHttpChannel will deliver remaining OnData/OnStop for the transfer.
+ nsresult rv = mChannel->ResumeInternal();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailDiversion(NS_ERROR_UNEXPECTED, true);
+ return rv;
+ }
+ mSuspendedForDiversion = false;
+ }
+
+ if (NS_WARN_IF(mIPCClosed || !DoSendDeleteSelf())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+void
+HttpChannelParent::DivertTo(nsIStreamListener *aListener)
+{
+ LOG(("HttpChannelParent::DivertTo [this=%p aListener=%p]\n",
+ this, aListener));
+ MOZ_ASSERT(mParentListener);
+
+ // If we're in the process of opening a synthesized response, we must wait
+ // to perform the diversion. Some of our diversion listeners clear callbacks
+ // which breaks the synthesis process.
+ if (mWillSynthesizeResponse) {
+ // We should already have started pending the diversion when
+ // SuspendForDiversion() was called.
+ MOZ_ASSERT(mPendingDiversion);
+ mDivertListener = aListener;
+ return;
+ }
+
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertTo new listener if diverting is not set!");
+ return;
+ }
+
+ mDivertListener = aListener;
+
+ // Call OnStartRequest and SendDivertMessages asynchronously to avoid
+ // reentering client context.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod(this, &HttpChannelParent::StartDiversion));
+ return;
+}
+
+void
+HttpChannelParent::StartDiversion()
+{
+ LOG(("HttpChannelParent::StartDiversion [this=%p]\n", this));
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot StartDiversion if diverting is not set!");
+ return;
+ }
+
+ // Fake pending status in case OnStopRequest has already been called.
+ if (mChannel) {
+ mChannel->ForcePending(true);
+ }
+
+ {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ // Call OnStartRequest for the "DivertTo" listener.
+ nsresult rv = mDivertListener->OnStartRequest(mChannel, nullptr);
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ }
+ }
+ mDivertedOnStartRequest = true;
+
+ // After OnStartRequest has been called, setup content decoders if needed.
+ //
+ // Create a content conversion chain based on mDivertListener and update
+ // mDivertListener.
+ nsCOMPtr<nsIStreamListener> converterListener;
+ mChannel->DoApplyContentConversions(mDivertListener,
+ getter_AddRefs(converterListener));
+ if (converterListener) {
+ mDivertListener = converterListener.forget();
+ }
+
+ // Now mParentListener can be diverted to mDivertListener.
+ DebugOnly<nsresult> rvdbg = mParentListener->DivertTo(mDivertListener);
+ MOZ_ASSERT(NS_SUCCEEDED(rvdbg));
+ mDivertListener = nullptr;
+
+ if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // The listener chain should now be setup; tell HttpChannelChild to divert
+ // the OnDataAvailables and OnStopRequest to this HttpChannelParent.
+ if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+}
+
+class HTTPFailDiversionEvent : public Runnable
+{
+public:
+ HTTPFailDiversionEvent(HttpChannelParent *aChannelParent,
+ nsresult aErrorCode,
+ bool aSkipResume)
+ : mChannelParent(aChannelParent)
+ , mErrorCode(aErrorCode)
+ , mSkipResume(aSkipResume)
+ {
+ MOZ_RELEASE_ASSERT(aChannelParent);
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ }
+ NS_IMETHOD Run() override
+ {
+ mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume);
+ return NS_OK;
+ }
+private:
+ RefPtr<HttpChannelParent> mChannelParent;
+ nsresult mErrorCode;
+ bool mSkipResume;
+};
+
+void
+HttpChannelParent::FailDiversion(nsresult aErrorCode,
+ bool aSkipResume)
+{
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ MOZ_RELEASE_ASSERT(mDivertingFromChild);
+ MOZ_RELEASE_ASSERT(mParentListener);
+ MOZ_RELEASE_ASSERT(mChannel);
+
+ NS_DispatchToCurrentThread(
+ new HTTPFailDiversionEvent(this, aErrorCode, aSkipResume));
+}
+
+void
+HttpChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
+ bool aSkipResume)
+{
+ LOG(("HttpChannelParent::NotifyDiversionFailed [this=%p aErrorCode=%x]\n",
+ this, aErrorCode));
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ MOZ_RELEASE_ASSERT(mDivertingFromChild);
+ MOZ_RELEASE_ASSERT(mParentListener);
+ MOZ_RELEASE_ASSERT(mChannel);
+
+ mChannel->Cancel(aErrorCode);
+
+ mChannel->ForcePending(false);
+
+ bool isPending = false;
+ nsresult rv = mChannel->IsPending(&isPending);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ // Resume only if we suspended earlier.
+ if (mSuspendedForDiversion) {
+ mChannel->ResumeInternal();
+ }
+ // Channel has already sent OnStartRequest to the child, so ensure that we
+ // call it here if it hasn't already been called.
+ if (!mDivertedOnStartRequest) {
+ mChannel->ForcePending(true);
+ mParentListener->OnStartRequest(mChannel, nullptr);
+ mChannel->ForcePending(false);
+ }
+ // If the channel is pending, it will call OnStopRequest itself; otherwise, do
+ // it here.
+ if (!isPending) {
+ mParentListener->OnStopRequest(mChannel, nullptr, aErrorCode);
+ }
+ mParentListener = nullptr;
+ mChannel = nullptr;
+
+ if (!mIPCClosed) {
+ Unused << DoSendDeleteSelf();
+ }
+}
+
+nsresult
+HttpChannelParent::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_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;
+ }
+ return mCacheEntry->OpenAlternativeOutputStream(type, _retval);
+}
+
+NS_IMETHODIMP
+HttpChannelParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
+ void** aResult)
+{
+ nsCOMPtr<nsIAuthPrompt2> prompt =
+ new NeckoParent::NestedFrameAuthPrompt(Manager(), mNestedFrameId);
+ prompt.forget(aResult);
+ return NS_OK;
+}
+
+void
+HttpChannelParent::UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut)
+{
+ nsCOMPtr<nsISupports> secInfoSupp;
+ mChannel->GetSecurityInfo(getter_AddRefs(secInfoSupp));
+ if (secInfoSupp) {
+ mAssociatedContentSecurity = do_QueryInterface(secInfoSupp);
+ nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp);
+ if (secInfoSer) {
+ NS_SerializeToString(secInfoSer, aSerializedSecurityInfoOut);
+ }
+ }
+}
+
+bool
+HttpChannelParent::DoSendDeleteSelf()
+{
+ bool rv = SendDeleteSelf();
+ mIPCClosed = true;
+ return rv;
+}
+
+bool
+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;
+ return DoSendDeleteSelf();
+}
+
+bool
+HttpChannelParent::RecvFinishInterceptedRedirect()
+{
+ // We make sure not to send any more messages until the IPC channel is torn
+ // down by the child.
+ mIPCClosed = true;
+ return SendFinishInterceptedRedirect();
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
new file mode 100644
index 0000000000..51fae5a82a
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -0,0 +1,269 @@
+/* -*- 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 "ADivertableParentChannel.h"
+#include "nsHttp.h"
+#include "mozilla/net/PHttpChannelParent.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIObserver.h"
+#include "nsIParentRedirectingChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsHttpChannel.h"
+#include "nsIAuthPromptProvider.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsIDeprecationWarner.h"
+
+class nsICacheEntry;
+class nsIAssociatedContentSecurity;
+
+#define HTTP_CHANNEL_PARENT_IID \
+ { 0x982b2372, 0x7aa5, 0x4e8a, \
+ { 0xbd, 0x9f, 0x89, 0x74, 0xd7, 0xf0, 0x58, 0xeb } }
+
+namespace mozilla {
+
+namespace dom{
+class TabParent;
+class PBrowserOrId;
+} // namespace dom
+
+namespace net {
+
+class HttpChannelParentListener;
+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 ADivertableParentChannel
+ , public nsIAuthPromptProvider
+ , public nsIDeprecationWarner
+ , public HttpChannelSecurityWarningReporter
+{
+ 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_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_IID)
+
+ HttpChannelParent(const dom::PBrowserOrId& iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aStatus);
+
+ bool Init(const HttpChannelCreationArgs& aOpenArgs);
+
+ // ADivertableParentChannel functions.
+ void DivertTo(nsIStreamListener *aListener) override;
+ nsresult SuspendForDiversion() override;
+ nsresult SuspendMessageDiversion() override;
+ nsresult ResumeMessageDiversion() override;
+
+ // Calls OnStartRequest for "DivertTo" listener, then notifies child channel
+ // that it should divert OnDataAvailable and OnStopRequest calls to this
+ // parent channel.
+ void StartDiversion();
+
+ // Handles calling OnStart/Stop if there are errors during diversion.
+ // Called asynchronously from FailDiversion.
+ void NotifyDiversionFailed(nsresult aErrorCode, bool aSkipResume = true);
+
+ // Forwarded to nsHttpChannel::SetApplyConversion.
+ void SetApplyConversion(bool aApplyConversion) {
+ if (mChannel) {
+ mChannel->SetApplyConversion(aApplyConversion);
+ }
+ }
+
+ nsresult OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval);
+
+ void InvokeAsyncOpen(nsresult rv);
+protected:
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during redirects.
+ bool ConnectChannel(const uint32_t& channelId, const bool& shouldIntercept);
+
+ bool DoAsyncOpen(const URIParams& uri,
+ const OptionalURIParams& originalUri,
+ const OptionalURIParams& docUri,
+ const OptionalURIParams& referrerUri,
+ const uint32_t& referrerPolicy,
+ const OptionalURIParams& internalRedirectUri,
+ const OptionalURIParams& topWindowUri,
+ const uint32_t& loadFlags,
+ const RequestHeaderTuples& requestHeaders,
+ const nsCString& requestMethod,
+ const OptionalIPCStream& uploadStream,
+ const bool& uploadStreamHasHeaders,
+ const uint16_t& priority,
+ const uint32_t& classOfService,
+ const uint8_t& redirectionLimit,
+ const bool& allowPipelining,
+ 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& allowAltSvc,
+ const bool& beConservative,
+ const OptionalLoadInfoArgs& aLoadInfoArgs,
+ const OptionalHttpResponseHead& aSynthesizedResponseHead,
+ const nsCString& aSecurityInfoSerialization,
+ const uint32_t& aCacheKey,
+ const nsCString& aRequestContextID,
+ const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+ const uint32_t& aInitialRwin,
+ const bool& aBlockAuthPrompt,
+ const bool& aSuspendAfterSynthesizeResponse,
+ const bool& aAllowStaleCacheContent,
+ const nsCString& aContentTypeHint,
+ const nsCString& aChannelId,
+ const uint64_t& aContentWindowId,
+ const nsCString& aPreferredAlternativeType);
+
+ virtual bool RecvSetPriority(const uint16_t& priority) override;
+ virtual bool RecvSetClassOfService(const uint32_t& cos) override;
+ virtual bool RecvSetCacheTokenCachedCharset(const nsCString& charset) override;
+ virtual bool RecvSuspend() override;
+ virtual bool RecvResume() override;
+ virtual bool RecvCancel(const nsresult& status) override;
+ virtual bool RecvRedirect2Verify(const nsresult& result,
+ const RequestHeaderTuples& changedHeaders,
+ const uint32_t& loadFlags,
+ const OptionalURIParams& apiRedirectUri,
+ const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+ const bool& aForceHSTSPriming,
+ const bool& aMixedContentWouldBlock,
+ const bool& aChooseAppcache) override;
+ virtual bool RecvUpdateAssociatedContentSecurity(const int32_t& broken,
+ const int32_t& no) override;
+ virtual bool RecvDocumentChannelCleanup() override;
+ virtual bool RecvMarkOfflineCacheEntryAsForeign() override;
+ virtual bool RecvDivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count) override;
+ virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) override;
+ virtual bool RecvDivertComplete() override;
+ virtual bool RecvRemoveCorsPreflightCacheEntry(const URIParams& uri,
+ const mozilla::ipc::PrincipalInfo& requestingPrincipal) override;
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ // Supporting function for ADivertableParentChannel.
+ nsresult ResumeForDiversion();
+
+ // Asynchronously calls NotifyDiversionFailed.
+ void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
+
+ friend class HttpChannelParentListener;
+ RefPtr<mozilla::dom::TabParent> mTabParent;
+
+ nsresult ReportSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) override;
+
+ // Calls SendDeleteSelf and sets mIPCClosed to true because we should not
+ // send any more messages after that. Bug 1274886
+ bool DoSendDeleteSelf();
+ // Called to notify the parent channel to not send any more IPC messages.
+ virtual bool RecvDeletingChannel() override;
+ virtual bool RecvFinishInterceptedRedirect() override;
+
+private:
+ void UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut);
+
+ void DivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void DivertOnStopRequest(const nsresult& statusCode);
+ void DivertComplete();
+ void MaybeFlushPendingDiversion();
+ void ResponseSynthesized();
+
+ friend class DivertDataAvailableEvent;
+ friend class DivertStopRequestEvent;
+ friend class DivertCompleteEvent;
+
+ RefPtr<nsHttpChannel> mChannel;
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+ nsCOMPtr<nsIAssociatedContentSecurity> mAssociatedContentSecurity;
+ bool mIPCClosed; // PHttpChannel actor has been Closed()
+
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+
+ nsAutoPtr<class nsHttpChannel::OfflineCacheEntryAsForeignMarker> mOfflineForeignMarker;
+
+ // state for combining OnStatus/OnProgress with OnDataAvailable
+ // into one IPDL call to child.
+ nsresult mStoredStatus;
+ int64_t mStoredProgress;
+ int64_t mStoredProgressMax;
+
+ bool mSentRedirect1Begin : 1;
+ bool mSentRedirect1BeginFailed : 1;
+ bool mReceivedRedirect2Verify : 1;
+
+ PBOverrideStatus mPBOverride;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ RefPtr<nsHttpHandler> mHttpHandler;
+
+ RefPtr<HttpChannelParentListener> mParentListener;
+ // The listener we are diverting to or will divert to if mPendingDiversion
+ // is set.
+ nsCOMPtr<nsIStreamListener> mDivertListener;
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus;
+ // Indicates that diversion has been requested, but we could not start it
+ // yet because the channel is still being opened with a synthesized response.
+ bool mPendingDiversion;
+ // Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it
+ // must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are
+ // received from the child channel.
+ bool mDivertingFromChild;
+
+ // Set if OnStart|StopRequest was called during a diversion from the child.
+ bool mDivertedOnStartRequest;
+
+ bool mSuspendedForDiversion;
+
+ // Set if this channel should be suspended after synthesizing a response.
+ bool mSuspendAfterSynthesizeResponse;
+ // Set if this channel will synthesize its response.
+ bool mWillSynthesizeResponse;
+
+ dom::TabId mNestedFrameId;
+
+ RefPtr<ChannelEventQueue> mEventQ;
+};
+
+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/HttpChannelParentListener.cpp b/netwerk/protocol/http/HttpChannelParentListener.cpp
new file mode 100644
index 0000000000..59030cf99c
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParentListener.cpp
@@ -0,0 +1,407 @@
+/* -*- 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 "HttpChannelParentListener.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/Unused.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIHttpEventSink.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIRedirectChannelRegistrar.h"
+#include "nsIPromptFactory.h"
+#include "nsQueryObject.h"
+
+using mozilla::Unused;
+
+namespace mozilla {
+namespace net {
+
+HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitialChannel)
+ : mNextListener(aInitialChannel)
+ , mRedirectChannelId(0)
+ , mSuspendedForDiversion(false)
+ , mShouldIntercept(false)
+ , mShouldSuspendIntercept(false)
+{
+}
+
+HttpChannelParentListener::~HttpChannelParentListener()
+{
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpChannelParentListener)
+NS_IMPL_RELEASE(HttpChannelParentListener)
+NS_INTERFACE_MAP_BEGIN(HttpChannelParentListener)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener)
+ NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
+ if (aIID.Equals(NS_GET_IID(HttpChannelParentListener))) {
+ foundInterface = static_cast<nsIInterfaceRequestor*>(this);
+ } else
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+ "Cannot call OnStartRequest if suspended for diversion!");
+
+ if (!mNextListener)
+ return NS_ERROR_UNEXPECTED;
+
+ LOG(("HttpChannelParentListener::OnStartRequest [this=%p]\n", this));
+ return mNextListener->OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP
+HttpChannelParentListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+ "Cannot call OnStopRequest if suspended for diversion!");
+
+ if (!mNextListener)
+ return NS_ERROR_UNEXPECTED;
+
+ LOG(("HttpChannelParentListener::OnStopRequest: [this=%p status=%ul]\n",
+ this, aStatusCode));
+ nsresult rv = mNextListener->OnStopRequest(aRequest, aContext, aStatusCode);
+
+ mNextListener = nullptr;
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+ "Cannot call OnDataAvailable if suspended for diversion!");
+
+ if (!mNextListener)
+ return NS_ERROR_UNEXPECTED;
+
+ LOG(("HttpChannelParentListener::OnDataAvailable [this=%p]\n", this));
+ return mNextListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::GetInterface(const nsIID& aIID, void **result)
+{
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) ||
+ aIID.Equals(NS_GET_IID(nsIHttpEventSink)) ||
+ aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) ||
+ aIID.Equals(NS_GET_IID(nsIRedirectResultListener)))
+ {
+ return QueryInterface(aIID, result);
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir;
+ if (mNextListener &&
+ NS_SUCCEEDED(CallQueryInterface(mNextListener.get(),
+ getter_AddRefs(ir))))
+ {
+ return ir->GetInterface(aIID, result);
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return wwatch->GetPrompt(nullptr, aIID,
+ reinterpret_cast<void**>(result));
+ }
+
+ return NS_NOINTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::AsyncOnChannelRedirect(
+ nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t redirectFlags,
+ nsIAsyncVerifyRedirectCallback* callback)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
+ do_QueryInterface(mNextListener);
+ if (!activeRedirectingChannel) {
+ NS_ERROR("Channel got a redirect response, but doesn't implement "
+ "nsIParentRedirectingChannel to handle it.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Register the new channel and obtain id for it
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId));
+
+ return activeRedirectingChannel->StartRedirect(mRedirectChannelId,
+ newChannel,
+ redirectFlags,
+ callback);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::OnRedirectResult(bool succeeded)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIParentChannel> redirectChannel;
+ if (mRedirectChannelId) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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=%d", 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;
+ }
+
+ nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
+ do_QueryInterface(mNextListener);
+ MOZ_ASSERT(activeRedirectingChannel,
+ "Channel finished a redirect response, but doesn't implement "
+ "nsIParentRedirectingChannel to complete it.");
+
+ if (activeRedirectingChannel) {
+ activeRedirectingChannel->CompleteRedirect(succeeded);
+ } else {
+ succeeded = false;
+ }
+
+ if (succeeded) {
+ // Switch to redirect channel and delete the old one.
+ nsCOMPtr<nsIParentChannel> parent;
+ parent = do_QueryInterface(mNextListener);
+ MOZ_ASSERT(parent);
+ parent->Delete();
+ mNextListener = do_QueryInterface(redirectChannel);
+ MOZ_ASSERT(mNextListener);
+ redirectChannel->SetParentListener(this);
+ } else if (redirectChannel) {
+ // Delete the redirect target channel: continue using old channel
+ redirectChannel->Delete();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsINetworkInterceptController
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::ShouldPrepareForIntercept(nsIURI* aURI,
+ bool aIsNonSubresourceRequest,
+ bool* aShouldIntercept)
+{
+ *aShouldIntercept = mShouldIntercept;
+ return NS_OK;
+}
+
+class HeaderVisitor final : public nsIHttpHeaderVisitor
+{
+ nsCOMPtr<nsIInterceptedChannel> mChannel;
+ ~HeaderVisitor()
+ {
+ }
+public:
+ explicit HeaderVisitor(nsIInterceptedChannel* aChannel) : mChannel(aChannel)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
+ {
+ mChannel->SynthesizeHeader(aHeader, aValue);
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor)
+
+class FinishSynthesizedResponse : public Runnable
+{
+ nsCOMPtr<nsIInterceptedChannel> mChannel;
+public:
+ explicit FinishSynthesizedResponse(nsIInterceptedChannel* aChannel)
+ : mChannel(aChannel)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ // The URL passed as an argument here doesn't matter, since the child will
+ // receive a redirection notification as a result of this synthesized response.
+ mChannel->FinishSynthesizedResponse(EmptyCString());
+ return NS_OK;
+ }
+};
+
+NS_IMETHODIMP
+HttpChannelParentListener::ChannelIntercepted(nsIInterceptedChannel* aChannel)
+{
+ if (mShouldSuspendIntercept) {
+ mInterceptedChannel = aChannel;
+ return NS_OK;
+ }
+
+ nsAutoCString statusText;
+ mSynthesizedResponseHead->StatusText(statusText);
+ aChannel->SynthesizeStatus(mSynthesizedResponseHead->Status(), statusText);
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor = new HeaderVisitor(aChannel);
+ mSynthesizedResponseHead->VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterResponse);
+
+ nsCOMPtr<nsIRunnable> event = new FinishSynthesizedResponse(aChannel);
+ NS_DispatchToCurrentThread(event);
+
+ mSynthesizedResponseHead = nullptr;
+
+ MOZ_ASSERT(mNextListener);
+ RefPtr<HttpChannelParent> channel = do_QueryObject(mNextListener);
+ MOZ_ASSERT(channel);
+ channel->ResponseSynthesized();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+HttpChannelParentListener::SuspendForDiversion()
+{
+ if (NS_WARN_IF(mSuspendedForDiversion)) {
+ MOZ_ASSERT(!mSuspendedForDiversion, "Cannot SuspendForDiversion twice!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // While this is set, no OnStart/OnData/OnStop callbacks should be forwarded
+ // to mNextListener.
+ mSuspendedForDiversion = true;
+
+ return NS_OK;
+}
+
+nsresult
+HttpChannelParentListener::ResumeForDiversion()
+{
+ MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
+
+ // Allow OnStart/OnData/OnStop callbacks to be forwarded to mNextListener.
+ mSuspendedForDiversion = false;
+
+ return NS_OK;
+}
+
+nsresult
+HttpChannelParentListener::DivertTo(nsIStreamListener* aListener)
+{
+ MOZ_ASSERT(aListener);
+ MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
+
+ mNextListener = aListener;
+
+ return ResumeForDiversion();
+}
+
+void
+HttpChannelParentListener::SetupInterception(const nsHttpResponseHead& aResponseHead)
+{
+ mSynthesizedResponseHead = new nsHttpResponseHead(aResponseHead);
+ mShouldIntercept = true;
+}
+
+void
+HttpChannelParentListener::SetupInterceptionAfterRedirect(bool aShouldIntercept)
+{
+ mShouldIntercept = aShouldIntercept;
+ if (mShouldIntercept) {
+ // When an interception occurs, this channel should suspend all further activity.
+ // It will be torn down and recreated if necessary.
+ mShouldSuspendIntercept = true;
+ }
+}
+
+void
+HttpChannelParentListener::ClearInterceptedChannel()
+{
+ if (mInterceptedChannel) {
+ mInterceptedChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
+ mInterceptedChannel = nullptr;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelParentListener.h b/netwerk/protocol/http/HttpChannelParentListener.h
new file mode 100644
index 0000000000..de90f4c741
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParentListener.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 tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpChannelCallbackWrapper_h
+#define mozilla_net_HttpChannelCallbackWrapper_h
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIRedirectResultListener.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+class HttpChannelParent;
+
+#define HTTP_CHANNEL_PARENT_LISTENER_IID \
+ { 0xe409da52, 0xda76, 0x4eb7, \
+ { 0xa7, 0xf4, 0x03, 0x3d, 0x88, 0xac, 0x87, 0x6d } }
+
+// 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 HttpChannelParentListener final : public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+ , public nsIRedirectResultListener
+ , public nsIStreamListener
+ , public nsINetworkInterceptController
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSINETWORKINTERCEPTCONTROLLER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_LISTENER_IID)
+
+ explicit HttpChannelParentListener(HttpChannelParent* aInitialChannel);
+
+ // For channel diversion from child to parent.
+ nsresult DivertTo(nsIStreamListener *aListener);
+ nsresult SuspendForDiversion();
+
+ void SetupInterception(const nsHttpResponseHead& aResponseHead);
+ void SetupInterceptionAfterRedirect(bool aShouldIntercept);
+ void ClearInterceptedChannel();
+
+private:
+ virtual ~HttpChannelParentListener();
+
+ // Private partner function to SuspendForDiversion.
+ nsresult ResumeForDiversion();
+
+ // 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;
+ uint32_t mRedirectChannelId;
+ // When set, no OnStart/OnData/OnStop calls should be received.
+ bool mSuspendedForDiversion;
+
+ // Set if this channel should be intercepted before it sets up the HTTP transaction.
+ bool mShouldIntercept;
+ // Set if this channel should suspend on interception.
+ bool mShouldSuspendIntercept;
+
+ nsAutoPtr<nsHttpResponseHead> mSynthesizedResponseHead;
+
+ // Handle to the channel wrapper if this channel has been intercepted.
+ nsCOMPtr<nsIInterceptedChannel> mInterceptedChannel;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelParentListener,
+ HTTP_CHANNEL_PARENT_LISTENER_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpChannelParent_h
diff --git a/netwerk/protocol/http/HttpInfo.cpp b/netwerk/protocol/http/HttpInfo.cpp
new file mode 100644
index 0000000000..827f2c6ecf
--- /dev/null
+++ b/netwerk/protocol/http/HttpInfo.cpp
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+// 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()->GetConnectionData(args);
+}
diff --git a/netwerk/protocol/http/HttpInfo.h b/netwerk/protocol/http/HttpInfo.h
new file mode 100644
index 0000000000..e13ffff16c
--- /dev/null
+++ b/netwerk/protocol/http/HttpInfo.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 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..21f29cdcdd
--- /dev/null
+++ b/netwerk/protocol/http/HttpLog.h
@@ -0,0 +1,58 @@
+/* -*- 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 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 {
+extern LazyLogModule gHttpLog;
+}
+}
+
+// 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 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/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp
new file mode 100644
index 0000000000..9e38e27344
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedChannel.cpp
@@ -0,0 +1,540 @@
+/* -*- 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 "nsIPipe.h"
+#include "nsIStreamListener.h"
+#include "nsHttpChannel.h"
+#include "HttpChannelChild.h"
+#include "nsHttpResponseHead.h"
+#include "nsNetUtil.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "nsIChannelEventSink.h"
+
+namespace mozilla {
+namespace net {
+
+extern bool
+WillRedirect(const nsHttpResponseHead * response);
+
+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)
+{
+}
+
+InterceptedChannelBase::~InterceptedChannelBase()
+{
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::GetResponseBody(nsIOutputStream** aStream)
+{
+ NS_IF_ADDREF(*aStream = mResponseBody);
+ return NS_OK;
+}
+
+void
+InterceptedChannelBase::EnsureSynthesizedResponse()
+{
+ if (mSynthesizedResponseHead.isNothing()) {
+ mSynthesizedResponseHead.emplace(new nsHttpResponseHead());
+ }
+}
+
+void
+InterceptedChannelBase::DoNotifyController()
+{
+ nsresult rv = NS_OK;
+
+ if (NS_WARN_IF(!mController)) {
+ rv = ResetInterception();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to resume intercepted network request");
+ return;
+ }
+
+ rv = mController->ChannelIntercepted(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ rv = ResetInterception();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to resume intercepted network request");
+ }
+ mController = nullptr;
+}
+
+nsresult
+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);
+ return NS_OK;
+}
+
+nsresult
+InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ EnsureSynthesizedResponse();
+
+ nsAutoCString header = aName + NS_LITERAL_CSTRING(": ") + aValue;
+ // Overwrite any existing header.
+ nsresult rv = (*mSynthesizedResponseHead)->ParseHeaderLine(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+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;
+}
+
+/* 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();
+}
+
+InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel,
+ nsINetworkInterceptController* aController,
+ nsICacheEntry* aEntry)
+: InterceptedChannelBase(aController)
+, mChannel(aChannel)
+, mSynthesizedCacheEntry(aEntry)
+{
+ nsresult rv = mChannel->GetApplyConversion(&mOldApplyConversion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mOldApplyConversion = false;
+ }
+}
+
+void
+InterceptedChannelChrome::NotifyController()
+{
+ // Intercepted responses should already be decoded.
+ mChannel->SetApplyConversion(false);
+
+ nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(mResponseBody));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ DoNotifyController();
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetChannel(nsIChannel** aChannel)
+{
+ NS_IF_ADDREF(*aChannel = mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::ResetInterception()
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ mSynthesizedCacheEntry->AsyncDoom(nullptr);
+ mSynthesizedCacheEntry = nullptr;
+
+ mChannel->SetApplyConversion(mOldApplyConversion);
+
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+
+ nsresult rv = mChannel->StartRedirectChannelToURI(uri, nsIChannelEventSink::REDIRECT_INTERNAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mResponseBody->Close();
+ mResponseBody = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
+{
+ if (!mSynthesizedCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeStatus(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ if (!mSynthesizedCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeHeader(aName, aValue);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::FinishSynthesizedResponse(const nsACString& aFinalURLSpec)
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Make sure the cache entry's output stream is always closed. If the
+ // channel was intercepted with a null-body response then its possible
+ // the synthesis completed without a stream copy operation.
+ mResponseBody->Close();
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ EnsureSynthesizedResponse();
+
+ // If the synthesized response is a redirect, then we want to respect
+ // the encoding of whatever is loaded as a result.
+ if (WillRedirect(mSynthesizedResponseHead.ref())) {
+ nsresult rv = mChannel->SetApplyConversion(mOldApplyConversion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mChannel->MarkIntercepted();
+
+ // First we ensure the appropriate metadata is set on the synthesized cache entry
+ // (i.e. the flattened response head)
+
+ nsCOMPtr<nsISupports> securityInfo;
+ nsresult rv = mChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t expirationTime = 0;
+ rv = DoUpdateExpirationTime(mChannel, mSynthesizedCacheEntry,
+ mSynthesizedResponseHead.ref(),
+ expirationTime);
+
+ rv = DoAddCacheEntryHeaders(mChannel, mSynthesizedCacheEntry,
+ mChannel->GetRequestHead(),
+ mSynthesizedResponseHead.ref(), securityInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> originalURI;
+ mChannel->GetURI(getter_AddRefs(originalURI));
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = originalURI;
+ }
+
+ bool equal = false;
+ originalURI->Equals(responseURI, &equal);
+ if (!equal) {
+ rv =
+ mChannel->StartRedirectChannelToURI(responseURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ bool usingSSL = false;
+ responseURI->SchemeIs("https", &usingSSL);
+
+ // Then we open a real cache entry to read the synthesized response from.
+ rv = mChannel->OpenCacheEntry(usingSSL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSynthesizedCacheEntry = nullptr;
+
+ if (!mChannel->AwaitingCacheCallbacks()) {
+ rv = mChannel->ContinueConnect();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::Cancel(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ // we need to use AsyncAbort instead of Cancel since there's no active pump
+ // to cancel which will provide OnStart/OnStopRequest to the channel.
+ nsresult rv = mChannel->AsyncAbort(aStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SetChannelInfo(dom::ChannelInfo* aChannelInfo)
+{
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return aChannelInfo->ResurrectInfoOnChannel(mChannel);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetInternalContentPolicyType(nsContentPolicyType* aPolicyType)
+{
+ NS_ENSURE_ARG(aPolicyType);
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aPolicyType = loadInfo->InternalContentPolicyType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetSecureUpgradedChannelURI(nsIURI** aURI)
+{
+ return mChannel->GetURI(aURI);
+}
+
+InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel,
+ nsINetworkInterceptController* aController,
+ InterceptStreamListener* aListener,
+ bool aSecureUpgrade)
+: InterceptedChannelBase(aController)
+, mChannel(aChannel)
+, mStreamListener(aListener)
+, mSecureUpgrade(aSecureUpgrade)
+{
+}
+
+void
+InterceptedChannelContent::NotifyController()
+{
+ nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
+ getter_AddRefs(mResponseBody),
+ 0, UINT32_MAX, true, true);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ DoNotifyController();
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetChannel(nsIChannel** aChannel)
+{
+ NS_IF_ADDREF(*aChannel = mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::ResetInterception()
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ mResponseBody->Close();
+ mResponseBody = nullptr;
+ mSynthesizedInput = nullptr;
+
+ mChannel->ResetInterception();
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
+{
+ if (!mResponseBody) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeStatus(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ if (!mResponseBody) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeHeader(aName, aValue);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURLSpec)
+{
+ if (NS_WARN_IF(mClosed)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Make sure the body output stream is always closed. If the channel was
+ // intercepted with a null-body response then its possible the synthesis
+ // completed without a stream copy operation.
+ mResponseBody->Close();
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ EnsureSynthesizedResponse();
+
+ nsCOMPtr<nsIURI> originalURI;
+ mChannel->GetURI(getter_AddRefs(originalURI));
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ nsresult rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mSecureUpgrade) {
+ nsresult rv = NS_GetSecureUpgradedURI(originalURI,
+ getter_AddRefs(responseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = originalURI;
+ }
+
+ bool equal = false;
+ originalURI->Equals(responseURI, &equal);
+ if (!equal) {
+ mChannel->ForceIntercepted(mSynthesizedInput);
+ mChannel->BeginNonIPCRedirect(responseURI, *mSynthesizedResponseHead.ptr());
+ } else {
+ mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ref(),
+ mSynthesizedInput,
+ mStreamListener);
+ }
+
+ mResponseBody = nullptr;
+ mStreamListener = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::Cancel(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ // we need to use AsyncAbort instead of Cancel since there's no active pump
+ // to cancel which will provide OnStart/OnStopRequest to the channel.
+ nsresult rv = mChannel->AsyncAbort(aStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mStreamListener = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SetChannelInfo(dom::ChannelInfo* aChannelInfo)
+{
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return aChannelInfo->ResurrectInfoOnChannel(mChannel);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetInternalContentPolicyType(nsContentPolicyType* aPolicyType)
+{
+ NS_ENSURE_ARG(aPolicyType);
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aPolicyType = loadInfo->InternalContentPolicyType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetSecureUpgradedChannelURI(nsIURI** aURI)
+{
+ nsCOMPtr<nsIURI> uri;
+ if (mSecureUpgrade) {
+ uri = SecureUpgradeChannelURI(mChannel);
+ } else {
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (uri) {
+ uri.forget(aURI);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/InterceptedChannel.h b/netwerk/protocol/http/InterceptedChannel.h
new file mode 100644
index 0000000000..688f211de7
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedChannel.h
@@ -0,0 +1,134 @@
+/* -*- 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;
+
+ // The stream to write the body of the synthesized response
+ nsCOMPtr<nsIOutputStream> mResponseBody;
+
+ // Response head for use when synthesizing
+ Maybe<nsAutoPtr<nsHttpResponseHead>> mSynthesizedResponseHead;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
+ nsCOMPtr<nsISupports> mReleaseHandle;
+
+ bool mClosed;
+
+ void EnsureSynthesizedResponse();
+ void DoNotifyController();
+ nsresult DoSynthesizeStatus(uint16_t aStatus, const nsACString& aReason);
+ nsresult DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue);
+
+ virtual ~InterceptedChannelBase();
+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 GetResponseBody(nsIOutputStream** aOutput) override;
+ NS_IMETHOD GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) override;
+ NS_IMETHOD SetReleaseHandle(nsISupports* aHandle) override;
+
+ static already_AddRefed<nsIURI>
+ SecureUpgradeChannelURI(nsIChannel* aChannel);
+};
+
+class InterceptedChannelChrome : public InterceptedChannelBase
+{
+ // The actual channel being intercepted.
+ RefPtr<nsHttpChannel> mChannel;
+
+ // Writeable cache entry for use when synthesizing a response in a parent process
+ nsCOMPtr<nsICacheEntry> mSynthesizedCacheEntry;
+
+ // When a channel is intercepted, content decoding is disabled since the
+ // ServiceWorker will have already extracted the decoded data. For parent
+ // process channels we need to preserve the earlier value in case
+ // ResetInterception is called.
+ bool mOldApplyConversion;
+public:
+ InterceptedChannelChrome(nsHttpChannel* aChannel,
+ nsINetworkInterceptController* aController,
+ nsICacheEntry* aEntry);
+
+ NS_IMETHOD ResetInterception() override;
+ NS_IMETHOD FinishSynthesizedResponse(const nsACString& aFinalURLSpec) override;
+ NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
+ NS_IMETHOD GetSecureUpgradedChannelURI(nsIURI** aURI) override;
+ NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override;
+ NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) override;
+ NS_IMETHOD Cancel(nsresult aStatus) override;
+ NS_IMETHOD SetChannelInfo(mozilla::dom::ChannelInfo* aChannelInfo) override;
+ NS_IMETHOD GetInternalContentPolicyType(nsContentPolicyType *aInternalContentPolicyType) override;
+
+ virtual void NotifyController() override;
+};
+
+class InterceptedChannelContent : public InterceptedChannelBase
+{
+ // The actual channel being intercepted.
+ RefPtr<HttpChannelChild> mChannel;
+
+ // Reader-side of the response body when synthesizing in a child proces
+ nsCOMPtr<nsIInputStream> mSynthesizedInput;
+
+ // Listener for the synthesized response to fix up the notifications before they reach
+ // the actual channel.
+ RefPtr<InterceptStreamListener> mStreamListener;
+
+ // Set for intercepted channels that have gone through a secure upgrade.
+ bool mSecureUpgrade;
+public:
+ InterceptedChannelContent(HttpChannelChild* aChannel,
+ nsINetworkInterceptController* aController,
+ InterceptStreamListener* aListener,
+ bool aSecureUpgrade);
+
+ NS_IMETHOD ResetInterception() override;
+ NS_IMETHOD FinishSynthesizedResponse(const nsACString& aFinalURLSpec) override;
+ NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
+ NS_IMETHOD GetSecureUpgradedChannelURI(nsIURI** aURI) override;
+ NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override;
+ NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) override;
+ NS_IMETHOD Cancel(nsresult aStatus) override;
+ NS_IMETHOD SetChannelInfo(mozilla::dom::ChannelInfo* aChannelInfo) override;
+ NS_IMETHOD GetInternalContentPolicyType(nsContentPolicyType *aInternalContentPolicyType) override;
+
+ virtual void NotifyController() override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // InterceptedChannel_h
diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp
new file mode 100644
index 0000000000..8c048a6b5a
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -0,0 +1,772 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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, nsITimedChannel)
+
+NullHttpChannel::NullHttpChannel()
+{
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+}
+
+NullHttpChannel::NullHttpChannel(nsIHttpChannel * chan)
+{
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ ssm->GetChannelURIPrincipal(chan, getter_AddRefs(mResourcePrincipal));
+
+ chan->GetResponseHeader(NS_LITERAL_CSTRING("Timing-Allow-Origin"),
+ 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(nsACString& aChannelId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetChannelId(const nsACString& 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::GetTransferSize(uint64_t *aTransferSize)
+{
+ 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::GetReferrer(nsIURI * *aReferrer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReferrer(nsIURI *aReferrer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetReferrerPolicy(uint32_t *aReferrerPolicy)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReferrerWithPolicy(nsIURI *referrer, uint32_t referrerPolicy)
+{
+ 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::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::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::GetRequestContextID(nsID *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestContextID(const nsID 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 * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+NullHttpChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+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::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::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetTimingEnabled(bool *aTimingEnabled)
+{
+ *aTimingEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTimingEnabled(bool aTimingEnabled)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectCount(uint16_t *aRedirectCount)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectCount(uint16_t aRedirectCount)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetChannelCreation(mozilla::TimeStamp *aChannelCreation)
+{
+ *aChannelCreation = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAsyncOpen(mozilla::TimeStamp *aAsyncOpen)
+{
+ *aAsyncOpen = mAsyncOpenTime;
+ 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::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;
+ nsContentUtils::GetASCIIOrigin(aOrigin, 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;
+}
+
+#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(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+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..69b28d9927
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.h
@@ -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/. */
+
+#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
+
+ 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
+ nsresult Init(nsIURI *aURI, uint32_t aCaps, nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI);
+private:
+ ~NullHttpChannel() { }
+
+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..965ffcc2c5
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "NullHttpTransaction.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIHttpActivityObserver.h"
+#include "NullHttpChannel.h"
+#include "nsQueryObject.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+class CallObserveActivity final : public nsIRunnable
+{
+ ~CallObserveActivity()
+ {
+ }
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ CallObserveActivity(nsIHttpActivityObserver *aActivityDistributor,
+ const nsCString &aHost,
+ int32_t aPort,
+ bool aEndToEndSSL,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString &aExtraStringData)
+ : mActivityDistributor(aActivityDistributor)
+ , mHost(aHost)
+ , mPort(aPort)
+ , mEndToEndSSL(aEndToEndSSL)
+ , mActivityType(aActivityType)
+ , mActivitySubtype(aActivitySubtype)
+ , mTimestamp(aTimestamp)
+ , mExtraSizeData(aExtraSizeData)
+ , mExtraStringData(aExtraStringData)
+ {
+ }
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString port(NS_LITERAL_CSTRING(""));
+ if (mPort != -1 && ((mEndToEndSSL && mPort != 443) ||
+ (!mEndToEndSSL && mPort != 80))) {
+ port.AppendInt(mPort);
+ }
+
+ nsresult rv = NS_NewURI(getter_AddRefs(uri),
+ (mEndToEndSSL ? NS_LITERAL_CSTRING("https://")
+ : NS_LITERAL_CSTRING("http://") ) + mHost + port);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ RefPtr<NullHttpChannel> channel = new NullHttpChannel();
+ channel->Init(uri, 0, nullptr, 0, nullptr);
+ mActivityDistributor->ObserveActivity(
+ nsCOMPtr<nsISupports>(do_QueryObject(channel)),
+ mActivityType,
+ mActivitySubtype,
+ mTimestamp,
+ mExtraSizeData,
+ mExtraStringData);
+
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
+ nsCString mHost;
+ int32_t mPort;
+ bool mEndToEndSSL;
+ uint32_t mActivityType;
+ uint32_t mActivitySubtype;
+ PRTime mTimestamp;
+ uint64_t mExtraSizeData;
+ nsCString mExtraStringData;
+};
+
+NS_IMPL_ISUPPORTS(CallObserveActivity, nsIRunnable)
+
+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)
+ , mCapsToClear(0)
+ , mIsDone(false)
+ , mClaimed(false)
+ , mCallbacks(callbacks)
+ , mConnectionInfo(ci)
+{
+ nsresult rv;
+ mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID,
+ &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool activityDistributorActive;
+ rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
+ if (NS_SUCCEEDED(rv) && activityDistributorActive) {
+ // There are some observers registered at activity distributor.
+ LOG(("NulHttpTransaction::NullHttpTransaction() "
+ "mActivityDistributor is active "
+ "[this=%p, %s]", this, ci->GetOrigin().get()));
+ } else {
+ // There is no observer, so don't use it.
+ mActivityDistributor = nullptr;
+ }
+}
+
+NullHttpTransaction::~NullHttpTransaction()
+{
+ mCallbacks = nullptr;
+ delete mRequestHead;
+}
+
+bool
+NullHttpTransaction::Claim()
+{
+ if (mClaimed) {
+ return false;
+ }
+ mClaimed = true;
+ return true;
+}
+
+void
+NullHttpTransaction::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 (mActivityDistributor) {
+ NS_DispatchToMainThread(new CallObserveActivity(mActivityDistributor,
+ mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL(),
+ NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
+ static_cast<uint32_t>(status),
+ PR_Now(),
+ progress,
+ EmptyCString()));
+ }
+}
+
+bool
+NullHttpTransaction::IsDone()
+{
+ return mIsDone;
+}
+
+nsresult
+NullHttpTransaction::Status()
+{
+ return mStatus;
+}
+
+uint32_t
+NullHttpTransaction::Caps()
+{
+ return mCaps & ~mCapsToClear;
+}
+
+void
+NullHttpTransaction::SetDNSWasRefreshed()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!");
+ mCapsToClear |= NS_HTTP_REFRESH_DNS;
+}
+
+uint64_t
+NullHttpTransaction::Available()
+{
+ return 0;
+}
+
+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)) {
+ mRequestHead->SetHeader(nsHttp::Host, hostHeader);
+ if (mActivityDistributor) {
+ // Report request headers.
+ nsCString reqHeaderBuf;
+ mRequestHead->Flatten(reqHeaderBuf, false);
+ NS_DispatchToMainThread(new CallObserveActivity(mActivityDistributor,
+ 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.
+ NS_DispatchToMainThread(new CallObserveActivity(mActivityDistributor,
+ mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL(),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,
+ PR_Now(), 0, EmptyCString()));
+ }
+}
+
+nsHttpConnectionInfo *
+NullHttpTransaction::ConnectionInfo()
+{
+ return mConnectionInfo;
+}
+
+nsresult
+NullHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+NullHttpTransaction::PipelineDepth()
+{
+ return 0;
+}
+
+nsresult
+NullHttpTransaction::SetPipelinePosition(int32_t position)
+{
+ return NS_OK;
+}
+
+int32_t
+NullHttpTransaction::PipelinePosition()
+{
+ return 1;
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/http/NullHttpTransaction.h b/netwerk/protocol/http/NullHttpTransaction.h
new file mode 100644
index 0000000000..04f80a9b3f
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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"
+
+// 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);
+
+ bool Claim();
+
+ // Overload of nsAHttpTransaction methods
+ bool IsNullTransaction() override final { return true; }
+ NullHttpTransaction *QueryNullTransaction() override final { return this; }
+ bool ResponseTimeoutEnabled() const override final {return true; }
+ PRIntervalTime ResponseTimeout() override final
+ {
+ return PR_SecondsToInterval(15);
+ }
+
+protected:
+ virtual ~NullHttpTransaction();
+
+private:
+ nsresult mStatus;
+protected:
+ uint32_t mCaps;
+ nsHttpRequestHead *mRequestHead;
+private:
+ // 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;
+ bool mIsDone;
+ bool mClaimed;
+
+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..824137f8e3
--- /dev/null
+++ b/netwerk/protocol/http/PAltDataOutputStream.ipdl
@@ -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 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();
+ 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);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpChannel.ipdl b/netwerk/protocol/http/PHttpChannel.ipdl
new file mode 100644
index 0000000000..1eb25a4039
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include InputStreamParams;
+include URIParams;
+include PBackgroundSharedTypes;
+include NeckoChannelParams;
+
+include protocol PBlob; //FIXME: bug #792908
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+using class nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using struct mozilla::net::ResourceTimingStruct from "mozilla/net/TimingStruct.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+protocol PHttpChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+
+ async SetPriority(uint16_t priority);
+ async SetClassOfService(uint32_t cos);
+
+ async SetCacheTokenCachedCharset(nsCString charset);
+
+ async UpdateAssociatedContentSecurity(int32_t broken,
+ int32_t no);
+ async Suspend();
+ async Resume();
+
+ async Cancel(nsresult status);
+
+ // Reports approval/veto of redirect by child process redirect observers
+ async Redirect2Verify(nsresult result, RequestHeaderTuples changedHeaders,
+ uint32_t loadFlags, OptionalURIParams apiRedirectTo,
+ OptionalCorsPreflightArgs corsPreflightArgs,
+ bool forceHSTSPriming, bool mixedContentWouldBlock,
+ 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();
+
+ // 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();
+
+ // Divert OnDataAvailable to the parent.
+ async DivertOnDataAvailable(nsCString data,
+ uint64_t offset,
+ uint32_t count);
+
+ // Divert OnStopRequest to the parent.
+ async DivertOnStopRequest(nsresult statusCode);
+
+ // Child has no more events/messages to divert to the parent.
+ async DivertComplete();
+
+ // 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);
+
+ // After receiving this message, the parent calls SendDeleteSelf, and makes
+ // sure not to send any more messages after that.
+ async DeletingChannel();
+
+ async __delete__();
+
+child:
+ async OnStartRequest(nsresult channelStatus,
+ nsHttpResponseHead responseHead,
+ bool useResponseHead,
+ nsHttpHeaderArray requestHeaders,
+ bool isFromCache,
+ bool cacheEntryAvailable,
+ uint32_t cacheExpirationTime,
+ nsCString cachedCharset,
+ nsCString securityInfoSerialization,
+ NetAddr selfAddr,
+ NetAddr peerAddr,
+ int16_t redirectCount,
+ uint32_t cacheKey,
+ nsCString altDataType);
+
+ // Combines a single OnDataAvailable and its associated OnProgress &
+ // OnStatus calls into one IPDL message
+ async OnTransportAndData(nsresult channelStatus,
+ nsresult transportStatus,
+ uint64_t progress,
+ uint64_t progressMax,
+ uint64_t offset,
+ uint32_t count,
+ nsCString data);
+
+ async OnStopRequest(nsresult channelStatus, ResourceTimingStruct timing);
+
+ async OnProgress(int64_t progress, int64_t progressMax);
+
+ async OnStatus(nsresult status);
+
+ // Used to cancel child channel if we hit errors during creating and
+ // AsyncOpen of nsHttpChannel on the parent.
+ async FailedAsyncOpen(nsresult status);
+
+ // 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 newUri,
+ uint32_t redirectFlags,
+ nsHttpResponseHead responseHead,
+ nsCString securityInfoSerialization,
+ nsCString channelId);
+
+ // Called if redirect successful so that child can complete setup.
+ async Redirect3Complete();
+
+ // Associate the child with an application ids
+ async AssociateApplicationCache(nsCString groupID,
+ nsCString clientID);
+
+ // Tell the child that tracking protection was disabled for this load.
+ async NotifyTrackingProtectionDisabled();
+
+ // Parent has been suspended for diversion; no more events to be enqueued.
+ async FlushedForDiversion();
+
+ // Child should resume processing the ChannelEventQueue, i.e. diverting any
+ // OnDataAvailable and OnStopRequest messages in the queue back to the parent.
+ async DivertMessages();
+
+ // 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);
+
+both:
+ // After receiving this message, the parent also calls
+ // SendFinishInterceptedRedirect, and makes sure not to send any more messages
+ // after that. When receiving this message, the child will call
+ // Send__delete__() and complete the steps required to finish the redirect.
+ async FinishInterceptedRedirect();
+};
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpChannelParams.h b/netwerk/protocol/http/PHttpChannelParams.h
new file mode 100644
index 0000000000..4df5c7832e
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannelParams.h
@@ -0,0 +1,222 @@
+/* -*- 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 "nsHttp.h"
+#include "nsHttpHeaderArray.h"
+#include "nsHttpResponseHead.h"
+
+#include "nsIClassInfo.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 nsTArray<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)
+ {
+ WriteParam(aMsg, aParam.header);
+ 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;
+ if (!ReadParam(aMsg, aIter, &aResult->header) ||
+ !ReadParam(aMsg, aIter, &aResult->value) ||
+ !ReadParam(aMsg, aIter, &variety))
+ return false;
+
+ 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::nsHttpResponseHead>
+{
+ typedef mozilla::net::nsHttpResponseHead paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mHeaders);
+ WriteParam(aMsg, 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.mCacheControlPrivate);
+ WriteParam(aMsg, aParam.mCacheControlNoStore);
+ WriteParam(aMsg, aParam.mCacheControlNoCache);
+ WriteParam(aMsg, aParam.mPragmaNoCache);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ if (!ReadParam(aMsg, aIter, &aResult->mHeaders) ||
+ !ReadParam(aMsg, aIter, &aResult->mVersion) ||
+ !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->mCacheControlPrivate) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlNoStore) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlNoCache) ||
+ !ReadParam(aMsg, aIter, &aResult->mPragmaNoCache))
+ return false;
+
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_PHttpChannelParams_h
diff --git a/netwerk/protocol/http/PSpdyPush.h b/netwerk/protocol/http/PSpdyPush.h
new file mode 100644
index 0000000000..aa489912a4
--- /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 "nsAutoPtr.h"
+#include "nsDataHashtable.h"
+#include "nsISupports.h"
+
+class nsCString;
+
+namespace mozilla {
+namespace net {
+
+class Http2PushedStream;
+
+// One cache per load group
+class SpdyPushCache
+{
+public:
+ // The cache holds only weak pointers - no references
+ SpdyPushCache();
+ virtual ~SpdyPushCache();
+ bool RegisterPushedStreamHttp2(nsCString key,
+ Http2PushedStream *stream);
+ Http2PushedStream *RemovePushedStreamHttp2(nsCString key);
+private:
+ nsDataHashtable<nsCStringHashKey, Http2PushedStream *> mHashHttp2;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SpdyPush_Public_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/TimingStruct.h b/netwerk/protocol/http/TimingStruct.h
new file mode 100644
index 0000000000..b177eee8e4
--- /dev/null
+++ b/netwerk/protocol/http/TimingStruct.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef TimingStruct_h_
+#define TimingStruct_h_
+
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla { namespace net {
+
+struct TimingStruct {
+ TimeStamp domainLookupStart;
+ TimeStamp domainLookupEnd;
+ TimeStamp connectStart;
+ 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..4cc24a07fd
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.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"
+
+#include "Http2Session.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsISocketProvider.h"
+#include "nsISocketProviderService.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"
+
+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)
+ , mForce(false)
+ , mNudgeCounter(0)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction ctor %p\n", this));
+
+ nsCOMPtr<nsISocketProvider> provider;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID);
+
+ 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);
+
+ if (provider && mFD) {
+ mFD->secret = reinterpret_cast<PRFilePrivate *>(this);
+ provider->AddToSocket(PR_AF_INET, aTLSHost, aTLSPort, nullptr,
+ NeckoOriginAttributes(), 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));
+ Cleanup();
+}
+
+void
+TLSFilterTransaction::Cleanup()
+{
+ 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)
+{
+ if (!mTransaction) {
+ return;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ mTransaction->Close(aReason);
+ mTransaction = nullptr;
+}
+
+nsresult
+TLSFilterTransaction::OnReadSegment(const char *aData,
+ uint32_t aCount,
+ uint32_t *outCountRead)
+{
+ LOG(("TLSFilterTransaction %p OnReadSegment %d (buffered %d)\n",
+ this, aCount, mEncryptedTextUsed));
+
+ mReadSegmentBlocked = false;
+ 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);
+
+ 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 insepct. Similar
+ // to nsHttpConnection::mSocketOutCondition
+ mReadSegmentBlocked = (PR_GetError() == PR_WOULD_BLOCK_ERROR);
+ return mReadSegmentBlocked ? NS_BASE_STREAM_WOULD_BLOCK : NS_ERROR_FAILURE;
+ }
+ 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
+ Connection()->TransactionHasDataToWrite(this);
+ return NS_OK;
+ } else 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;
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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;
+ int32_t bytesRead = PR_Read(mFD, aData, aCount);
+ if (bytesRead == -1) {
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+ *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=%x didread=%d "
+ "2 layers of ssl stripped to plaintext\n", this, mFilterReadCode, bytesRead));
+ return mFilterReadCode;
+}
+
+int32_t
+TLSFilterTransaction::FilterInput(char *aBuf, int32_t aAmount)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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=%x read=%d input from net "
+ "1 layer stripped, 1 still on\n", mFilterReadCode, outCountRead));
+ if (mReadSegmentBlocked) {
+ mNudgeCounter = 0;
+ }
+ }
+ 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(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction::ReadSegments %p max=%d\n", this, aCount));
+
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mReadSegmentBlocked = false;
+ mSegmentReader = aReader;
+ nsresult rv = mTransaction->ReadSegments(this, aCount, outCountRead);
+ LOG(("TLSFilterTransaction %p called trans->ReadSegments rv=%x %d\n",
+ this, rv, *outCountRead));
+ if (NS_SUCCEEDED(rv) && mReadSegmentBlocked) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ LOG(("TLSFilterTransaction %p read segment blocked found rv=%x\n",
+ this, rv));
+ Connection()->ForceSend();
+ }
+
+ return rv;
+}
+
+nsresult
+TLSFilterTransaction::WriteSegments(nsAHttpSegmentWriter *aWriter,
+ uint32_t aCount, uint32_t *outCountWritten)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction::WriteSegments %p max=%d\n", this, aCount));
+
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mSegmentWriter = aWriter;
+ nsresult rv = mTransaction->WriteSegments(this, aCount, outCountWritten);
+ if (NS_SUCCEEDED(rv) && NS_FAILED(mFilterReadCode) && !(*outCountWritten)) {
+ // nsPipe turns failures into silent OK.. undo that!
+ rv = mFilterReadCode;
+ if (Connection() && (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK)) {
+ Connection()->ResumeRecv();
+ }
+ }
+ LOG(("TLSFilterTransaction %p called trans->WriteSegments rv=%x %d\n",
+ this, rv, *outCountWritten));
+ return rv;
+}
+
+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(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction %p NudgeTunnel\n", this));
+ mNudgeCallback = nullptr;
+
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t notUsed;
+ int32_t written = PR_Write(mFD, "", 0);
+ if ((written < 0) && (PR_GetError() != PR_WOULD_BLOCK_ERROR)) {
+ // fatal handshake failure
+ LOG(("TLSFilterTransaction %p Fatal Handshake Failure: %d\n", this, PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ 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 = do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction %p NudgeTunnel notify\n", this));
+
+ if (timer != mTimer) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ StartTimerCallback();
+ 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;
+ cb->OnTunnelNudged(this);
+ }
+ return NS_OK;
+}
+
+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::SetDNSWasRefreshed()
+{
+ if (!mTransaction) {
+ return;
+ }
+
+ mTransaction->SetDNSWasRefreshed();
+}
+
+uint64_t
+TLSFilterTransaction::Available()
+{
+ if (!mTransaction) {
+ return 0;
+ }
+
+ return mTransaction->Available();
+}
+
+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)
+{
+ LOG(("TLSFilterTransaction::SetProxiedTransaction [this=%p] aTrans=%p\n",
+ this, aTrans));
+
+ mTransaction = aTrans;
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
+ if (secCtrl && callbacks) {
+ secCtrl->SetNotificationCallbacks(callbacks);
+ }
+
+ return NS_OK;
+}
+
+// AddTransaction is for adding pipelined subtransactions
+nsresult
+TLSFilterTransaction::AddTransaction(nsAHttpTransaction *aTrans)
+{
+ LOG(("TLSFilterTransaction::AddTransaction passing on subtransaction "
+ "[this=%p] aTrans=%p ,mTransaction=%p\n", this, aTrans, mTransaction.get()));
+
+ if (!mTransaction) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mTransaction->AddTransaction(aTrans);
+}
+
+uint32_t
+TLSFilterTransaction::PipelineDepth()
+{
+ if (!mTransaction) {
+ return 0;
+ }
+
+ return mTransaction->PipelineDepth();
+}
+
+nsresult
+TLSFilterTransaction::SetPipelinePosition(int32_t aPosition)
+{
+ if (!mTransaction) {
+ return NS_OK;
+ }
+
+ return mTransaction->SetPipelinePosition(aPosition);
+}
+
+int32_t
+TLSFilterTransaction::PipelinePosition()
+{
+ if (!mTransaction) {
+ return 1;
+ }
+
+ return mTransaction->PipelinePosition();
+}
+
+nsHttpPipeline *
+TLSFilterTransaction::QueryPipeline()
+{
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->QueryPipeline();
+}
+
+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() {};
+
+ 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 %x %d\n",
+ this, 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() {};
+
+ 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 SocketTransportShim : public nsISocketTransport
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+
+ explicit SocketTransportShim(nsISocketTransport *aWrapped)
+ : mWrapped(aWrapped)
+ {};
+
+private:
+ virtual ~SocketTransportShim() {};
+
+ nsCOMPtr<nsISocketTransport> mWrapped;
+};
+
+class OutputStreamShim : public nsIAsyncOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ friend class SpdyConnectTransaction;
+
+ explicit OutputStreamShim(SpdyConnectTransaction *aTrans)
+ : mCallback(nullptr)
+ , mStatus(NS_OK)
+ {
+ mWeakTrans = do_GetWeakReference(aTrans);
+ }
+
+private:
+ virtual ~OutputStreamShim() {};
+
+ nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
+ nsIOutputStreamCallback *mCallback;
+ nsresult mStatus;
+};
+
+class InputStreamShim : public nsIAsyncInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ friend class SpdyConnectTransaction;
+
+ explicit InputStreamShim(SpdyConnectTransaction *aTrans)
+ : mCallback(nullptr)
+ , mStatus(NS_OK)
+ {
+ mWeakTrans = do_GetWeakReference(aTrans);
+ }
+
+private:
+ virtual ~InputStreamShim() {};
+
+ nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
+ nsIInputStreamCallback *mCallback;
+ nsresult mStatus;
+};
+
+SpdyConnectTransaction::SpdyConnectTransaction(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps,
+ nsHttpTransaction *trans,
+ nsAHttpConnection *session)
+ : 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)
+{
+ LOG(("SpdyConnectTransaction ctor %p\n", this));
+
+ mTimestampSyn = TimeStamp::Now();
+ mRequestHead = new nsHttpRequestHead();
+ nsHttpConnection::MakeConnectString(trans, mRequestHead, mConnectString);
+ mDrivingTransaction = trans;
+}
+
+SpdyConnectTransaction::~SpdyConnectTransaction()
+{
+ LOG(("SpdyConnectTransaction dtor %p\n", this));
+
+ if (mDrivingTransaction) {
+ // requeue it I guess. This should be gone.
+ gHttpHandler->InitiateTransaction(mDrivingTransaction,
+ mDrivingTransaction->Priority());
+ }
+}
+
+void
+SpdyConnectTransaction::ForcePlainText()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mInputDataUsed && !mInputDataSize && !mInputDataOffset);
+ MOZ_ASSERT(!mForcePlainText);
+ MOZ_ASSERT(!mTunnelTransport, "call before mapstreamtohttpconnection");
+
+ mForcePlainText = true;
+ return;
+}
+
+void
+SpdyConnectTransaction::MapStreamToHttpConnection(nsISocketTransport *aTransport,
+ nsHttpConnectionInfo *aConnInfo)
+{
+ mConnInfo = aConnInfo;
+
+ mTunnelTransport = new SocketTransportShim(aTransport);
+ mTunnelStreamIn = new InputStreamShim(this);
+ mTunnelStreamOut = new OutputStreamShim(this);
+ mTunneledConn = new nsHttpConnection();
+
+ // 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 new httpconnection %p %s\n",
+ mTunneledConn.get(), aConnInfo->HashKey().get()));
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ GetSecurityCallbacks(getter_AddRefs(callbacks));
+ mTunneledConn->SetTransactionCaps(Caps());
+ MOZ_ASSERT(aConnInfo->UsingHttpsProxy());
+ TimeDuration rtt = TimeStamp::Now() - mTimestampSyn;
+ mTunneledConn->Init(aConnInfo,
+ gHttpHandler->ConnMgr()->MaxRequestDelay(),
+ mTunnelTransport, mTunnelStreamIn, mTunnelStreamOut,
+ true, callbacks,
+ PR_MillisecondsToInterval(
+ static_cast<uint32_t>(rtt.ToMilliseconds())));
+ if (mForcePlainText) {
+ mTunneledConn->ForcePlainText();
+ } else {
+ mTunneledConn->SetupSecondaryTLS();
+ mTunneledConn->SetInSpdyTunnel(true);
+ }
+
+ // make the originating transaction stick to the tunneled conn
+ RefPtr<nsAHttpConnection> wrappedConn =
+ gHttpHandler->ConnMgr()->MakeConnectionHandle(mTunneledConn);
+ mDrivingTransaction->SetConnection(wrappedConn);
+ mDrivingTransaction->MakeSticky();
+
+ // jump the priority and start the dispatcher
+ gHttpHandler->InitiateTransaction(
+ mDrivingTransaction, nsISupportsPriority::PRIORITY_HIGHEST - 60);
+ mDrivingTransaction = nullptr;
+}
+
+nsresult
+SpdyConnectTransaction::Flush(uint32_t count, uint32_t *countRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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 %x\n", this, 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(PR_GetCurrentThread() == gSocketThread);
+ 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 %x\n",
+ this, rv));
+ CreateShimError(rv);
+ } else {
+ mConnectStringOffset += toWrite;
+ if (mConnectString.Length() == mConnectStringOffset) {
+ mConnectString.Truncate();
+ mConnectStringOffset = 0;
+ }
+ }
+ return rv;
+ }
+ 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;
+ Flush(count, countRead);
+ if (!mTunnelStreamOut->mCallback) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsresult rv =
+ mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t subtotal;
+ count -= *countRead;
+ rv = Flush(count, &subtotal);
+ *countRead += subtotal;
+ return rv;
+}
+
+void
+SpdyConnectTransaction::CreateShimError(nsresult code)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(NS_FAILED(code));
+
+ if (mTunnelStreamOut && NS_SUCCEEDED(mTunnelStreamOut->mStatus)) {
+ mTunnelStreamOut->mStatus = code;
+ }
+
+ if (mTunnelStreamIn && NS_SUCCEEDED(mTunnelStreamIn->mStatus)) {
+ mTunnelStreamIn->mStatus = code;
+ }
+
+ if (mTunnelStreamIn && mTunnelStreamIn->mCallback) {
+ mTunnelStreamIn->mCallback->OnInputStreamReady(mTunnelStreamIn);
+ }
+
+ if (mTunnelStreamOut && mTunnelStreamOut->mCallback) {
+ mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
+ }
+}
+
+nsresult
+SpdyConnectTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("SpdyConnectTransaction::WriteSegments %p max=%d cb=%p\n",
+ this, count, mTunneledConn ? mTunnelStreamIn->mCallback : nullptr));
+
+ // first call into the tunnel stream to get the demux'd data out of the
+ // spdy session.
+ 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 %x\n", this, rv));
+ CreateShimError(rv);
+ }
+ return rv;
+ }
+ mInputDataUsed += *countWritten;
+ LOG(("SpdyConnectTransaction %p %d new bytes [%d total] of ciphered data buffered\n",
+ this, *countWritten, mInputDataUsed - mInputDataOffset));
+
+ if (!mTunneledConn || !mTunnelStreamIn->mCallback) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mTunnelStreamIn->mCallback->OnInputStreamReady(mTunnelStreamIn);
+ LOG(("SpdyConnectTransaction::WriteSegments %p "
+ "after InputStreamReady callback %d total of ciphered data buffered rv=%x\n",
+ this, mInputDataUsed - mInputDataOffset, rv));
+ LOG(("SpdyConnectTransaction::WriteSegments %p "
+ "goodput %p out %llu\n", this, mTunneledConn.get(),
+ mTunneledConn->ContentBytesWritten()));
+ if (NS_SUCCEEDED(rv) && !mTunneledConn->ContentBytesWritten()) {
+ mTunnelStreamOut->AsyncWait(mTunnelStreamOut->mCallback, 0, 0, nullptr);
+ }
+ return rv;
+}
+
+bool
+SpdyConnectTransaction::ConnectedReadyForInput()
+{
+ return mTunneledConn && mTunnelStreamIn->mCallback;
+}
+
+nsHttpRequestHead *
+SpdyConnectTransaction::RequestHead()
+{
+ return mRequestHead;
+}
+
+void
+SpdyConnectTransaction::Close(nsresult code)
+{
+ LOG(("SpdyConnectTransaction close %p %x\n", this, code));
+
+ NullHttpTransaction::Close(code);
+ if (NS_FAILED(code) && (code != NS_BASE_STREAM_WOULD_BLOCK)) {
+ CreateShimError(code);
+ } else {
+ CreateShimError(NS_BASE_STREAM_CLOSED);
+ }
+}
+
+NS_IMETHODIMP
+OutputStreamShim::AsyncWait(nsIOutputStreamCallback *callback,
+ unsigned int, unsigned int, nsIEventTarget *target)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ bool currentThread;
+
+ if (target &&
+ (NS_FAILED(target->IsOnCurrentThread(&currentThread)) || !currentThread)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(("OutputStreamShim::AsyncWait %p callback %p\n", this, callback));
+ mCallback = callback;
+
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ 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::CloseWithStatus(nsresult reason)
+{
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ 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()
+{
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ 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;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Write(const char * aBuf, uint32_t aCount, uint32_t *_retval)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ 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)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::WriteSegments(nsReadSegmentFun aReader, void *aClosure, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::IsNonBlocking(bool *_retval)
+{
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::AsyncWait(nsIInputStreamCallback *callback,
+ unsigned int, unsigned int, nsIEventTarget *target)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ bool currentThread;
+
+ if (target &&
+ (NS_FAILED(target->IsOnCurrentThread(&currentThread)) || !currentThread)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(("InputStreamShim::AsyncWait %p callback %p\n", this, callback));
+ mCallback = callback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::CloseWithStatus(nsresult reason)
+{
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ 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(do_QueryReferent(mWeakTrans));
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ 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)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputStreamShim::IsNonBlocking(bool *_retval)
+{
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveEnabled(bool aKeepaliveEnabled)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveVals(int32_t keepaliveIdleTime, int32_t keepaliveRetryInterval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetSecurityCallbacks(nsIInterfaceRequestor *aSecurityCallbacks)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount, nsIInputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount, nsIOutputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Close(nsresult aReason)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetEventSink(nsITransportEventSink *aSink, nsIEventTarget *aEventTarget)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Bind(NetAddr *aLocalAddr)
+{
+ 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_ADDREF(GetSecurityCallbacks, nsIInterfaceRequestor);
+FWD_TS_PTR(IsAlive, bool);
+FWD_TS_PTR(GetConnectionFlags, uint32_t);
+FWD_TS(SetConnectionFlags, uint32_t);
+FWD_TS_PTR(GetRecvBufferSize, uint32_t);
+FWD_TS(SetRecvBufferSize, uint32_t);
+
+nsresult
+SocketTransportShim::GetOriginAttributes(mozilla::NeckoOriginAttributes* aOriginAttributes)
+{
+ return mWrapped->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult
+SocketTransportShim::SetOriginAttributes(const mozilla::NeckoOriginAttributes& 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::GetNetworkInterfaceId(nsACString_internal &aNetworkInterfaceId)
+{
+ return mWrapped->GetNetworkInterfaceId(aNetworkInterfaceId);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetNetworkInterfaceId(const nsACString_internal &aNetworkInterfaceId)
+{
+ return mWrapped->SetNetworkInterfaceId(aNetworkInterfaceId);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetTimeout(uint32_t aType, uint32_t aValue)
+{
+ return mWrapped->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetQoSBits(uint8_t *aQoSBits)
+{
+ return mWrapped->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetQoSBits(uint8_t aQoSBits)
+{
+ return mWrapped->SetQoSBits(aQoSBits);
+}
+
+NS_IMPL_ISUPPORTS(TLSFilterTransaction, nsITimerCallback)
+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..20cfaf7ee9
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.h
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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 "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()
+ SpdyConnectTransaction.mTunneledConn.mTunnelStreamIn->Read() // gets data from mInputData
+
+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 void OnTunnelNudged(TLSFilterTransaction *) = 0;
+};
+
+#define NS_DECL_NUDGETUNNELCALLBACK void OnTunnelNudged(TLSFilterTransaction *) override;
+
+class TLSFilterTransaction final
+ : public nsAHttpTransaction
+ , public nsAHttpSegmentReader
+ , public nsAHttpSegmentWriter
+ , public nsITimerCallback
+{
+ ~TLSFilterTransaction();
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSITIMERCALLBACK
+
+ TLSFilterTransaction(nsAHttpTransaction *aWrappedTransaction,
+ const char *tlsHost, int32_t tlsPort,
+ nsAHttpSegmentReader *reader,
+ nsAHttpSegmentWriter *writer);
+
+ const nsAHttpTransaction *Transaction() const { return mTransaction.get(); }
+ nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment) override;
+ nsresult GetTransactionSecurityInfo(nsISupports **) override;
+ nsresult NudgeTunnel(NudgeTunnelCallback *callback);
+ nsresult SetProxiedTransaction(nsAHttpTransaction *aTrans);
+ void newIODriver(nsIAsyncInputStream *aSocketIn,
+ nsIAsyncOutputStream *aSocketOut,
+ nsIAsyncInputStream **outSocketIn,
+ nsIAsyncOutputStream **outSocketOut);
+
+ // nsAHttpTransaction overloads
+ nsHttpPipeline *QueryPipeline() override;
+ bool IsNullTransaction() override;
+ NullHttpTransaction *QueryNullTransaction() override;
+ nsHttpTransaction *QueryHttpTransaction() override;
+ SpdyConnectTransaction *QuerySpdyConnectTransaction() override;
+
+private:
+ 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;
+ 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;
+ bool mForce;
+ bool mReadSegmentBlocked;
+ 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);
+ ~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();
+ void MapStreamToHttpConnection(nsISocketTransport *aTransport,
+ nsHttpConnectionInfo *aConnInfo);
+
+ nsresult ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead) override final;
+ nsresult WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten) override final;
+ nsHttpRequestHead *RequestHead() override final;
+ void Close(nsresult reason) override final;
+
+ // ConnectedReadyForInput() tests whether the spdy connect transaction is attached to
+ // an nsHttpConnection that can properly deal with flow control, etc..
+ bool ConnectedReadyForInput();
+
+private:
+ friend class InputStreamShim;
+ friend class OutputStreamShim;
+
+ 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;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ // 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;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSFilterTransaction_h
diff --git a/netwerk/protocol/http/UserAgentOverrides.jsm b/netwerk/protocol/http/UserAgentOverrides.jsm
new file mode 100644
index 0000000000..22c676f068
--- /dev/null
+++ b/netwerk/protocol/http/UserAgentOverrides.jsm
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "UserAgentOverrides" ];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/UserAgentUpdates.jsm");
+
+const OVERRIDE_MESSAGE = "Useragent:GetOverride";
+const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides";
+const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
+ .getService(Ci.nsIHttpProtocolHandler)
+ .userAgent;
+const MAX_OVERRIDE_FOR_HOST_CACHE_SIZE = 250;
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+ "@mozilla.org/parentprocessmessagemanager;1",
+ "nsIMessageListenerManager"); // Might have to make this broadcast?
+
+var gPrefBranch;
+var gOverrides = new Map;
+var gUpdatedOverrides;
+var gOverrideForHostCache = new Map;
+var gInitialized = false;
+var gOverrideFunctions = [
+ function (aHttpChannel) { return UserAgentOverrides.getOverrideForURI(aHttpChannel.URI); }
+];
+var gBuiltUAs = new Map;
+
+this.UserAgentOverrides = {
+ init: function uao_init() {
+ if (gInitialized)
+ return;
+
+ gPrefBranch = Services.prefs.getBranch("general.useragent.override.");
+ gPrefBranch.addObserver("", buildOverrides, false);
+
+ ppmm.addMessageListener(OVERRIDE_MESSAGE, this);
+ Services.prefs.addObserver(PREF_OVERRIDES_ENABLED, buildOverrides, false);
+
+ try {
+ Services.obs.addObserver(HTTP_on_useragent_request, "http-on-useragent-request", false);
+ } catch (x) {
+ // The http-on-useragent-request notification is disallowed in content processes.
+ }
+
+ UserAgentUpdates.init(function(overrides) {
+ gOverrideForHostCache.clear();
+ if (overrides) {
+ for (let domain in overrides) {
+ overrides[domain] = getUserAgentFromOverride(overrides[domain]);
+ }
+ overrides.get = function(key) { return this[key]; };
+ }
+ gUpdatedOverrides = overrides;
+ });
+
+ buildOverrides();
+ gInitialized = true;
+ },
+
+ addComplexOverride: function uao_addComplexOverride(callback) {
+ // Add to front of array so complex overrides have precedence
+ gOverrideFunctions.unshift(callback);
+ },
+
+ getOverrideForURI: function uao_getOverrideForURI(aURI) {
+ let host = aURI.asciiHost;
+ if (!gInitialized ||
+ (!gOverrides.size && !gUpdatedOverrides) ||
+ !(host)) {
+ return null;
+ }
+
+ let override = gOverrideForHostCache.get(host);
+ if (override !== undefined)
+ return override;
+
+ function findOverride(overrides) {
+ let searchHost = host;
+ let userAgent = overrides.get(searchHost);
+
+ while (!userAgent) {
+ let dot = searchHost.indexOf('.');
+ if (dot === -1) {
+ return null;
+ }
+ searchHost = searchHost.slice(dot + 1);
+ userAgent = overrides.get(searchHost);
+ }
+ return userAgent;
+ }
+
+ override = (gOverrides.size && findOverride(gOverrides))
+ || (gUpdatedOverrides && findOverride(gUpdatedOverrides));
+
+ if (gOverrideForHostCache.size >= MAX_OVERRIDE_FOR_HOST_CACHE_SIZE) {
+ gOverrideForHostCache.clear();
+ }
+ gOverrideForHostCache.set(host, override);
+
+ return override;
+ },
+
+ uninit: function uao_uninit() {
+ if (!gInitialized)
+ return;
+ gInitialized = false;
+
+ gPrefBranch.removeObserver("", buildOverrides);
+
+ Services.prefs.removeObserver(PREF_OVERRIDES_ENABLED, buildOverrides);
+
+ Services.obs.removeObserver(HTTP_on_useragent_request, "http-on-useragent-request");
+ },
+
+ receiveMessage: function(aMessage) {
+ let name = aMessage.name;
+ switch (name) {
+ case OVERRIDE_MESSAGE:
+ let uri = Services.io.newURI(aMessage.data.uri, null, null);
+ return this.getOverrideForURI(uri);
+ default:
+ throw("Wrong Message in UserAgentOverride: " + name);
+ }
+ }
+};
+
+function getUserAgentFromOverride(override)
+{
+ let userAgent = gBuiltUAs.get(override);
+ if (userAgent !== undefined) {
+ return userAgent;
+ }
+ let [search, replace] = override.split("#", 2);
+ if (search && replace) {
+ userAgent = DEFAULT_UA.replace(new RegExp(search, "g"), replace);
+ } else {
+ userAgent = override;
+ }
+ gBuiltUAs.set(override, userAgent);
+ return userAgent;
+}
+
+function buildOverrides() {
+ gOverrides.clear();
+ gOverrideForHostCache.clear();
+
+ if (!Services.prefs.getBoolPref(PREF_OVERRIDES_ENABLED))
+ return;
+
+ let builtUAs = new Map;
+ let domains = gPrefBranch.getChildList("");
+
+ for (let domain of domains) {
+ let override = gPrefBranch.getCharPref(domain);
+ let userAgent = getUserAgentFromOverride(override);
+
+ if (userAgent != DEFAULT_UA) {
+ gOverrides.set(domain, userAgent);
+ }
+ }
+}
+
+function HTTP_on_useragent_request(aSubject, aTopic, aData) {
+ let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+
+ for (let callback of gOverrideFunctions) {
+ let modifiedUA = callback(channel, DEFAULT_UA);
+ if (modifiedUA) {
+ channel.setRequestHeader("User-Agent", modifiedUA, false);
+ return;
+ }
+ }
+}
diff --git a/netwerk/protocol/http/UserAgentUpdates.jsm b/netwerk/protocol/http/UserAgentUpdates.jsm
new file mode 100644
index 0000000000..602705ebe7
--- /dev/null
+++ b/netwerk/protocol/http/UserAgentUpdates.jsm
@@ -0,0 +1,285 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["UserAgentUpdates"];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "OS", "resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "Promise", "resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(
+ this, "gUpdateTimer", "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");
+
+XPCOMUtils.defineLazyGetter(this, "gApp",
+ function() {
+ return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo)
+ .QueryInterface(Ci.nsIXULRuntime);
+ });
+
+XPCOMUtils.defineLazyGetter(this, "gDecoder",
+ function() { return new TextDecoder(); }
+);
+
+XPCOMUtils.defineLazyGetter(this, "gEncoder",
+ function() { return new TextEncoder(); }
+);
+
+const TIMER_ID = "user-agent-updates-timer";
+
+const PREF_UPDATES = "general.useragent.updates.";
+const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
+const PREF_UPDATES_URL = PREF_UPDATES + "url";
+const PREF_UPDATES_INTERVAL = PREF_UPDATES + "interval";
+const PREF_UPDATES_RETRY = PREF_UPDATES + "retry";
+const PREF_UPDATES_TIMEOUT = PREF_UPDATES + "timeout";
+const PREF_UPDATES_LASTUPDATED = PREF_UPDATES + "lastupdated";
+
+const KEY_PREFDIR = "PrefD";
+const KEY_APPDIR = "XCurProcD";
+const FILE_UPDATES = "ua-update.json";
+
+const PREF_APP_DISTRIBUTION = "distribution.id";
+const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
+
+var gInitialized = false;
+
+function readChannel(url) {
+ return new Promise((resolve, reject) => {
+ try {
+ let channel = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+ channel.contentType = "application/json";
+
+ NetUtil.asyncFetch(channel, (inputStream, status) => {
+ if (!Components.isSuccessCode(status)) {
+ reject();
+ return;
+ }
+
+ let data = JSON.parse(
+ NetUtil.readInputStreamToString(inputStream, inputStream.available())
+ );
+ resolve(data);
+ });
+ } catch (ex) {
+ reject(new Error("UserAgentUpdates: Could not fetch " + url + " " +
+ ex + "\n" + ex.stack));
+ }
+ });
+}
+
+this.UserAgentUpdates = {
+ init: function(callback) {
+ if (gInitialized) {
+ return;
+ }
+ gInitialized = true;
+
+ this._callback = callback;
+ this._lastUpdated = 0;
+ this._applySavedUpdate();
+
+ Services.prefs.addObserver(PREF_UPDATES, this, false);
+ },
+
+ uninit: function() {
+ if (!gInitialized) {
+ return;
+ }
+ gInitialized = false;
+ Services.prefs.removeObserver(PREF_UPDATES, this);
+ },
+
+ _applyUpdate: function(update) {
+ // Check pref again in case it has changed
+ if (update && this._getPref(PREF_UPDATES_ENABLED, false)) {
+ this._callback(update);
+ } else {
+ this._callback(null);
+ }
+ },
+
+ _applySavedUpdate: function() {
+ if (!this._getPref(PREF_UPDATES_ENABLED, false)) {
+ // remove previous overrides
+ this._applyUpdate(null);
+ return;
+ }
+ // try loading from profile dir, then from app dir
+ let dirs = [KEY_PREFDIR, KEY_APPDIR];
+
+ dirs.reduce((prevLoad, dir) => {
+ let file = FileUtils.getFile(dir, [FILE_UPDATES], true).path;
+ // tryNext returns promise to read file under dir and parse it
+ let tryNext = () => OS.File.read(file).then(
+ (bytes) => {
+ let update = JSON.parse(gDecoder.decode(bytes));
+ if (!update) {
+ throw new Error("invalid update");
+ }
+ return update;
+ }
+ );
+ // try to load next one if the previous load failed
+ return prevLoad ? prevLoad.then(null, tryNext) : tryNext();
+ }, null).then(null, (ex) => {
+ if (AppConstants.platform !== "android") {
+ // All previous (non-Android) load attempts have failed, so we bail.
+ throw new Error("UserAgentUpdates: Failed to load " + FILE_UPDATES +
+ ex + "\n" + ex.stack);
+ }
+ // Make one last attempt to read from the Fennec APK root.
+ return readChannel("resource://android/" + FILE_UPDATES);
+ }).then((update) => {
+ // Apply update if loading was successful
+ this._applyUpdate(update);
+ }).catch(Cu.reportError);
+ this._scheduleUpdate();
+ },
+
+ _saveToFile: function(update) {
+ let file = FileUtils.getFile(KEY_PREFDIR, [FILE_UPDATES], true);
+ let path = file.path;
+ let bytes = gEncoder.encode(JSON.stringify(update));
+ OS.File.writeAtomic(path, bytes, {tmpPath: path + ".tmp"}).then(
+ () => {
+ this._lastUpdated = Date.now();
+ Services.prefs.setCharPref(
+ PREF_UPDATES_LASTUPDATED, this._lastUpdated.toString());
+ },
+ Cu.reportError
+ );
+ },
+
+ _getPref: function(name, def) {
+ try {
+ switch (typeof def) {
+ case "number": return Services.prefs.getIntPref(name);
+ case "boolean": return Services.prefs.getBoolPref(name);
+ }
+ return Services.prefs.getCharPref(name);
+ } catch (e) {
+ return def;
+ }
+ },
+
+ _getParameters() {
+ return {
+ "%DATE%": function() { return Date.now().toString(); },
+ "%PRODUCT%": function() { return gApp.name; },
+ "%APP_ID%": function() { return gApp.ID; },
+ "%APP_VERSION%": function() { return gApp.version; },
+ "%BUILD_ID%": function() { return gApp.appBuildID; },
+ "%OS%": function() { return gApp.OS; },
+ "%CHANNEL%": function() { return UpdateUtils.UpdateChannel; },
+ "%DISTRIBUTION%": function() { return this._getPref(PREF_APP_DISTRIBUTION, ""); },
+ "%DISTRIBUTION_VERSION%": function() { return this._getPref(PREF_APP_DISTRIBUTION_VERSION, ""); },
+ };
+ },
+
+ _getUpdateURL: function() {
+ let url = this._getPref(PREF_UPDATES_URL, "");
+ let params = this._getParameters();
+ return url.replace(/%[A-Z_]+%/g, function(match) {
+ let param = params[match];
+ // preserve the %FOO% string (e.g. as an encoding) if it's not a valid parameter
+ return param ? encodeURIComponent(param()) : match;
+ });
+ },
+
+ _fetchUpdate: function(url, success, error) {
+ let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ request.mozBackgroundRequest = true;
+ request.timeout = this._getPref(PREF_UPDATES_TIMEOUT, 60000);
+ request.open("GET", url, true);
+ request.overrideMimeType("application/json");
+ request.responseType = "json";
+
+ request.addEventListener("load", function() {
+ let response = request.response;
+ response ? success(response) : error();
+ });
+ request.addEventListener("error", error);
+ request.send();
+ },
+
+ _update: function() {
+ let url = this._getUpdateURL();
+ url && this._fetchUpdate(url,
+ (function(response) { // success
+ // apply update and save overrides to profile
+ this._applyUpdate(response);
+ this._saveToFile(response);
+ this._scheduleUpdate(); // cancel any retries
+ }).bind(this),
+ (function(response) { // error
+ this._scheduleUpdate(true /* retry */);
+ }).bind(this));
+ },
+
+ _scheduleUpdate: function(retry) {
+ // only schedule updates in the main process
+ if (gApp.processType !== Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ return;
+ }
+ let interval = this._getPref(PREF_UPDATES_INTERVAL, 604800 /* 1 week */);
+ if (retry) {
+ interval = this._getPref(PREF_UPDATES_RETRY, interval);
+ }
+ gUpdateTimer.registerTimer(TIMER_ID, this, Math.max(1, interval));
+ },
+
+ notify: function(timer) {
+ // timer notification
+ if (this._getPref(PREF_UPDATES_ENABLED, false)) {
+ this._update();
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ if (data === PREF_UPDATES_ENABLED) {
+ this._applySavedUpdate();
+ } else if (data === PREF_UPDATES_INTERVAL) {
+ this._scheduleUpdate();
+ } else if (data === PREF_UPDATES_LASTUPDATED) {
+ // reload from file if there has been an update
+ let lastUpdated = parseInt(
+ this._getPref(PREF_UPDATES_LASTUPDATED, "0"), 0);
+ if (lastUpdated > this._lastUpdated) {
+ this._applySavedUpdate();
+ this._lastUpdated = lastUpdated;
+ }
+ }
+ break;
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsITimerCallback,
+ ]),
+};
diff --git a/netwerk/protocol/http/WellKnownOpportunisticUtils.js b/netwerk/protocol/http/WellKnownOpportunisticUtils.js
new file mode 100644
index 0000000000..865f2a6b49
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.js
@@ -0,0 +1,43 @@
+/* -*- 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID = "@mozilla.org/network/well-known-opportunistic-utils;1";
+const WELLKNOWNOPPORTUNISTICUTILS_CID = Components.ID("{b4f96c89-5238-450c-8bda-e12c26f1d150}");
+
+function WellKnownOpportunisticUtils() {
+ this.valid = false;
+ this.mixed = false;
+ this.lifetime = 0;
+}
+
+WellKnownOpportunisticUtils.prototype = {
+ classID: WELLKNOWNOPPORTUNISTICUTILS_CID,
+ contractID: WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID,
+ classDescription: "Well-Known Opportunistic Utils",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWellKnownOpportunisticUtils]),
+
+ verify: function(aJSON, aOrigin, aAlternatePort) {
+ try {
+ let obj = JSON.parse(aJSON.toLowerCase());
+ let ports = obj[aOrigin.toLowerCase()]['tls-ports'];
+ if (ports.indexOf(aAlternatePort) == -1) {
+ throw "invalid port";
+ }
+ this.lifetime = obj[aOrigin.toLowerCase()]['lifetime'];
+ this.mixed = obj[aOrigin.toLowerCase()]['mixed-scheme'];
+ } catch (e) {
+ return;
+ }
+ this.valid = true;
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WellKnownOpportunisticUtils]);
diff --git a/netwerk/protocol/http/WellKnownOpportunisticUtils.manifest b/netwerk/protocol/http/WellKnownOpportunisticUtils.manifest
new file mode 100644
index 0000000000..645358e435
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.manifest
@@ -0,0 +1,3 @@
+# WellKnownOpportunisticUtils.js
+component {b4f96c89-5238-450c-8bda-e12c26f1d150} WellKnownOpportunisticUtils.js
+contract @mozilla.org/network/well-known-opportunistic-utils;1 {b4f96c89-5238-450c-8bda-e12c26f1d150}
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..35525660a0
--- /dev/null
+++ b/netwerk/protocol/http/make_incoming_tables.py
@@ -0,0 +1,194 @@
+# 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 {
+ const uint16_t mValue:9; // 9 bits so it can hold 0..256
+ const 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..abf559dad0
--- /dev/null
+++ b/netwerk/protocol/http/make_outgoing_tables.py
@@ -0,0 +1,55 @@
+# 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 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..e13101aa02
--- /dev/null
+++ b/netwerk/protocol/http/moz.build
@@ -0,0 +1,121 @@
+# -*- 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 += [
+ 'nsIHstsPrimingCallback.idl',
+ 'nsIHttpActivityObserver.idl',
+ 'nsIHttpAuthenticableChannel.idl',
+ 'nsIHttpAuthenticator.idl',
+ 'nsIHttpAuthManager.idl',
+ 'nsIHttpChannel.idl',
+ 'nsIHttpChannelAuthProvider.idl',
+ 'nsIHttpChannelChild.idl',
+ 'nsIHttpChannelInternal.idl',
+ 'nsIHttpEventSink.idl',
+ 'nsIHttpHeaderVisitor.idl',
+ 'nsIHttpProtocolHandler.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',
+ 'HttpBaseChannel.h',
+ 'HttpChannelChild.h',
+ 'HttpChannelParent.h',
+ 'HttpInfo.h',
+ 'NullHttpChannel.h',
+ 'PHttpChannelParams.h',
+ 'PSpdyPush.h',
+ 'TimingStruct.h',
+]
+
+# ASpdySession.cpp and nsHttpAuthCache cannot be built in unified mode because
+# they use plarena.h.
+SOURCES += [
+ 'AlternateServices.cpp',
+ 'ASpdySession.cpp',
+ 'nsHttpAuthCache.cpp',
+ 'nsHttpChannelAuthProvider.cpp', # redefines GetAuthType
+]
+
+UNIFIED_SOURCES += [
+ 'AltDataOutputStreamChild.cpp',
+ 'AltDataOutputStreamParent.cpp',
+ 'CacheControlParser.cpp',
+ 'ConnectionDiagnostics.cpp',
+ 'HSTSPrimerListener.cpp',
+ 'Http2Compression.cpp',
+ 'Http2Push.cpp',
+ 'Http2Session.cpp',
+ 'Http2Stream.cpp',
+ 'HttpBaseChannel.cpp',
+ 'HttpChannelChild.cpp',
+ 'HttpChannelParent.cpp',
+ 'HttpChannelParentListener.cpp',
+ 'HttpInfo.cpp',
+ 'InterceptedChannel.cpp',
+ 'nsCORSListenerProxy.cpp',
+ 'nsHttp.cpp',
+ 'nsHttpActivityDistributor.cpp',
+ 'nsHttpAuthManager.cpp',
+ 'nsHttpBasicAuth.cpp',
+ 'nsHttpChannel.cpp',
+ 'nsHttpChunkedDecoder.cpp',
+ 'nsHttpConnection.cpp',
+ 'nsHttpConnectionInfo.cpp',
+ 'nsHttpConnectionMgr.cpp',
+ 'nsHttpDigestAuth.cpp',
+ 'nsHttpHeaderArray.cpp',
+ 'nsHttpNTLMAuth.cpp',
+ 'nsHttpPipeline.cpp',
+ 'nsHttpRequestHead.cpp',
+ 'nsHttpResponseHead.cpp',
+ 'nsHttpTransaction.cpp',
+ 'NullHttpChannel.cpp',
+ 'NullHttpTransaction.cpp',
+ 'TunnelUtils.cpp',
+]
+
+# These files cannot be built in unified mode because of OS X headers.
+SOURCES += [
+ 'nsHttpHandler.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PAltDataOutputStream.ipdl',
+ 'PHttpChannel.ipdl',
+]
+
+EXTRA_JS_MODULES += [
+ 'UserAgentOverrides.jsm',
+ 'UserAgentUpdates.jsm',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/netwerk/base',
+]
+
+EXTRA_COMPONENTS += [
+ 'WellKnownOpportunisticUtils.js',
+ 'WellKnownOpportunisticUtils.manifest',
+]
diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h
new file mode 100644
index 0000000000..1775a2c680
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -0,0 +1,263 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsISupports.h"
+#include "nsAHttpTransaction.h"
+
+class nsISocketTransport;
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+
+namespace mozilla { namespace net {
+
+class nsHttpConnectionInfo;
+class nsHttpConnection;
+
+//-----------------------------------------------------------------------------
+// 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.
+ //
+ 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.
+ //
+ virtual nsresult ResumeSend() = 0;
+ 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
+ virtual nsresult ForceSend() = 0;
+ 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 - plain HTTP or pipelined HTTP
+ // implicitly have 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
+ return;
+ }
+
+ // 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
+ return;
+ }
+
+ // 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.
+ 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 pipelined response).
+ 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<nsHttpConnection> TakeHttpConnection() = 0;
+
+ // Get the nsISocketTransport used by the connection without changing
+ // references or ownership.
+ virtual nsISocketTransport *Transport() = 0;
+
+ // Cancel and reschedule transactions deeper than the current response.
+ // Returns the number of canceled transactions.
+ virtual uint32_t CancelPipeline(nsresult originalReason) = 0;
+
+ // Read and write class of transaction that is carried on this connection
+ virtual nsAHttpTransaction::Classifier Classification() = 0;
+ virtual void Classify(nsAHttpTransaction::Classifier newclass) = 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 uint32_t Version() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
+
+#define NS_DECL_NSAHTTPCONNECTION(fwdObject) \
+ nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset) override; \
+ void CloseTransaction(nsAHttpTransaction *, nsresult) override; \
+ nsresult TakeTransport(nsISocketTransport **, \
+ nsIAsyncInputStream **, \
+ nsIAsyncOutputStream **) override; \
+ bool IsPersistent() override; \
+ bool IsReused() override; \
+ void DontReuse() override; \
+ nsresult PushBack(const char *, uint32_t) override; \
+ already_AddRefed<nsHttpConnection> TakeHttpConnection() override; \
+ uint32_t CancelPipeline(nsresult originalReason) override; \
+ nsAHttpTransaction::Classifier Classification() 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); \
+ } \
+ nsresult ResumeSend() override \
+ { \
+ if (!(fwdObject)) \
+ return NS_ERROR_FAILURE; \
+ return (fwdObject)->ResumeSend(); \
+ } \
+ nsresult ResumeRecv() override \
+ { \
+ if (!(fwdObject)) \
+ return NS_ERROR_FAILURE; \
+ return (fwdObject)->ResumeRecv(); \
+ } \
+ nsresult ForceSend() override \
+ { \
+ if (!(fwdObject)) \
+ return NS_ERROR_FAILURE; \
+ return (fwdObject)->ForceSend(); \
+ } \
+ nsresult ForceRecv() override \
+ { \
+ if (!(fwdObject)) \
+ return NS_ERROR_FAILURE; \
+ return (fwdObject)->ForceRecv(); \
+ } \
+ nsISocketTransport *Transport() \
+ override \
+ { \
+ if (!(fwdObject)) \
+ return nullptr; \
+ return (fwdObject)->Transport(); \
+ } \
+ uint32_t Version() override \
+ { \
+ return (fwdObject) ? \
+ (fwdObject)->Version() : \
+ NS_HTTP_VERSION_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); \
+ } \
+ void Classify(nsAHttpTransaction::Classifier newclass) \
+ override \
+ { \
+ if (fwdObject) \
+ (fwdObject)->Classify(newclass); \
+ } \
+ int64_t BytesWritten() override \
+ { return fwdObject ? (fwdObject)->BytesWritten() : 0; } \
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) \
+ override \
+ { \
+ if (fwdObject) \
+ (fwdObject)->SetSecurityCallbacks(aCallbacks); \
+ }
+
+} // 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..7e42d191a0
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -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/. */
+
+#ifndef nsAHttpTransaction_h__
+#define nsAHttpTransaction_h__
+
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+class nsIInterfaceRequestor;
+class nsITransport;
+class nsIRequestContext;
+
+namespace mozilla { namespace net {
+
+class nsAHttpConnection;
+class nsAHttpSegmentReader;
+class nsAHttpSegmentWriter;
+class nsHttpTransaction;
+class nsHttpPipeline;
+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;
+
+ // 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 notify that a requested DNS cache entry was refreshed.
+ virtual void SetDNSWasRefreshed() = 0;
+
+ // called to find out how much request data is available for writing.
+ virtual uint64_t Available() = 0;
+
+ // called to read request data from the transaction.
+ virtual nsresult ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead) = 0;
+
+ // called to write response data to the transaction.
+ 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
+ virtual nsresult ReadSegmentsAgain(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead, bool *again)
+ {
+ return ReadSegments(reader, count, countRead);
+ }
+ 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 may have 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. pipelines or spdy).
+ //
+ // 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.
+ //
+ virtual nsresult TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) = 0;
+
+ // called to add a sub-transaction in the case of pipelined transactions
+ // classes that do not implement sub transactions
+ // return NS_ERROR_NOT_IMPLEMENTED
+ virtual nsresult AddTransaction(nsAHttpTransaction *transaction) = 0;
+
+ // The total length of the outstanding pipeline comprised of transacations
+ // and sub-transactions.
+ virtual uint32_t PipelineDepth() = 0;
+
+ // Used to inform the connection that it is being used in a pipelined
+ // context. That may influence the handling of some errors.
+ // The value is the pipeline position (> 1).
+ virtual nsresult SetPipelinePosition(int32_t) = 0;
+ virtual int32_t PipelinePosition() = 0;
+
+ // Occasionally the abstract interface has to give way to base implementations
+ // to respect differences between spdy, pipelines, 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
+
+ // If we used rtti this would be the result of doing
+ // dynamic_cast<nsHttpPipeline *>(this).. i.e. it can be nullptr for
+ // non pipeline implementations of nsAHttpTransaction
+ virtual nsHttpPipeline *QueryPipeline() { return nullptr; }
+
+ // 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();
+
+ // Every transaction is classified into one of the types below. When using
+ // HTTP pipelines, only transactions with the same type appear on the same
+ // pipeline.
+ enum Classifier {
+ // Transactions that expect a short 304 (no-content) response
+ CLASS_REVALIDATION,
+
+ // Transactions for content expected to be CSS or JS
+ CLASS_SCRIPT,
+
+ // Transactions for content expected to be an image
+ CLASS_IMAGE,
+
+ // Transactions that cannot involve a pipeline
+ CLASS_SOLO,
+
+ // Transactions that do not fit any of the other categories. HTML
+ // is normally GENERAL.
+ CLASS_GENERAL,
+
+ CLASS_MAX
+ };
+
+ // 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.
+ virtual nsresult GetTransactionSecurityInfo(nsISupports **)
+ {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual void DisableSpdy() { }
+ virtual void ReuseConnectionOnRestartOK(bool) { }
+
+ // Returns true if early-data is possible.
+ 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.
+ // The function will return success or failure of the transaction restart.
+ virtual nsresult Finish0RTT(bool aRestart) {
+ 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; \
+ void SetDNSWasRefreshed() override; \
+ uint64_t Available() override; \
+ virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *) override; \
+ 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; \
+ nsresult TakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) override; \
+ nsresult AddTransaction(nsAHttpTransaction *) override; \
+ uint32_t PipelineDepth() override; \
+ nsresult SetPipelinePosition(int32_t) override; \
+ int32_t PipelinePosition() override;
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+class nsAHttpSegmentReader
+{
+public:
+ // any returned failure code stops segment iteration
+ 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.
+ virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment)
+ {
+ return NS_ERROR_FAILURE;
+ }
+};
+
+#define NS_DECL_NSAHTTPSEGMENTREADER \
+ nsresult OnReadSegment(const char *, uint32_t, uint32_t *) override;
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+class nsAHttpSegmentWriter
+{
+public:
+ // any returned failure code stops segment iteration
+ virtual nsresult OnWriteSegment(char *segment,
+ uint32_t count,
+ uint32_t *countWritten) = 0;
+};
+
+#define NS_DECL_NSAHTTPSEGMENTWRITER \
+ 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..c2a6243305
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -0,0 +1,1526 @@
+/* -*- 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 "nsCORSListenerProxy.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "HttpChannelChild.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMimeTypes.h"
+#include "nsIStreamConverterService.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 "nsIDOMNode.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDOMWindow.h"
+#include "nsINetworkInterceptController.h"
+#include "nsNullPrincipal.h"
+#include "nsICorsPreflightCallback.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/LoadInfo.h"
+#include "nsIHttpHeaderVisitor.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+#define PREFLIGHT_CACHE_SIZE 100
+
+static bool gDisableCORS = false;
+static bool gDisableCORSPrivateData = false;
+
+static void
+LogBlockedRequest(nsIRequest* aRequest,
+ const char* aProperty,
+ const char16_t* aParam)
+{
+ 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;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIURI> aUri;
+ channel->GetURI(getter_AddRefs(aUri));
+ nsAutoCString spec;
+ if (aUri) {
+ spec = aUri->GetSpecOrDefault();
+ }
+
+ // Generate the error message
+ nsXPIDLString blockedMessage;
+ NS_ConvertUTF8toUTF16 specUTF16(spec);
+ const char16_t* params[] = { specUTF16.get(), aParam };
+ 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());
+
+ // query innerWindowID and log to web console, otherwise log to
+ // the error to the browser console.
+ uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
+
+ if (innerWindowID > 0) {
+ rv = scriptError->InitWithWindowID(msg,
+ EmptyString(), // sourceName
+ EmptyString(), // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ nsIScriptError::warningFlag,
+ "CORS",
+ innerWindowID);
+ }
+ else {
+ rv = scriptError->Init(msg,
+ EmptyString(), // sourceName
+ EmptyString(), // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ nsIScriptError::warningFlag,
+ "CORS");
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (scriptError init failed)");
+ return;
+ }
+ console->LogMessage(scriptError);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// 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;
+ };
+
+ nsPreflightCache()
+ {
+ MOZ_COUNT_CTOR(nsPreflightCache);
+ }
+
+ ~nsPreflightCache()
+ {
+ Clear();
+ MOZ_COUNT_DTOR(nsPreflightCache);
+ }
+
+ bool Initialize()
+ {
+ return true;
+ }
+
+ CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ bool aWithCredentials, bool aCreate);
+ void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal);
+
+ void Clear();
+
+private:
+ static bool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ bool aWithCredentials, nsACString& _retval);
+
+ nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
+ LinkedList<CacheEntry> mList;
+};
+
+// Will be initialized in EnsurePreflightCache.
+static nsPreflightCache* sPreflightCache = nullptr;
+
+static bool EnsurePreflightCache()
+{
+ if (sPreflightCache)
+ return true;
+
+ nsAutoPtr<nsPreflightCache> newCache(new nsPreflightCache());
+
+ if (newCache->Initialize()) {
+ sPreflightCache = newCache.forget();
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now)
+{
+ uint32_t i;
+ for (i = 0; i < mMethods.Length(); ++i) {
+ if (now >= mMethods[i].expirationTime) {
+ mMethods.RemoveElementAt(i--);
+ }
+ }
+ for (i = 0; i < mHeaders.Length(); ++i) {
+ if (now >= mHeaders[i].expirationTime) {
+ mHeaders.RemoveElementAt(i--);
+ }
+ }
+}
+
+bool
+nsPreflightCache::CacheEntry::CheckRequest(const nsCString& aMethod,
+ const nsTArray<nsCString>& aHeaders)
+{
+ PurgeExpired(TimeStamp::NowLoRes());
+
+ if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
+ uint32_t i;
+ for (i = 0; i < mMethods.Length(); ++i) {
+ if (aMethod.Equals(mMethods[i].token))
+ break;
+ }
+ if (i == mMethods.Length()) {
+ return false;
+ }
+ }
+
+ for (uint32_t i = 0; i < aHeaders.Length(); ++i) {
+ uint32_t j;
+ for (j = 0; j < mHeaders.Length(); ++j) {
+ if (aHeaders[i].Equals(mHeaders[j].token,
+ nsCaseInsensitiveCStringComparator())) {
+ break;
+ }
+ }
+ if (j == mHeaders.Length()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsPreflightCache::CacheEntry*
+nsPreflightCache::GetEntry(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ bool aWithCredentials,
+ bool aCreate)
+{
+ nsCString key;
+ if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, 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()) {
+ nsAutoPtr<CacheEntry>& entry = iter.Data();
+ 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)
+{
+ CacheEntry* entry;
+ nsCString key;
+ if (GetCacheKey(aURI, aPrincipal, true, key) &&
+ mTable.Get(key, &entry)) {
+ entry->removeFrom(mList);
+ mTable.Remove(key);
+ }
+
+ if (GetCacheKey(aURI, aPrincipal, false, key) &&
+ mTable.Get(key, &entry)) {
+ entry->removeFrom(mList);
+ mTable.Remove(key);
+ }
+}
+
+void
+nsPreflightCache::Clear()
+{
+ mList.clear();
+ mTable.Clear();
+}
+
+/* static */ bool
+nsPreflightCache::GetCacheKey(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ bool aWithCredentials,
+ nsACString& _retval)
+{
+ NS_ASSERTION(aURI, "Null uri!");
+ NS_ASSERTION(aPrincipal, "Null principal!");
+
+ NS_NAMED_LITERAL_CSTRING(space, " ");
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString scheme, host, port;
+ if (uri) {
+ uri->GetScheme(scheme);
+ uri->GetHost(host);
+ port.AppendInt(NS_GetRealPort(uri));
+ }
+
+ if (aWithCredentials) {
+ _retval.AssignLiteral("cred");
+ }
+ else {
+ _retval.AssignLiteral("nocred");
+ }
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ _retval.Append(space + scheme + space + host + space + port + space +
+ spec);
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// nsCORSListenerProxy
+
+NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener,
+ nsIRequestObserver, nsIChannelEventSink,
+ nsIInterfaceRequestor, nsIThreadRetargetableStreamListener)
+
+/* static */
+void
+nsCORSListenerProxy::Startup()
+{
+ Preferences::AddBoolVarCache(&gDisableCORS,
+ "content.cors.disable");
+ Preferences::AddBoolVarCache(&gDisableCORSPrivateData,
+ "content.cors.no_private_data");
+}
+
+/* static */
+void
+nsCORSListenerProxy::Shutdown()
+{
+ delete sPreflightCache;
+ sPreflightCache = nullptr;
+}
+
+nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials)
+ : mOuterListener(aOuter),
+ mRequestingPrincipal(aRequestingPrincipal),
+ mOriginHeaderPrincipal(aRequestingPrincipal),
+ mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
+ mRequestApproved(false),
+ mHasBeenCrossSite(false)
+{
+}
+
+nsCORSListenerProxy::~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)) {
+ mOuterListener = nullptr;
+ mRequestingPrincipal = nullptr;
+ mOriginHeaderPrincipal = nullptr;
+ mOuterNotificationCallbacks = nullptr;
+ }
+#ifdef DEBUG
+ mInited = true;
+#endif
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ 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) {
+ if (sPreflightCache) {
+ // OK to use mRequestingPrincipal since preflights never get
+ // redirected.
+ sPreflightCache->RemoveEntries(uri, mRequestingPrincipal);
+ } else {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
+ do_QueryInterface(channel);
+ if (httpChannelChild) {
+ rv = httpChannelChild->RemoveCorsPreflightCacheEntry(uri, mRequestingPrincipal);
+ 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);
+ mOuterListener->OnStartRequest(aRequest, aContext);
+
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ return mOuterListener->OnStartRequest(aRequest, aContext);
+}
+
+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()
+ {}
+
+};
+
+NS_IMPL_ISUPPORTS(CheckOriginHeader, nsIHttpHeaderVisitor)
+}
+
+nsresult
+nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest)
+{
+ // Check if this was actually a cross domain request
+ if (!mHasBeenCrossSite) {
+ return NS_OK;
+ }
+
+ if (gDisableCORS) {
+ LogBlockedRequest(aRequest, "CORSDisabled", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Check if the request failed
+ 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);
+ if (!http) {
+ LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(internal);
+ bool responseSynthesized = false;
+ if (NS_SUCCEEDED(internal->GetResponseSynthesized(&responseSynthesized)) &&
+ responseSynthesized) {
+ // For synthesized responses, we don't need to perform any checks.
+ // Note: This would be unsafe if we ever changed our behavior to allow
+ // service workers to intercept CORS preflights.
+ return NS_OK;
+ }
+
+ // Check the Access-Control-Allow-Origin header
+ RefPtr<CheckOriginHeader> visitor = new CheckOriginHeader();
+ nsAutoCString allowedOriginHeader;
+
+ // check for duplicate headers
+ rv = http->VisitOriginalResponseHeaders(visitor);
+ if (NS_FAILED(rv)) {
+ LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin", nullptr);
+ return rv;
+ }
+
+ rv = http->GetResponseHeader(
+ NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader);
+ if (NS_FAILED(rv)) {
+ LogBlockedRequest(aRequest, "CORSMissingAllowOrigin", nullptr);
+ 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);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
+ MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal));
+ nsAutoCString origin;
+ nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
+
+ if (!allowedOriginHeader.Equals(origin)) {
+ LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin",
+ NS_ConvertUTF8toUTF16(allowedOriginHeader).get());
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ // Check Access-Control-Allow-Credentials header
+ if (mWithCredentials) {
+ nsAutoCString allowCredentialsHeader;
+ rv = http->GetResponseHeader(
+ NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"), allowCredentialsHeader);
+
+ if (!allowCredentialsHeader.EqualsLiteral("true")) {
+ LogBlockedRequest(aRequest, "CORSMissingAllowCredentials", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatusCode)
+{
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ nsresult rv = mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode);
+ mOuterListener = nullptr;
+ mOuterNotificationCallbacks = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ 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) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ return mOuterListener->OnDataAvailable(aRequest, aContext, 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) {
+ if (sPreflightCache) {
+ // OK to use mRequestingPrincipal since preflights never get
+ // redirected.
+ sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal);
+ } else {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
+ do_QueryInterface(aOldChannel);
+ if (httpChannelChild) {
+ rv = httpChannelChild->RemoveCorsPreflightCacheEntry(oldURI, mRequestingPrincipal);
+ 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);
+ 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 =
+ nsNullPrincipal::CreateWithInheritedAttributes(oldChannelPrincipal);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ }
+
+ rv = UpdateChannel(aNewChannel, DataURIHandling::Disallow,
+ 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());
+
+ if (nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mOuterListener)) {
+ return retargetableListener->CheckListenerChain();
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+// Please note that the CSP directive 'upgrade-insecure-requests' relies
+// 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
+CheckUpgradeInsecureRequestsPreventsCORS(nsIPrincipal* aRequestingPrincipal,
+ nsIChannel* aChannel)
+{
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ NS_ENSURE_SUCCESS(rv, false);
+ bool isHttpScheme = false;
+ rv = channelURI->SchemeIs("http", &isHttpScheme);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // upgrade insecure requests is only applicable to http requests
+ if (!isHttpScheme) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> principalURI;
+ rv = aRequestingPrincipal->GetURI(getter_AddRefs(principalURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // if the requestingPrincipal does not have a uri, there is nothing to do
+ if (!principalURI) {
+ 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(principalURI->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;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // lets see if the loadInfo indicates that the request will
+ // be upgraded before fetching any data from the netwerk.
+ return loadInfo->GetUpgradeInsecureRequests();
+}
+
+
+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->GetLoadInfo();
+
+ // exempt data URIs from the same origin check.
+ if (aAllowDataURI == DataURIHandling::Allow && originalURI == uri) {
+ bool dataScheme = false;
+ rv = uri->SchemeIs("data", &dataScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (dataScheme) {
+ return NS_OK;
+ }
+ if (loadInfo && 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);
+ }
+
+ // Check that the uri is ok to load
+ rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(mRequestingPrincipal, uri,
+ nsIScriptSecurityManager::STANDARD);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (originalURI != uri) {
+ rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(mRequestingPrincipal, originalURI,
+ nsIScriptSecurityManager::STANDARD);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mHasBeenCrossSite &&
+ NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false, false)) &&
+ (originalURI == uri ||
+ NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI,
+ false, false)))) {
+ return NS_OK;
+ }
+
+ // if the CSP directive 'upgrade-insecure-requests' is used 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 (CheckUpgradeInsecureRequestsPreventsCORS(mRequestingPrincipal, aChannel)) {
+ 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)) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Add the Origin header
+ nsAutoCString origin;
+ rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
+
+ rv = http->SetRequestHeader(NS_LITERAL_CSTRING("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 AsyncOpen2, since then AsyncOpen2 will take
+ // care of the cookie policy for us.
+ if (!mWithCredentials &&
+ (!loadInfo || !loadInfo->GetEnforceSecurity())) {
+ nsLoadFlags flags;
+ rv = http->GetLoadFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ flags |= nsIRequest::LOAD_ANONYMOUS;
+ rv = http->SetLoadFlags(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsCORSListenerProxy::CheckPreflightNeeded(nsIChannel* aChannel, UpdateType aUpdateType)
+{
+ // If this caller isn't using AsyncOpen2, or if this *is* a preflight channel,
+ // then we shouldn't initiate preflight for this channel.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (!loadInfo ||
+ loadInfo->GetSecurityMode() !=
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS ||
+ loadInfo->GetIsPreflight()) {
+ return NS_OK;
+ }
+
+ bool doPreflight = loadInfo->GetForcePreflight();
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ NS_ENSURE_TRUE(http, NS_ERROR_DOM_BAD_URI);
+ nsAutoCString method;
+ 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(NS_LITERAL_CSTRING("Content-Type"),
+ 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(NS_LITERAL_CSTRING("content-type"),
+ nsCaseInsensitiveCStringArrayComparator())) {
+ headers.AppendElements(loadInfoHeaders);
+ headers.AppendElement(NS_LITERAL_CSTRING("content-type"));
+ doPreflight = true;
+ }
+
+ if (!doPreflight) {
+ return NS_OK;
+ }
+
+ // A preflight is needed. But if we've already been cross-site, then
+ // we already did a preflight when that happened, and so we're not allowed
+ // to do another preflight again.
+ if (aUpdateType != UpdateType::InternalOrHSTSRedirect) {
+ NS_ENSURE_FALSE(mHasBeenCrossSite, NS_ERROR_DOM_BAD_URI);
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(http);
+ NS_ENSURE_TRUE(internal, NS_ERROR_DOM_BAD_URI);
+
+ internal->SetCorsPreflightParameters(
+ headers.IsEmpty() ? loadInfoHeaders : headers);
+
+ 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),
+ 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() {}
+
+ 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;
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
+ headerVal);
+ if (headerVal.IsEmpty()) {
+ return;
+ }
+
+ // 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).
+ uint32_t age = 0;
+ nsCSubstring::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);
+
+ nsPreflightCache::CacheEntry* entry =
+ sPreflightCache->GetEntry(uri, mReferrerPrincipal, mWithCredentials,
+ true);
+ if (!entry) {
+ return;
+ }
+
+ // The "Access-Control-Allow-Methods" header contains a comma separated
+ // list of method names.
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
+ headerVal);
+
+ nsCCharSeparatedTokenizer methods(headerVal, ',');
+ while(methods.hasMoreTokens()) {
+ const nsDependentCSubstring& method = methods.nextToken();
+ 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.
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
+ headerVal);
+
+ nsCCharSeparatedTokenizer headers(headerVal, ',');
+ while(headers.hasMoreTokens()) {
+ const nsDependentCSubstring& header = headers.nextToken();
+ 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,
+ nsISupports *aContext)
+{
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ bool responseSynthesized = false;
+ if (internal &&
+ NS_SUCCEEDED(internal->GetResponseSynthesized(&responseSynthesized))) {
+ // For synthesized responses, we don't need to perform any checks.
+ // This would be unsafe if we ever changed our behavior to allow
+ // service workers to intercept CORS preflights.
+ MOZ_ASSERT(!responseSynthesized);
+ }
+ }
+#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,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ mCallback = nullptr;
+ return NS_OK;
+}
+
+/** nsIStreamListener methods **/
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *ctxt,
+ 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))
+ 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);
+
+ bool succeedded;
+ rv = http->GetRequestSucceeded(&succeedded);
+ if (NS_FAILED(rv) || !succeedded) {
+ LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString headerVal;
+ // The "Access-Control-Allow-Methods" header contains a comma separated
+ // list of method names.
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
+ headerVal);
+ bool foundMethod = mPreflightMethod.EqualsLiteral("GET") ||
+ mPreflightMethod.EqualsLiteral("HEAD") ||
+ mPreflightMethod.EqualsLiteral("POST");
+ nsCCharSeparatedTokenizer methodTokens(headerVal, ',');
+ while(methodTokens.hasMoreTokens()) {
+ const nsDependentCSubstring& method = methodTokens.nextToken();
+ if (method.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(method)) {
+ LogBlockedRequest(aRequest, "CORSInvalidAllowMethod",
+ NS_ConvertUTF8toUTF16(method).get());
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ foundMethod |= mPreflightMethod.Equals(method);
+ }
+ if (!foundMethod) {
+ LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // The "Access-Control-Allow-Headers" header contains a comma separated
+ // list of header names.
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
+ headerVal);
+ nsTArray<nsCString> headers;
+ nsCCharSeparatedTokenizer headerTokens(headerVal, ',');
+ while(headerTokens.hasMoreTokens()) {
+ const nsDependentCSubstring& header = headerTokens.nextToken();
+ if (header.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(header)) {
+ LogBlockedRequest(aRequest, "CORSInvalidAllowHeader",
+ NS_ConvertUTF8toUTF16(header).get());
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ headers.AppendElement(header);
+ }
+ for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
+ if (!headers.Contains(mPreflightHeaders[i],
+ nsCaseInsensitiveCStringArrayComparator())) {
+ LogBlockedRequest(aRequest, "CORSMissingAllowHeaderFromPreflight",
+ NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get());
+ 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)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (sPreflightCache) {
+ sPreflightCache->RemoveEntries(aURI, aRequestingPrincipal);
+ }
+}
+
+nsresult
+nsCORSListenerProxy::StartCORSPreflight(nsIChannel* aRequestChannel,
+ nsICorsPreflightCallback* aCallback,
+ nsTArray<nsCString>& aUnsafeHeaders,
+ nsIChannel** aPreflightChannel)
+{
+ *aPreflightChannel = nullptr;
+
+ if (gDisableCORS) {
+ LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString method;
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
+ NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
+ httpChannel->GetRequestMethod(method);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo();
+ MOZ_ASSERT(originalLoadInfo, "can not perform CORS preflight without a loadInfo");
+ if (!originalLoadInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(originalLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
+ "how did we end up here?");
+
+ nsCOMPtr<nsIPrincipal> principal = originalLoadInfo->LoadingPrincipal();
+ MOZ_ASSERT(principal &&
+ originalLoadInfo->GetExternalContentPolicyType() !=
+ nsIContentPolicy::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 =
+ sPreflightCache ?
+ sPreflightCache->GetEntry(uri, principal, withCredentials, false) :
+ nullptr;
+
+ 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::LoadInfo*>
+ (originalLoadInfo.get())->CloneForNewRequest();
+ static_cast<mozilla::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,
+ 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(NS_LITERAL_CSTRING("OPTIONS"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = preHttp->
+ SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"),
+ method, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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(NS_LITERAL_CSTRING("Access-Control-Request-Headers"),
+ 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);
+
+ // Start preflight
+ rv = preflightChannel->AsyncOpen2(preflightListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Return newly created preflight channel
+ preflightChannel.forget(aPreflightChannel);
+
+ return NS_OK;
+}
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.h b/netwerk/protocol/http/nsCORSListenerProxy.h
new file mode 100644
index 0000000000..75918fc46d
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.h
@@ -0,0 +1,112 @@
+/* -*- 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 "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "mozilla/Attributes.h"
+
+class nsIURI;
+class nsIPrincipal;
+class nsINetworkInterceptController;
+class nsICorsPreflightCallback;
+
+namespace mozilla {
+namespace net {
+class HttpChannelParent;
+class nsHttpChannel;
+}
+}
+
+enum class DataURIHandling
+{
+ Allow,
+ Disallow
+};
+
+enum class UpdateType
+{
+ Default,
+ InternalOrHSTSRedirect
+};
+
+class nsCORSListenerProxy final : public nsIStreamListener,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsIThreadRetargetableStreamListener
+{
+public:
+ nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ // Must be called at startup.
+ static void Startup();
+
+ static void Shutdown();
+
+ nsresult Init(nsIChannel* aChannel, DataURIHandling aAllowDataURI);
+
+ void SetInterceptController(nsINetworkInterceptController* aInterceptController);
+
+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);
+ static nsresult StartCORSPreflight(nsIChannel* aRequestChannel,
+ nsICorsPreflightCallback* aCallback,
+ nsTArray<nsCString>& aACUnsafeHeaders,
+ nsIChannel** aPreflightChannel);
+
+ ~nsCORSListenerProxy();
+
+ nsresult UpdateChannel(nsIChannel* aChannel, DataURIHandling aAllowDataURI,
+ UpdateType aUpdateType);
+ nsresult CheckRequestApproved(nsIRequest* aRequest);
+ 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;
+ bool 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;
+#ifdef DEBUG
+ bool mInited;
+#endif
+};
+
+#endif
diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp
new file mode 100644
index 0000000000..faf9e21ffd
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -0,0 +1,481 @@
+/* -*- 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 "nsHttp.h"
+#include "PLDHashTable.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/HashFunctions.h"
+#include "nsCRT.h"
+#include <errno.h>
+
+namespace mozilla {
+namespace net {
+
+// define storage for all atoms
+#define HTTP_ATOM(_name, _value) nsHttpAtom nsHttp::_name = { _value };
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+
+// 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.
+nsresult
+nsHttp::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) nsHttp::_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
+nsHttp::DestroyAtomTable()
+{
+ delete sAtomTable;
+ sAtomTable = nullptr;
+
+ while (sHeapAtoms) {
+ HttpHeapAtom *next = sHeapAtoms->next;
+ free(sHeapAtoms);
+ sHeapAtoms = next;
+ }
+
+ delete sLock;
+ sLock = nullptr;
+}
+
+Mutex *
+nsHttp::GetLock()
+{
+ return sLock;
+}
+
+// this function may be called from multiple threads
+nsHttpAtom
+nsHttp::ResolveAtom(const char *str)
+{
+ nsHttpAtom atom = { nullptr };
+
+ 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
+nsHttp::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*
+nsHttp::GetProtocolVersion(uint32_t pv)
+{
+ switch (pv) {
+ case HTTP_VERSION_2:
+ case NS_HTTP_VERSION_2_0:
+ return "h2";
+ case NS_HTTP_VERSION_1_0:
+ return "http/1.0";
+ case NS_HTTP_VERSION_1_1:
+ return "http/1.1";
+ default:
+ NS_WARNING(nsPrintfCString("Unkown protocol version: 0x%X. "
+ "Please file a bug", pv).get());
+ return "http/1.1";
+ }
+}
+
+// static
+bool
+nsHttp::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 *
+nsHttp::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
+nsHttp::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=%ld errno=%d", value, errno));
+ return false;
+ }
+
+ if (next) {
+ *next = end;
+ }
+ *r = value;
+ return true;
+}
+
+bool
+nsHttp::IsPermanentRedirect(uint32_t httpStatus)
+{
+ return httpStatus == 301 || httpStatus == 308;
+}
+
+
+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 = 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);
+}
+///
+
+void
+ParsedHeaderValueList::Tokenize(char *input, uint32_t inputLen, char **token,
+ uint32_t *tokenLen, bool *foundEquals, char **next)
+{
+ if (foundEquals) {
+ *foundEquals = false;
+ }
+ if (next) {
+ *next = nullptr;
+ }
+ if (inputLen < 1 || !input || !token) {
+ return;
+ }
+
+ bool foundFirst = false;
+ bool inQuote = false;
+ bool foundToken = false;
+ *token = input;
+ *tokenLen = inputLen;
+
+ for (uint32_t index = 0; !foundToken && index < inputLen; ++index) {
+ // strip leading cruft
+ if (!foundFirst &&
+ (input[index] == ' ' || input[index] == '"' || input[index] == '\t')) {
+ (*token)++;
+ } else {
+ foundFirst = true;
+ }
+
+ if (input[index] == '"') {
+ inQuote = !inQuote;
+ continue;
+ }
+
+ if (inQuote) {
+ continue;
+ }
+
+ if (input[index] == '=' || input[index] == ';') {
+ *tokenLen = (input + index) - *token;
+ if (next && ((index + 1) < inputLen)) {
+ *next = input + index + 1;
+ }
+ foundToken = true;
+ if (foundEquals && input[index] == '=') {
+ *foundEquals = true;
+ }
+ break;
+ }
+ }
+
+ if (!foundToken) {
+ *tokenLen = (input + inputLen) - *token;
+ }
+
+ // strip trailing cruft
+ for (char *index = *token + *tokenLen - 1; index >= *token; --index) {
+ if (*index != ' ' && *index != '\t' && *index != '"') {
+ break;
+ }
+ --(*tokenLen);
+ if (*index == '"') {
+ break;
+ }
+ }
+}
+
+ParsedHeaderValueList::ParsedHeaderValueList(char *t, uint32_t len)
+{
+ char *name = nullptr;
+ uint32_t nameLen = 0;
+ char *value = nullptr;
+ uint32_t valueLen = 0;
+ char *next = nullptr;
+ bool foundEquals;
+
+ while (t) {
+ Tokenize(t, len, &name, &nameLen, &foundEquals, &next);
+ if (next) {
+ len -= next - t;
+ }
+ t = next;
+ if (foundEquals && t) {
+ Tokenize(t, len, &value, &valueLen, nullptr, &next);
+ if (next) {
+ len -= next - t;
+ }
+ t = next;
+ }
+ mValues.AppendElement(ParsedHeaderPair(name, nameLen, value, valueLen));
+ value = name = nullptr;
+ valueLen = nameLen = 0;
+ next = nullptr;
+ }
+}
+
+ParsedHeaderValueListList::ParsedHeaderValueListList(const nsCString &fullHeader)
+ : mFull(fullHeader)
+{
+ char *t = mFull.BeginWriting();
+ uint32_t len = mFull.Length();
+ char *last = t;
+ bool inQuote = false;
+ for (uint32_t index = 0; index < len; ++index) {
+ if (t[index] == '"') {
+ inQuote = !inQuote;
+ continue;
+ }
+ if (inQuote) {
+ continue;
+ }
+ if (t[index] == ',') {
+ mValues.AppendElement(ParsedHeaderValueList(last, (t + index) - last));
+ last = t + index + 1;
+ }
+ }
+ if (!inQuote) {
+ mValues.AppendElement(ParsedHeaderValueList(last, (t + len) - last));
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h
new file mode 100644
index 0000000000..a20637808a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.h
@@ -0,0 +1,276 @@
+/* -*- 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 nsHttp_h__
+#define nsHttp_h__
+
+#include <stdint.h>
+#include "prtime.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsError.h"
+#include "nsTArray.h"
+#include "mozilla/UniquePtr.h"
+
+// http version codes
+#define NS_HTTP_VERSION_UNKNOWN 0
+#define NS_HTTP_VERSION_0_9 9
+#define NS_HTTP_VERSION_1_0 10
+#define NS_HTTP_VERSION_1_1 11
+#define NS_HTTP_VERSION_2_0 20
+
+namespace mozilla {
+
+class Mutex;
+
+namespace net {
+ enum {
+ // SPDY_VERSION_2 = 2, REMOVED
+ // SPDY_VERSION_3 = 3, REMOVED
+ // SPDY_VERSION_31 = 4, REMOVED
+ HTTP_VERSION_2 = 5
+
+ // leave room for official versions. telem goes to 48
+ // 24 was a internal spdy/3.1
+ // 25 was spdy/4a2
+ // 26 was http/2-draft08 and http/2-draft07 (they were the same)
+ // 27 was http/2-draft09, h2-10, and h2-11
+ // 28 was http/2-draft12
+ // 29 was http/2-draft13
+ // 30 was h2-14 and h2-15
+ // 31 was h2-16
+ };
+
+typedef uint8_t nsHttpVersion;
+
+//-----------------------------------------------------------------------------
+// http connection capabilities
+//-----------------------------------------------------------------------------
+
+#define NS_HTTP_ALLOW_KEEPALIVE (1<<0)
+#define NS_HTTP_ALLOW_PIPELINING (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)
+
+// (1<<12) is used for NS_HTTP_URGENT_START on a newer branch
+
+// A sticky connection of the transaction is explicitly allowed to be restarted
+// on ERROR_NET_RESET.
+#define NS_HTTP_CONNECTION_RESTARTABLE (1<<13)
+
+//-----------------------------------------------------------------------------
+// some default values
+//-----------------------------------------------------------------------------
+
+#define NS_HTTP_DEFAULT_PORT 80
+#define NS_HTTPS_DEFAULT_PORT 443
+
+#define NS_HTTP_HEADER_SEPS ", \t"
+
+//-----------------------------------------------------------------------------
+// http atoms...
+//-----------------------------------------------------------------------------
+
+struct nsHttpAtom
+{
+ 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;
+};
+
+struct nsHttp
+{
+ static nsresult CreateAtomTable();
+ static 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
+ static Mutex *GetLock();
+
+ // will dynamically add atoms to the table if they don't already exist
+ static nsHttpAtom ResolveAtom(const char *);
+ static 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
+ static bool IsValidToken(const char *start, const char *end);
+
+ static inline bool IsValidToken(const nsACString &s) {
+ return IsValidToken(s.BeginReading(), s.EndReading());
+ }
+
+ // 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.
+ static 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.
+ static 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.
+ //
+ static 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.
+ static 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
+ static bool IsPermanentRedirect(uint32_t httpStatus);
+
+ // Returns the APLN token which represents the used protocol version.
+ static const char* GetProtocolVersion(uint32_t pv);
+
+ // 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) static nsHttpAtom _name;
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+};
+
+//-----------------------------------------------------------------------------
+// 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)
+ {
+ if (nameLen > 0) {
+ mName.Rebind(name, name + nameLen);
+ }
+ if (valLen > 0) {
+ mValue.Rebind(val, val + valLen);
+ }
+ }
+
+ ParsedHeaderPair(ParsedHeaderPair const &copy)
+ : mName(copy.mName)
+ , mValue(copy.mValue)
+ {
+ }
+
+ nsDependentCSubstring mName;
+ nsDependentCSubstring mValue;
+};
+
+class ParsedHeaderValueList
+{
+public:
+ ParsedHeaderValueList(char *t, uint32_t len);
+ nsTArray<ParsedHeaderPair> mValues;
+
+private:
+ void ParsePair(char *t, uint32_t len);
+ void Tokenize(char *input, uint32_t inputLen, char **token,
+ uint32_t *tokenLen, bool *foundEquals, char **next);
+};
+
+class ParsedHeaderValueListList
+{
+public:
+ explicit ParsedHeaderValueListList(const nsCString &txt);
+ nsTArray<ParsedHeaderValueList> mValues;
+
+private:
+ nsCString mFull;
+};
+
+
+} // 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..4b332a53d8
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.cpp
@@ -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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpActivityDistributor.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+typedef nsMainThreadPtrHolder<nsIHttpActivityObserver> ObserverHolder;
+typedef nsMainThreadPtrHandle<nsIHttpActivityObserver> ObserverHandle;
+typedef nsTArray<ObserverHandle> ObserverArray;
+
+class nsHttpActivityEvent : public Runnable
+{
+public:
+ nsHttpActivityEvent(nsISupports *aHttpChannel,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString & aExtraStringData,
+ ObserverArray *aObservers)
+ : mHttpChannel(aHttpChannel)
+ , mActivityType(aActivityType)
+ , mActivitySubtype(aActivitySubtype)
+ , mTimestamp(aTimestamp)
+ , mExtraSizeData(aExtraSizeData)
+ , mExtraStringData(aExtraStringData)
+ , mObservers(*aObservers)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ for (size_t i = 0 ; i < mObservers.Length() ; i++)
+ mObservers[i]->ObserveActivity(mHttpChannel, mActivityType,
+ mActivitySubtype, mTimestamp,
+ mExtraSizeData, mExtraStringData);
+ return NS_OK;
+ }
+
+private:
+ virtual ~nsHttpActivityEvent()
+ {
+ }
+
+ nsCOMPtr<nsISupports> mHttpChannel;
+ uint32_t mActivityType;
+ uint32_t mActivitySubtype;
+ PRTime mTimestamp;
+ uint64_t mExtraSizeData;
+ nsCString mExtraStringData;
+
+ ObserverArray mObservers;
+};
+
+NS_IMPL_ISUPPORTS(nsHttpActivityDistributor,
+ nsIHttpActivityDistributor,
+ nsIHttpActivityObserver)
+
+nsHttpActivityDistributor::nsHttpActivityDistributor()
+ : mLock("nsHttpActivityDistributor.mLock")
+{
+}
+
+nsHttpActivityDistributor::~nsHttpActivityDistributor()
+{
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::ObserveActivity(nsISupports *aHttpChannel,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString & aExtraStringData)
+{
+ nsCOMPtr<nsIRunnable> event;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (!mObservers.Length())
+ return NS_OK;
+
+ event = new nsHttpActivityEvent(aHttpChannel, aActivityType,
+ aActivitySubtype, aTimestamp,
+ aExtraSizeData, aExtraStringData,
+ &mObservers);
+ }
+ NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
+ return NS_DispatchToMainThread(event);
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetIsActive(bool *isActive)
+{
+ NS_ENSURE_ARG_POINTER(isActive);
+ MutexAutoLock lock(mLock);
+ *isActive = !!mObservers.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::AddObserver(nsIHttpActivityObserver *aObserver)
+{
+ MutexAutoLock lock(mLock);
+
+ ObserverHandle observer(new ObserverHolder(aObserver));
+ if (!mObservers.AppendElement(observer))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::RemoveObserver(nsIHttpActivityObserver *aObserver)
+{
+ MutexAutoLock lock(mLock);
+
+ ObserverHandle observer(new ObserverHolder(aObserver));
+ if (!mObservers.RemoveElement(observer))
+ return NS_ERROR_FAILURE;
+
+ 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..4da59de7ae
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.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 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();
+
+ ObserverArray mObservers;
+ Mutex mLock;
+};
+
+} // 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..5db985613e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******
+ 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(Alternate_Protocol, "Alternate-Protocol")
+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(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(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(Service_Worker_Allowed, "Service-Worker-Allowed")
+HTTP_ATOM(Set_Cookie, "Set-Cookie")
+HTTP_ATOM(Set_Cookie2, "Set-Cookie2")
+HTTP_ATOM(Status_URI, "Status-URI")
+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")
+
+// 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..be5cd17a77
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.cpp
@@ -0,0 +1,607 @@
+/* -*- 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 "nsHttpAuthCache.h"
+
+#include <stdlib.h>
+
+#include "mozilla/Attributes.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "mozIApplicationClearPrivateDataParams.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(nullptr)
+ , mObserver(new OriginClearObserver(this))
+{
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(mObserver, "clear-origin-attributes-data", false);
+ }
+}
+
+nsHttpAuthCache::~nsHttpAuthCache()
+{
+ if (mDB)
+ ClearAll();
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(mObserver, "clear-origin-attributes-data");
+ mObserver->mOwner = nullptr;
+ }
+}
+
+nsresult
+nsHttpAuthCache::Init()
+{
+ NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
+
+ LOG(("nsHttpAuthCache::Init\n"));
+
+ mDB = PL_NewHashTable(128, (PLHashFunction) PL_HashString,
+ (PLHashComparator) PL_CompareStrings,
+ (PLHashComparator) 0, &gHashAllocOps, this);
+ if (!mDB)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpAuthCache::GetAuthEntryForPath(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *path,
+ nsACString const &originSuffix,
+ nsHttpAuthEntry **entry)
+{
+ LOG(("nsHttpAuthCache::GetAuthEntryForPath [key=%s://%s:%d path=%s]\n",
+ scheme, host, port, path));
+
+ nsAutoCString key;
+ nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, originSuffix, key);
+ if (!node)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *entry = node->LookupEntryByPath(path);
+ 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 [key=%s://%s:%d realm=%s]\n",
+ scheme, host, port, realm));
+
+ nsAutoCString key;
+ nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, originSuffix, key);
+ if (!node)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *entry = node->LookupEntryByRealm(realm);
+ 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 [key=%s://%s:%d realm=%s path=%s metadata=%x]\n",
+ scheme, host, port, realm, path, metadata));
+
+ if (!mDB) {
+ rv = Init();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ 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();
+ if (!node)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+ if (NS_FAILED(rv))
+ delete node;
+ else
+ PL_HashTableAdd(mDB, strdup(key.get()), 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)
+{
+ if (!mDB)
+ return;
+
+ nsAutoCString key;
+ GetAuthKey(scheme, host, port, originSuffix, key);
+ PL_HashTableRemove(mDB, key.get());
+}
+
+nsresult
+nsHttpAuthCache::ClearAll()
+{
+ LOG(("nsHttpAuthCache::ClearAll\n"));
+
+ if (mDB) {
+ PL_HashTableDestroy(mDB);
+ mDB = 0;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache <private>
+//-----------------------------------------------------------------------------
+
+nsHttpAuthNode *
+nsHttpAuthCache::LookupAuthNode(const char *scheme,
+ const char *host,
+ int32_t port,
+ nsACString const &originSuffix,
+ nsCString &key)
+{
+ if (!mDB)
+ return nullptr;
+
+ GetAuthKey(scheme, host, port, originSuffix, key);
+
+ return (nsHttpAuthNode *) PL_HashTableLookup(mDB, key.get());
+}
+
+void *
+nsHttpAuthCache::AllocTable(void *self, size_t size)
+{
+ return malloc(size);
+}
+
+void
+nsHttpAuthCache::FreeTable(void *self, void *item)
+{
+ free(item);
+}
+
+PLHashEntry *
+nsHttpAuthCache::AllocEntry(void *self, const void *key)
+{
+ return (PLHashEntry *) malloc(sizeof(PLHashEntry));
+}
+
+void
+nsHttpAuthCache::FreeEntry(void *self, PLHashEntry *he, unsigned flag)
+{
+ if (flag == HT_FREE_VALUE) {
+ // this would only happen if PL_HashTableAdd were to replace an
+ // existing entry in the hash table, but we _always_ do a lookup
+ // before adding a new entry to avoid this case.
+ NS_NOTREACHED("should never happen");
+ }
+ else if (flag == HT_FREE_ENTRY) {
+ // three wonderful flavors of freeing memory ;-)
+ delete (nsHttpAuthNode *) he->value;
+ free((char *) he->key);
+ free(he);
+ }
+}
+
+PLHashAllocOps nsHttpAuthCache::gHashAllocOps =
+{
+ nsHttpAuthCache::AllocTable,
+ nsHttpAuthCache::FreeTable,
+ nsHttpAuthCache::AllocEntry,
+ nsHttpAuthCache::FreeEntry
+};
+
+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;
+}
+
+static int
+RemoveEntriesForPattern(PLHashEntry *entry, int32_t number, void *arg)
+{
+ nsDependentCString key(static_cast<const char*>(entry->key));
+
+ // Extract the origin attributes suffix from the key.
+ int32_t colon = key.Find(NS_LITERAL_CSTRING(":"));
+ MOZ_ASSERT(colon != kNotFound);
+ nsDependentCSubstring oaSuffix;
+ oaSuffix.Rebind(key.BeginReading(), colon);
+
+ // Build the NeckoOriginAttributes object of it...
+ NeckoOriginAttributes oa;
+ DebugOnly<bool> rv = oa.PopulateFromSuffix(oaSuffix);
+ MOZ_ASSERT(rv);
+
+ // ...and match it against the given pattern.
+ OriginAttributesPattern const *pattern = static_cast<OriginAttributesPattern const*>(arg);
+ if (pattern->Matches(oa)) {
+ return HT_ENUMERATE_NEXT | HT_ENUMERATE_REMOVE;
+ }
+ return HT_ENUMERATE_NEXT;
+}
+
+void
+nsHttpAuthCache::ClearOriginData(OriginAttributesPattern const &pattern)
+{
+ if (!mDB) {
+ return;
+ }
+ PL_HashTableEnumerateEntries(mDB, RemoveEntriesForPattern, (void*)&pattern);
+}
+
+//-----------------------------------------------------------------------------
+// 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 @%x\n", this));
+}
+
+nsHttpAuthNode::~nsHttpAuthNode()
+{
+ LOG(("Destroying nsHttpAuthNode @%x\n", this));
+
+ mList.Clear();
+}
+
+nsHttpAuthEntry *
+nsHttpAuthNode::LookupEntryByPath(const char *path)
+{
+ nsHttpAuthEntry *entry;
+
+ // 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) {
+ 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;
+ }
+ else if (strncmp(path, entryPath, strlen(entryPath)) == 0)
+ return entry;
+
+ authPath = authPath->mNext;
+ }
+ }
+ return nullptr;
+}
+
+nsHttpAuthEntry *
+nsHttpAuthNode::LookupEntryByRealm(const char *realm)
+{
+ nsHttpAuthEntry *entry;
+
+ // null realm matches empty realm
+ if (!realm)
+ realm = "";
+
+ // look for an entry that matches this realm
+ uint32_t i;
+ for (i=0; i<mList.Length(); ++i) {
+ entry = mList[i];
+ if (strcmp(realm, entry->Realm()) == 0)
+ return entry;
+ }
+ 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) {
+ entry = new nsHttpAuthEntry(path, realm, creds, challenge, ident, metadata);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // 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, entry);
+ }
+ else {
+ // update the entry...
+ entry->Set(path, realm, creds, challenge, ident, metadata);
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttpAuthNode::ClearAuthEntry(const char *realm)
+{
+ nsHttpAuthEntry *entry = LookupEntryByRealm(realm);
+ if (entry) {
+ mList.RemoveElement(entry); // double search OK
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpAuthCache.h b/netwerk/protocol/http/nsHttpAuthCache.h
new file mode 100644
index 0000000000..f96623d847
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.h
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "plhash.h"
+#include "nsIObserver.h"
+
+class nsCString;
+
+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)
+ {
+ Set(domain, user, password);
+ }
+ ~nsHttpAuthIdentity()
+ {
+ Clear();
+ }
+
+ const char16_t *Domain() const { return mDomain; }
+ const char16_t *User() const { return mUser; }
+ const char16_t *Password() const { return mPass; }
+
+ nsresult Set(const char16_t *domain,
+ const char16_t *user,
+ const char16_t *password);
+ 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; }
+
+ 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)
+ {
+ Set(path, realm, creds, challenge, ident, metadata);
+ }
+ ~nsHttpAuthEntry();
+
+ 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 nsAutoPtr<nsHttpAuthEntry>; // needs to call the destructor
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthNode
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthNode
+{
+private:
+ 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);
+
+ // if a matching entry is found, then credentials will be changed.
+ 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:
+ nsTArray<nsAutoPtr<nsHttpAuthEntry> > mList;
+
+ friend class nsHttpAuthCache;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache
+// (holds a hash table from host:port to nsHttpAuthNode)
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthCache
+{
+public:
+ nsHttpAuthCache();
+ ~nsHttpAuthCache();
+
+ nsresult Init();
+
+ // |scheme|, |host|, and |port| are required
+ // |path| can be null
+ // |entry| is either null or a weak reference
+ 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
+ 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.
+ 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.
+ nsresult ClearAll();
+
+private:
+ nsHttpAuthNode *LookupAuthNode(const char *scheme,
+ const char *host,
+ int32_t port,
+ nsACString const &originSuffix,
+ nsCString &key);
+
+ // hash table allocation functions
+ static void* AllocTable(void *, size_t size);
+ static void FreeTable(void *, void *item);
+ static PLHashEntry* AllocEntry(void *, const void *key);
+ static void FreeEntry(void *, PLHashEntry *he, unsigned flag);
+
+ static PLHashAllocOps gHashAllocOps;
+
+ class OriginClearObserver : public nsIObserver {
+ virtual ~OriginClearObserver() {}
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ explicit OriginClearObserver(nsHttpAuthCache* aOwner) : mOwner(aOwner) {}
+ nsHttpAuthCache* mOwner;
+ };
+
+ void ClearOriginData(OriginAttributesPattern const &pattern);
+
+private:
+ PLHashTable *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..a2f1b7c5e7
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.cpp
@@ -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/. */
+
+// 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()
+{
+}
+
+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;
+}
+
+nsHttpAuthManager::~nsHttpAuthManager()
+{
+}
+
+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) {
+ BasePrincipal::Cast(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) {
+ BasePrincipal::Cast(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()
+{
+ nsresult rv = mAuthCache->ClearAll();
+ nsresult rv2 = mPrivateAuthCache->ClearAll();
+ if (NS_FAILED(rv))
+ return rv;
+ if (NS_FAILED(rv2))
+ return rv2;
+ 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..261417c1dc
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.h
@@ -0,0 +1,35 @@
+/* -*- 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();
+ nsresult Init();
+
+protected:
+ virtual ~nsHttpAuthManager();
+
+ 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..7e1232fd8f
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "plbase64.h"
+#include "plstr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpBasicAuth <public>
+//-----------------------------------------------------------------------------
+
+nsHttpBasicAuth::nsHttpBasicAuth()
+{
+}
+
+nsHttpBasicAuth::~nsHttpBasicAuth()
+{
+}
+
+//-----------------------------------------------------------------------------
+// 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 ASCII around here
+ nsAutoCString userpass;
+ LossyCopyUTF16toASCII(user, userpass);
+ userpass.Append(':'); // always send a ':' (see bug 129565)
+ if (password)
+ LossyAppendUTF16toASCII(password, userpass);
+
+ // plbase64.h provides this worst-case output buffer size calculation.
+ // use calloc, since PL_Base64Encode does not null terminate.
+ *creds = (char *) calloc(6 + ((userpass.Length() + 2)/3)*4 + 1, 1);
+ if (!*creds)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ memcpy(*creds, "Basic ", 6);
+ PL_Base64Encode(userpass.get(), userpass.Length(), *creds + 6);
+ 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..b824b1d5ac
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.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 nsBasicAuth_h__
+#define nsBasicAuth_h__
+
+#include "nsIHttpAuthenticator.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();
+private:
+ virtual ~nsHttpBasicAuth();
+};
+
+} // 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..0e570e8cb5
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -0,0 +1,8563 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include <inttypes.h>
+
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/Sprintf.h"
+
+#include "nsHttp.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntry.h"
+#include "nsICaptivePortalService.h"
+#include "nsICryptoHash.h"
+#include "nsINetworkInterceptController.h"
+#include "nsINSSErrorsService.h"
+#include "nsISecurityReporter.h"
+#include "nsIStringBundle.h"
+#include "nsIStreamListenerTee.h"
+#include "nsISeekableStream.h"
+#include "nsILoadGroupChild.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIURIClassifier.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/dom/ContentVerifier.h"
+#include "mozilla/TimeStamp.h"
+#include "nsError.h"
+#include "nsPrintfCString.h"
+#include "nsAlgorithm.h"
+#include "nsQueryObject.h"
+#include "GeckoProfiler.h"
+#include "nsIConsoleService.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Preferences.h"
+#include "nsISSLSocketControl.h"
+#include "sslt.h"
+#include "nsContentUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIClassOfService.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISSLStatus.h"
+#include "nsISSLStatusProvider.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIWebProgressListener.h"
+#include "LoadContextInfo.h"
+#include "netCore.h"
+#include "nsHttpTransaction.h"
+#include "nsICacheEntryDescriptor.h"
+#include "nsICancelable.h"
+#include "nsIHttpChannelAuthProvider.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIHttpEventSink.h"
+#include "nsIPrompt.h"
+#include "nsInputStreamPump.h"
+#include "nsURLHelper.h"
+#include "nsISocketTransport.h"
+#include "nsIStreamConverterService.h"
+#include "nsISiteSecurityService.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "CacheObserver.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/Telemetry.h"
+#include "AlternateServices.h"
+#include "InterceptedChannel.h"
+#include "nsIHttpPushListener.h"
+#include "nsIX509Cert.h"
+#include "ScopedNSSTypes.h"
+#include "nsNullPrincipal.h"
+#include "nsIDeprecationWarner.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsICompressConvStats.h"
+#include "nsCORSListenerProxy.h"
+#include "nsISocketProvider.h"
+#include "mozilla/net/Predictor.h"
+#include "CacheControlParser.h"
+#include "nsMixedContentBlocker.h"
+#include "HSTSPrimerListener.h"
+#include "CacheStorageService.h"
+
+namespace mozilla { namespace net {
+
+namespace {
+
+// Monotonically increasing ID for generating unique cache entries per
+// intercepted channel.
+static uint64_t gNumIntercepted = 0;
+
+// True if the local cache should be bypassed when processing a request.
+#define BYPASS_LOCAL_CACHE(loadFlags) \
+ (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
+
+#define RECOVER_FROM_CACHE_FILE_ERROR(result) \
+ ((result) == NS_ERROR_FILE_NOT_FOUND || \
+ (result) == NS_ERROR_FILE_CORRUPTED || \
+ (result) == NS_ERROR_OUT_OF_MEMORY)
+
+static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
+static NS_DEFINE_CID(kStreamTransportServiceCID,
+ NS_STREAMTRANSPORTSERVICE_CID);
+
+enum CacheDisposition {
+ kCacheHit = 1,
+ kCacheHitViaReval = 2,
+ kCacheMissedViaReval = 3,
+ kCacheMissed = 4
+};
+
+void
+AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss)
+{
+ if (!CacheObserver::UseNewCache()) {
+ Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2, hitOrMiss);
+ }
+ else {
+ Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss);
+
+ int32_t experiment = CacheObserver::HalfLifeExperiment();
+ if (experiment > 0 && hitOrMiss == kCacheMissed) {
+ Telemetry::Accumulate(Telemetry::HTTP_CACHE_MISS_HALFLIFE_EXPERIMENT_2,
+ experiment - 1);
+ }
+ }
+}
+
+// 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);
+
+ static bool sForbid = true;
+ static nsresult once = Preferences::AddBoolVarCache(&sForbid, "network.appcache.forbid-fallback-outside-manifest-path", true);
+ Unused << once;
+
+ if (!sForbid) {
+ return true;
+ }
+
+ 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
+WillRedirect(nsHttpResponseHead * response)
+{
+ return nsHttpChannel::IsRedirectStatus(response->Status()) &&
+ response->HasHeader(nsHttp::Location);
+}
+
+nsresult
+StoreAuthorizationMetaData(nsICacheEntry *entry, nsHttpRequestHead *requestHead);
+
+class AutoRedirectVetoNotifier
+{
+public:
+ explicit AutoRedirectVetoNotifier(nsHttpChannel* channel) : mChannel(channel)
+ {
+ if (mChannel->mHasAutoRedirectVetoNotifier) {
+ MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
+ mChannel = nullptr;
+ return;
+ }
+
+ mChannel->mHasAutoRedirectVetoNotifier = 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;
+
+ 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->mHasAutoRedirectVetoNotifier = false;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <public>
+//-----------------------------------------------------------------------------
+
+nsHttpChannel::nsHttpChannel()
+ : HttpAsyncAborter<nsHttpChannel>(this)
+ , mLogicalOffset(0)
+ , mPostID(0)
+ , mRequestTime(0)
+ , mOfflineCacheLastModifiedTime(0)
+ , mInterceptCache(DO_NOT_INTERCEPT)
+ , mInterceptionID(gNumIntercepted++)
+ , mCacheOpenWithPriority(false)
+ , mCacheQueueSizeWhenOpen(0)
+ , mCachedContentIsValid(false)
+ , mCachedContentIsPartial(false)
+ , mCacheOnlyMetadata(false)
+ , mTransactionReplaced(false)
+ , mAuthRetryPending(false)
+ , mProxyAuthPending(false)
+ , mCustomAuthHeader(false)
+ , mResuming(false)
+ , mInitedCacheEntry(false)
+ , mFallbackChannel(false)
+ , mCustomConditionalRequest(false)
+ , mFallingBack(false)
+ , mWaitingForRedirectCallback(false)
+ , mRequestTimeInitialized(false)
+ , mCacheEntryIsReadOnly(false)
+ , mCacheEntryIsWriteOnly(false)
+ , mCacheEntriesToWaitFor(0)
+ , mHasQueryString(0)
+ , mConcurrentCacheAccess(0)
+ , mIsPartialRequest(0)
+ , mHasAutoRedirectVetoNotifier(0)
+ , mPinCacheContent(0)
+ , mIsCorsPreflightDone(0)
+ , mStronglyFramed(false)
+ , mUsedNetwork(0)
+ , mAuthConnectionRestartable(0)
+ , mPushedStream(nullptr)
+ , mLocalBlocklist(false)
+ , mWarningReporter(nullptr)
+ , 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)
+ mAuthProvider->Disconnect(NS_ERROR_ABORT);
+}
+
+nsresult
+nsHttpChannel::Init(nsIURI *uri,
+ uint32_t caps,
+ nsProxyInfo *proxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ const nsID& channelId)
+{
+ nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo,
+ proxyResolveFlags, proxyURI, channelId);
+ if (NS_FAILED(rv))
+ return rv;
+
+ LOG(("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);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpChannel::Connect()
+{
+ nsresult rv;
+
+ LOG(("nsHttpChannel::Connect [this=%p]\n", this));
+
+ // 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
+ nsContentPolicyType type = mLoadInfo ?
+ mLoadInfo->GetExternalContentPolicyType() :
+ nsIContentPolicy::TYPE_OTHER;
+
+ if (type == nsIContentPolicy::TYPE_DOCUMENT ||
+ type == nsIContentPolicy::TYPE_SUBDOCUMENT) {
+ rv = SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
+ NS_LITERAL_CSTRING("1"), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool isHttps = false;
+ rv = mURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ if (!isHttps && mLoadInfo) {
+ nsContentUtils::GetSecurityManager()->
+ GetChannelResultPrincipal(this, getter_AddRefs(resultPrincipal));
+ }
+ bool shouldUpgrade = false;
+ rv = NS_ShouldSecureUpgrade(mURI,
+ mLoadInfo,
+ resultPrincipal,
+ mPrivateBrowsing,
+ mAllowSTS,
+ shouldUpgrade);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (shouldUpgrade) {
+ 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) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+
+ // Finalize ConnectionInfo flags before SpeculativeConnect
+ mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
+ mConnectionInfo->SetPrivate(mPrivateBrowsing);
+ mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
+ mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) || mBeConservative);
+
+ // Consider opening a TCP connection right away.
+ SpeculativeConnect();
+
+ // Don't allow resuming when cache must be used
+ if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+ LOG(("Resuming from cache is not supported yet"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ // open a cache entry for this channel...
+ rv = OpenCacheEntry(isHttps);
+
+ // 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");
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("OpenCacheEntry failed [rv=%x]\n", 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 (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+ }
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ // otherwise, let's just proceed without using the cache.
+ }
+
+ return TryHSTSPriming();
+}
+
+nsresult
+nsHttpChannel::TryHSTSPriming()
+{
+ if (mLoadInfo) {
+ // HSTS priming requires the LoadInfo provided with AsyncOpen2
+ bool requireHSTSPriming =
+ mLoadInfo->GetForceHSTSPriming();
+
+ if (requireHSTSPriming &&
+ nsMixedContentBlocker::sSendHSTSPriming &&
+ mInterceptCache == DO_NOT_INTERCEPT) {
+ bool isHttpsScheme;
+ nsresult rv = mURI->SchemeIs("https", &isHttpsScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isHttpsScheme) {
+ rv = HSTSPrimingListener::StartHSTSPriming(this, this);
+
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // The request was already upgraded, for example by
+ // upgrade-insecure-requests or a prior successful priming request
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ HSTSPrimingResult::eHSTS_PRIMING_ALREADY_UPGRADED);
+ mLoadInfo->ClearHSTSPriming();
+ }
+ }
+
+ return ContinueConnect();
+}
+
+nsresult
+nsHttpChannel::ContinueConnect()
+{
+ // If we have had HSTS priming, we need to reevaluate whether we need
+ // a CORS preflight. Bug: 1272440
+ // 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 (!mIsCorsPreflightDone && mRequireCORSPreflight &&
+ mInterceptCache != INTERCEPTED) {
+ MOZ_ASSERT(!mPreflightChannel);
+ nsresult rv =
+ nsCORSListenerProxy::StartCORSPreflight(this, this,
+ mUnsafeHeaders,
+ getter_AddRefs(mPreflightChannel));
+ return rv;
+ }
+
+ MOZ_RELEASE_ASSERT(!(mRequireCORSPreflight &&
+ mInterceptCache != INTERCEPTED) ||
+ mIsCorsPreflightDone,
+ "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;
+ if (!mCachedContentIsPartial) {
+ AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
+ }
+ nsresult rv = ReadFromCache(true);
+ if (NS_FAILED(rv) && event) {
+ event->Revoke();
+ }
+
+ // Don't accumulate the cache hit telemetry for intercepted channels.
+ if (mInterceptCache != INTERCEPTED) {
+ AccumulateCacheHitTelemetry(kCacheHit);
+ }
+
+ return rv;
+ }
+ else 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 (!mFallbackChannel && !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...
+ nsresult rv = SetupTransaction();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mTransactionPump->AsyncRead(this, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t suspendCount = mSuspendCount;
+ 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 a local blocklist, 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 (mLocalBlocklist || 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 (mAllowStaleCacheContent) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+ if (!callbacks)
+ return;
+
+ gHttpHandler->SpeculativeConnect(
+ mConnectionInfo, callbacks, mCaps & NS_HTTP_DISALLOW_SPDY);
+}
+
+void
+nsHttpChannel::DoNotifyListenerCleanup()
+{
+ // We don't need this info anymore
+ CleanRedirectCacheChainIfNecessary();
+}
+
+void
+nsHttpChannel::HandleAsyncRedirect()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncRedirect;
+ 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)
+ ContinueHandleAsyncRedirect(rv);
+ }
+ }
+ else {
+ ContinueHandleAsyncRedirect(mStatus);
+ }
+}
+
+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=%x]\n", rv));
+
+ bool redirectsEnabled =
+ !mLoadInfo || !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);
+
+ mIsPending = false;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::HandleAsyncNotModified()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async not-modified [this=%p]\n",
+ this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncNotModified;
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
+
+ DoNotifyListener();
+
+ CloseCacheEntry(false);
+
+ mIsPending = false;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+void
+nsHttpChannel::HandleAsyncFallback()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncFallback;
+ 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);
+ }
+
+ ContinueHandleAsyncFallback(rv);
+}
+
+nsresult
+nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv)
+{
+ if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
+ // If ProcessFallback fails, then we have to send out the
+ // OnStart/OnStop notifications.
+ LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, mFallingBack));
+ mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
+ DoNotifyListener();
+ }
+
+ mIsPending = false;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return rv;
+}
+
+void
+nsHttpChannel::SetupTransactionRequestContext()
+{
+ if (!EnsureRequestContextID()) {
+ return;
+ }
+
+ nsIRequestContextService *rcsvc =
+ gHttpHandler->GetRequestContextService();
+ if (!rcsvc) {
+ return;
+ }
+
+ nsCOMPtr<nsIRequestContext> rc;
+ nsresult rv = rcsvc->GetRequestContext(mRequestContextID,
+ getter_AddRefs(rc));
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ mTransaction->SetRequestContext(rc);
+}
+
+static bool
+SafeForPipelining(nsHttpRequestHead::ParsedMethodType method,
+ const nsCString &methodString)
+{
+ if (method == nsHttpRequestHead::kMethod_Get ||
+ method == nsHttpRequestHead::kMethod_Head ||
+ method == nsHttpRequestHead::kMethod_Options) {
+ return true;
+ }
+
+ if (method != nsHttpRequestHead::kMethod_Custom) {
+ return false;
+ }
+
+ return (!strcmp(methodString.get(), "PROPFIND") ||
+ !strcmp(methodString.get(), "PROPPATCH"));
+}
+
+nsresult
+nsHttpChannel::SetupTransaction()
+{
+ LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
+
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ mUsedNetwork = 1;
+ if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
+ //
+ // disable pipelining if:
+ // (1) pipelining has been disabled by config
+ // (2) pipelining has been disabled by connection mgr info
+ // (3) request corresponds to a top-level document load (link click)
+ // (4) request method is non-idempotent
+ // (5) request is marked slow (e.g XHR)
+ //
+ nsAutoCString method;
+ mRequestHead.Method(method);
+ if (!mAllowPipelining ||
+ (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) ||
+ !SafeForPipelining(mRequestHead.ParsedMethod(), method)) {
+ LOG((" pipelining disallowed\n"));
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ }
+ }
+
+ if (!mAllowSpdy) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (mBeConservative) {
+ 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->GetPath(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, 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;
+ rv = mURI->Clone(getter_AddRefs(tempURI));
+ if (NS_FAILED(rv)) return rv;
+ rv = tempURI->SetUserPass(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ 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();
+ mRequestTimeInitialized = 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.
+ mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
+ // no-cache'
+ if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
+ mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
+ }
+ else if ((mLoadFlags & VALIDATE_ALWAYS) && !mCacheEntryIsWriteOnly) {
+ // 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() >= NS_HTTP_VERSION_1_1)
+ mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
+ else
+ mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ }
+
+ if (mResuming) {
+ char byteRange[32];
+ SprintfLiteral(byteRange, "bytes=%" PRIu64 "-", mStartPos);
+ mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
+
+ 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;
+ mRequestHead.SetHeader(nsHttp::If_Match,
+ NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
+
+ ++slash; // Incrementing, so that searching for '/' won't find
+ // the same slash again
+ }
+
+ if (FindCharInReadable('/', slash, end)) {
+ mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
+ Substring(++slash, end));
+ }
+ }
+ }
+
+ // 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();
+ LOG(("nsHttpChannel %p created nsHttpTransaction %p\n", this, mTransaction.get()));
+ mTransaction->SetTransactionObserver(mTransactionObserver);
+ mTransactionObserver = nullptr;
+
+ // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
+ if (mLoadFlags & LOAD_ANONYMOUS)
+ mCaps |= NS_HTTP_LOAD_ANONYMOUS;
+
+ if (mTimingEnabled)
+ mCaps |= NS_HTTP_TIMING_ENABLED;
+
+ if (mUpgradeProtocolCallback) {
+ mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
+ mRequestHead.SetHeaderOnce(nsHttp::Connection,
+ nsHttp::Upgrade.get(),
+ true);
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+ }
+
+ if (mPushedStream) {
+ mTransaction->SetPushedStream(mPushedStream);
+ mPushedStream = nullptr;
+ }
+
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+ if (pushListener) {
+ mCaps |= NS_HTTP_ONPUSH_LISTENER;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> responseStream;
+ rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
+ mUploadStream, mUploadStreamHasHeaders,
+ NS_GetCurrentThread(), callbacks, this,
+ getter_AddRefs(responseStream));
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ return rv;
+ }
+
+ mTransaction->SetClassOfService(mClassOfService);
+ SetupTransactionRequestContext();
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
+ responseStream);
+ return rv;
+}
+
+// NOTE: This function duplicates code from nsBaseChannel. This will go away
+// once HTTP uses nsBaseChannel (part of bug 312760)
+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);
+ }
+}
+
+// Helper Function to report messages to the console when loading
+// a resource was blocked due to a MIME type mismatch.
+void
+ReportTypeBlocking(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ const char* aMessageName)
+{
+ NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
+ const char16_t* params[] = { specUTF16.get() };
+ nsCOMPtr<nsIDocument> doc;
+ if (aLoadInfo) {
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ aLoadInfo->GetLoadingDocument(getter_AddRefs(domDoc));
+ if (domDoc) {
+ doc = do_QueryInterface(domDoc);
+ }
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("MIMEMISMATCH"),
+ doc,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aMessageName,
+ params, ArrayLength(params));
+}
+
+// Check and potentially enforce X-Content-Type-Options: nosniff
+nsresult
+ProcessXCTO(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;
+ aResponseHead->GetHeader(nsHttp::X_Content_Type_Options, contentTypeOptionsHeader);
+ if (contentTypeOptionsHeader.IsEmpty()) {
+ // if there is no XCTO header, then there is nothing to do.
+ return NS_OK;
+ }
+ // 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"
+ contentTypeOptionsHeader.StripWhitespace();
+ // c) 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.
+ NS_ConvertUTF8toUTF16 char16_header(contentTypeOptionsHeader);
+ const char16_t* params[] = { char16_header.get() };
+ nsCOMPtr<nsIDocument> doc;
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ aLoadInfo->GetLoadingDocument(getter_AddRefs(domDoc));
+ if (domDoc) {
+ doc = do_QueryInterface(domDoc);
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XCTO"),
+ doc,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ "XCTOHeaderValueMissing",
+ params, ArrayLength(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() == nsIContentPolicy::TYPE_STYLESHEET) {
+ if (contentType.EqualsLiteral(TEXT_CSS)) {
+ return NS_OK;
+ }
+ ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_IMAGE) {
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
+ Accumulate(Telemetry::XCTO_NOSNIFF_BLOCK_IMAGE, 0);
+ return NS_OK;
+ }
+ Accumulate(Telemetry::XCTO_NOSNIFF_BLOCK_IMAGE, 1);
+ // Instead of consulting Preferences::GetBool() all the time we
+ // can cache the result to speed things up.
+ static bool sXCTONosniffBlockImages = false;
+ static bool sIsInited = false;
+ if (!sIsInited) {
+ sIsInited = true;
+ Preferences::AddBoolVarCache(&sXCTONosniffBlockImages,
+ "security.xcto_nosniff_block_images");
+ }
+ if (!sXCTONosniffBlockImages) {
+ return NS_OK;
+ }
+ ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_SCRIPT) {
+ if (nsContentUtils::IsScriptType(contentType)) {
+ return NS_OK;
+ }
+ ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ return NS_OK;
+}
+
+// Ensure that a load of type script has correct MIME type
+nsresult
+EnsureMIMEOfScript(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() != nsIContentPolicy::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
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 1);
+ return NS_OK;
+ }
+
+ bool block = false;
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
+ // script load has type image
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 2);
+ block = true;
+ } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("audio/"))) {
+ // script load has type audio
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 3);
+ block = true;
+ } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("video/"))) {
+ // script load has type video
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 4);
+ block = true;
+ } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/csv"))) {
+ // script load has type text/csv
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 6);
+ block = true;
+ }
+
+ if (block) {
+ // Instead of consulting Preferences::GetBool() all the time we
+ // can cache the result to speed things up.
+ static bool sCachedBlockScriptWithWrongMime = false;
+ static bool sIsInited = false;
+ if (!sIsInited) {
+ sIsInited = true;
+ Preferences::AddBoolVarCache(&sCachedBlockScriptWithWrongMime,
+ "security.block_script_with_wrong_mime");
+ }
+
+ // Do not block the load if the feature is not enabled.
+ if (!sCachedBlockScriptWithWrongMime) {
+ return NS_OK;
+ }
+
+ ReportTypeBlocking(aURI, aLoadInfo, "BlockScriptWithWrongMimeType");
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/plain"))) {
+ // script load has type text/plain
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 5);
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/xml"))) {
+ // script load has type text/xml
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 7);
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("application/octet-stream"))) {
+ // script load has type application/octet-stream
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 8);
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("application/xml"))) {
+ // script load has type application/xml
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 9);
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/html"))) {
+ // script load has type text/html
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 10);
+ return NS_OK;
+ }
+
+ if (contentType.IsEmpty()) {
+ // script load has no type
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 11);
+ return NS_OK;
+ }
+
+ // script load has unknown type
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 0);
+ return NS_OK;
+}
+
+
+nsresult
+nsHttpChannel::CallOnStartRequest()
+{
+ MOZ_RELEASE_ASSERT(!(mRequireCORSPreflight &&
+ mInterceptCache != INTERCEPTED) ||
+ mIsCorsPreflightDone,
+ "CORS preflight must have been finished by the time we "
+ "call OnStartRequest");
+
+ nsresult rv = EnsureMIMEOfScript(mURI, mResponseHead, mLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProcessXCTO(mURI, mResponseHead, mLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mOnStartRequestCalled) {
+ // 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(mConcurrentCacheAccess);
+ LOG(("CallOnStartRequest already invoked before"));
+ return mStatus;
+ }
+
+ mTracingEnabled = false;
+
+ // 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) {
+ mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
+ }
+ }
+
+ 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() == NS_HTTP_VERSION_0_9 &&
+ mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort())
+ mResponseHead->SetContentType(NS_LITERAL_CSTRING(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,
+ mListenerContext,
+ getter_AddRefs(converter));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = converter;
+ unknownDecoderStarted = true;
+ }
+ }
+ }
+ }
+
+ if (mResponseHead && !mResponseHead->HasContentCharset())
+ mResponseHead->SetContentCharset(mContentCharsetHint);
+
+ if (mResponseHead && mCacheEntry) {
+ // If we have a cache entry, set its predicted size to TotalEntitySize to
+ // avoid caching an entry that will exceed the max size limit.
+ rv = mCacheEntry->SetPredictedDataSize(
+ mResponseHead->TotalEntitySize());
+ if (NS_ERROR_FILE_TOO_BIG == rv) {
+ // Don't throw the entry away, we will need it later.
+ LOG((" entry too big"));
+ } else {
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ LOG((" calling mListener->OnStartRequest\n"));
+ if (mListener) {
+ MOZ_ASSERT(!mOnStartRequestCalled,
+ "We should not call OsStartRequest twice");
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ rv = deleteProtector->OnStartRequest(this, mListenerContext);
+ mOnStartRequestCalled = true;
+ if (NS_FAILED(rv))
+ return rv;
+ } else {
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ mOnStartRequestCalled = true;
+ }
+
+ // Install stream converter if required.
+ // If we use unknownDecoder, stream converters will be installed later (in
+ // nsUnknownDecoder) after OnStartRequest is called for the real listener.
+ if (!unknownDecoderStarted) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsISupports *ctxt = mListenerContext;
+ rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), ctxt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (listener) {
+ mListener = listener;
+ mCompressListener = listener;
+ }
+ }
+
+ rv = EnsureAssocReq();
+ if (NS_FAILED(rv))
+ return rv;
+
+ // if this channel is for a download, close off access to the cache.
+ if (mCacheEntry && mChannelIsForDownload) {
+ 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.
+ if (!mCachedContentIsPartial && !mConcurrentCacheAccess)
+ 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();
+ }
+ }
+
+ // Check for a Content-Signature header and inject mediator if the header is
+ // requested and available.
+ // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
+ // present but not valid, fail this channel and return
+ // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
+ // fallback load in nsDocShell.
+ // Note that OnStartRequest has already been called on the target stream
+ // listener at this point. We have to add the listener here that late to
+ // ensure that it's the last listener and can thus block the load in
+ // OnStopRequest.
+ if (!mCanceled) {
+ rv = ProcessContentSignatureHeader(mResponseHead);
+ if (NS_FAILED(rv)) {
+ LOG(("Content-signature verification failed.\n"));
+ return rv;
+ }
+ }
+
+ 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;
+ switch (httpStatus)
+ {
+ 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;
+ case 403: // HTTP/1.1: "Forbidden"
+ case 407: // ProcessAuthentication() failed
+ case 501: // HTTP/1.1: "Not Implemented"
+ // user sees boilerplate Mozilla "Proxy Refused Connection" page.
+ rv = NS_ERROR_PROXY_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"
+ /* User sees: "Address Not Found: Firefox can't find the server at
+ * www.foo.com."
+ */
+ rv = NS_ERROR_UNKNOWN_HOST;
+ break;
+ case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
+ // Squid returns 503 if target request fails for anything but DNS.
+ case 503: // HTTP/1.1: "Service Unavailable"
+ /* 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: // HTTP/1.1: "Gateway Timeout"
+ // user sees: "Network Timeout: The server at www.foo.com
+ // is taking too long to respond."
+ rv = NS_ERROR_NET_TIMEOUT;
+ break;
+ // Confused proxy server or malicious response
+ default:
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ break;
+ }
+ LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n",
+ this, httpStatus));
+ Cancel(rv);
+ CallOnStartRequest();
+ return rv;
+}
+
+static void
+GetSTSConsoleErrorTag(uint32_t failureResult, nsAString& consoleErrorTag)
+{
+ switch (failureResult) {
+ case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
+ consoleErrorTag = NS_LITERAL_STRING("STSUntrustworthyConnection");
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
+ consoleErrorTag = NS_LITERAL_STRING("STSCouldNotParseHeader");
+ break;
+ case nsISiteSecurityService::ERROR_NO_MAX_AGE:
+ consoleErrorTag = NS_LITERAL_STRING("STSNoMaxAge");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
+ consoleErrorTag = NS_LITERAL_STRING("STSMultipleMaxAges");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
+ consoleErrorTag = NS_LITERAL_STRING("STSInvalidMaxAge");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = NS_LITERAL_STRING("STSMultipleIncludeSubdomains");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = NS_LITERAL_STRING("STSInvalidIncludeSubdomains");
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
+ consoleErrorTag = NS_LITERAL_STRING("STSCouldNotSaveState");
+ break;
+ default:
+ consoleErrorTag = NS_LITERAL_STRING("STSUnknownError");
+ break;
+ }
+}
+
+static void
+GetPKPConsoleErrorTag(uint32_t failureResult, nsAString& consoleErrorTag)
+{
+ switch (failureResult) {
+ case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
+ consoleErrorTag = NS_LITERAL_STRING("PKPUntrustworthyConnection");
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
+ consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotParseHeader");
+ break;
+ case nsISiteSecurityService::ERROR_NO_MAX_AGE:
+ consoleErrorTag = NS_LITERAL_STRING("PKPNoMaxAge");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
+ consoleErrorTag = NS_LITERAL_STRING("PKPMultipleMaxAges");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
+ consoleErrorTag = NS_LITERAL_STRING("PKPInvalidMaxAge");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = NS_LITERAL_STRING("PKPMultipleIncludeSubdomains");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = NS_LITERAL_STRING("PKPInvalidIncludeSubdomains");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_PIN:
+ consoleErrorTag = NS_LITERAL_STRING("PKPInvalidPin");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS:
+ consoleErrorTag = NS_LITERAL_STRING("PKPMultipleReportURIs");
+ break;
+ case nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN:
+ consoleErrorTag = NS_LITERAL_STRING("PKPPinsetDoesNotMatch");
+ break;
+ case nsISiteSecurityService::ERROR_NO_BACKUP_PIN:
+ consoleErrorTag = NS_LITERAL_STRING("PKPNoBackupPin");
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
+ consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotSaveState");
+ break;
+ case nsISiteSecurityService::ERROR_ROOT_NOT_BUILT_IN:
+ consoleErrorTag = NS_LITERAL_STRING("PKPRootNotBuiltIn");
+ break;
+ default:
+ consoleErrorTag = NS_LITERAL_STRING("PKPUnknownError");
+ break;
+ }
+}
+
+/**
+ * Process a single security header. Only two types are supported: HSTS and HPKP.
+ */
+nsresult
+nsHttpChannel::ProcessSingleSecurityHeader(uint32_t aType,
+ nsISSLStatus *aSSLStatus,
+ uint32_t aFlags)
+{
+ nsHttpAtom atom;
+ switch (aType) {
+ case nsISiteSecurityService::HEADER_HSTS:
+ atom = nsHttp::ResolveAtom("Strict-Transport-Security");
+ break;
+ case nsISiteSecurityService::HEADER_HPKP:
+ atom = nsHttp::ResolveAtom("Public-Key-Pins");
+ break;
+ default:
+ NS_NOTREACHED("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)
+ uint32_t failureResult;
+ rv = sss->ProcessHeader(aType, mURI, securityHeader.get(), aSSLStatus,
+ aFlags, nullptr, nullptr, &failureResult);
+ if (NS_FAILED(rv)) {
+ nsAutoString consoleErrorCategory;
+ nsAutoString consoleErrorTag;
+ switch (aType) {
+ case nsISiteSecurityService::HEADER_HSTS:
+ GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
+ consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
+ break;
+ case nsISiteSecurityService::HEADER_HPKP:
+ GetPKPConsoleErrorTag(failureResult, consoleErrorTag);
+ consoleErrorCategory = NS_LITERAL_STRING("Invalid HPKP Headers");
+ break;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+ 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()
+{
+ nsresult rv;
+ bool isHttps = false;
+ rv = mURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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 (!isHttps)
+ return NS_OK;
+
+ nsAutoCString asciiHost;
+ 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 SSLStatus
+ nsCOMPtr<nsISSLStatusProvider> sslprov = do_QueryInterface(mSecurityInfo);
+ NS_ENSURE_TRUE(sslprov, NS_ERROR_FAILURE);
+ nsCOMPtr<nsISSLStatus> sslStatus;
+ rv = sslprov->GetSSLStatus(getter_AddRefs(sslStatus));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(sslStatus, NS_ERROR_FAILURE);
+
+ rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HSTS,
+ sslStatus, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HPKP,
+ sslStatus, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::ProcessContentSignatureHeader(nsHttpResponseHead *aResponseHead)
+{
+ nsresult rv = NS_OK;
+
+ // we only do this if we require it in loadInfo
+ if (!mLoadInfo || !mLoadInfo->GetVerifySignedContent()) {
+ return NS_OK;
+ }
+
+ // check if we verify content signatures on this newtab channel
+ if (gHttpHandler->NewTabContentSignaturesDisabled()) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(aResponseHead, NS_ERROR_ABORT);
+ nsAutoCString contentSignatureHeader;
+ nsHttpAtom atom = nsHttp::ResolveAtom("Content-Signature");
+ rv = aResponseHead->GetHeader(atom, contentSignatureHeader);
+ if (NS_FAILED(rv)) {
+ LOG(("Content-Signature header is missing but expected."));
+ DoInvalidateCacheEntry(mURI);
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // if we require a signature but it is empty, fail
+ if (contentSignatureHeader.IsEmpty()) {
+ DoInvalidateCacheEntry(mURI);
+ LOG(("An expected content-signature header is missing.\n"));
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // we ensure a content type here to avoid running into problems with
+ // content sniffing, which might sniff parts of the content before we can
+ // verify the signature
+ if (!aResponseHead->HasContentType()) {
+ NS_WARNING("Empty content type can get us in trouble when verifying "
+ "content signatures");
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+ // create a new listener that meadiates the content
+ RefPtr<ContentVerifier> contentVerifyingMediator =
+ new ContentVerifier(mListener, mListenerContext);
+ rv = contentVerifyingMediator->Init(contentSignatureHeader, this,
+ mListenerContext);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
+ mListener = contentVerifyingMediator;
+
+ return NS_OK;
+}
+
+/**
+ * Decide whether or not to send a security report and, if so, give the
+ * SecurityReporter the information required to send such a report.
+ */
+void
+nsHttpChannel::ProcessSecurityReport(nsresult status) {
+ uint32_t errorClass;
+ nsCOMPtr<nsINSSErrorsService> errSvc =
+ do_GetService("@mozilla.org/nss_errors_service;1");
+ // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
+ // not in the set of errors covered by the NSS errors service.
+ nsresult rv = errSvc->GetErrorClass(status, &errorClass);
+ if (!NS_SUCCEEDED(rv)) {
+ return;
+ }
+
+ // if the content was not loaded succesfully and we have security info,
+ // send a TLS error report - we must do this early as other parts of
+ // OnStopRequest can return early
+ bool reportingEnabled =
+ Preferences::GetBool("security.ssl.errorReporting.enabled");
+ bool reportingAutomatic =
+ Preferences::GetBool("security.ssl.errorReporting.automatic");
+ if (!mSecurityInfo || !reportingEnabled || !reportingAutomatic) {
+ return;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> secInfo =
+ do_QueryInterface(mSecurityInfo);
+ nsCOMPtr<nsISecurityReporter> errorReporter =
+ do_GetService("@mozilla.org/securityreporter;1");
+
+ if (!secInfo || !mURI) {
+ return;
+ }
+
+ nsAutoCString hostStr;
+ int32_t port;
+ rv = mURI->GetHost(hostStr);
+ if (!NS_SUCCEEDED(rv)) {
+ return;
+ }
+
+ rv = mURI->GetPort(&port);
+
+ if (NS_SUCCEEDED(rv)) {
+ errorReporter->ReportTLSError(secInfo, hostStr, port);
+ }
+}
+
+bool
+nsHttpChannel::IsHTTPS()
+{
+ bool isHttps;
+ if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps)
+ return false;
+ return true;
+}
+
+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<nsISSLStatusProvider> statusProvider =
+ do_QueryInterface(mSecurityInfo);
+ if (!statusProvider)
+ return;
+ nsCOMPtr<nsISSLStatus> sslstat;
+ statusProvider->GetSSLStatus(getter_AddRefs(sslstat));
+ if (!sslstat)
+ return;
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo =
+ do_QueryInterface(mSecurityInfo);
+ 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 = NS_LITERAL_STRING("WeakCipherSuiteWarning");
+ nsString consoleErrorCategory = NS_LITERAL_STRING("SSL");
+ AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ }
+ }
+
+ // Send (SHA-1) signature algorithm errors to the web console
+ nsCOMPtr<nsIX509Cert> cert;
+ sslstat->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 = NS_LITERAL_STRING("SHA1Sig");
+ nsString consoleErrorMessage
+ = NS_LITERAL_STRING("SHA-1 Signature");
+ AddSecurityMessage(consoleErrorTag, consoleErrorMessage);
+ }
+ }
+ }
+}
+
+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 (!mAllowAltSvc) { // per channel opt out
+ return;
+ }
+
+ if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
+ return;
+ }
+
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ bool isHttp = scheme.Equals(NS_LITERAL_CSTRING("http"));
+ if (!isHttp && !scheme.Equals(NS_LITERAL_CSTRING("https"))) {
+ return;
+ }
+
+ nsAutoCString altSvc;
+ 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->GetHost(originHost))) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+ if (mProxyInfo) {
+ proxyInfo = do_QueryInterface(mProxyInfo);
+ }
+
+ NeckoOriginAttributes originAttributes;
+ NS_GetOriginAttributes(this, originAttributes);
+
+ AltSvcMapping::ProcessHeader(altSvc, scheme, originHost, originPort,
+ mUsername, mPrivateBrowsing, callbacks, proxyInfo,
+ mCaps & NS_HTTP_DISALLOW_SPDY,
+ originAttributes);
+}
+
+nsresult
+nsHttpChannel::ProcessResponse()
+{
+ uint32_t httpStatus = mResponseHead->Status();
+
+ LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n",
+ this, httpStatus));
+
+ // do some telemetry
+ if (gHttpHandler->IsTelemetryEnabled()) {
+ // 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());
+ }
+
+ // how often do we see something like Alternate-Protocol: "443:quic,p=1"
+ nsAutoCString alt_protocol;
+ mResponseHead->GetHeader(nsHttp::Alternate_Protocol, alt_protocol);
+ bool saw_quic = (!alt_protocol.IsEmpty() &&
+ PL_strstr(alt_protocol.get(), "quic")) ? 1 : 0;
+ Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL, 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 mReferrer may not be set at all, or may
+ // not be a full URI (HttpBaseChannel::SetReferrer has the gorey details).
+ // If that's null, though, we'll fall back to mReferrer just in case (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) {
+ referrer = mReferrer;
+ }
+ if (referrer) {
+ nsCOMPtr<nsILoadContextInfo> lci = GetLoadContextInfo(this);
+ mozilla::net::Predictor::UpdateCacheability(referrer, mURI, httpStatus,
+ mRequestHead, mResponseHead,
+ lci);
+ }
+
+ if (mTransaction->ProxyConnectFailed()) {
+ // Only allow 407 (authentication required) to continue
+ if (httpStatus != 407)
+ return ProcessFailedProxyConnect(httpStatus);
+ // If proxy CONNECT response needs to complete, wait to process connection
+ // for Strict-Transport-Security.
+ } else {
+ // 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.");
+ }
+
+ MOZ_ASSERT(!mCachedContentIsValid);
+
+ 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()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to finish processing response [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::AsyncContinueProcessResponse;
+ return NS_OK;
+ }
+
+ uint32_t httpStatus = mResponseHead->Status();
+
+ // Cookies and Alt-Service should not be handled on proxy failure either.
+ // This would be consolidated with ProcessSecurityHeaders but it should
+ // happen after OnExamineResponse.
+ if (!mTransaction->ProxyConnectFailed() && (httpStatus != 407)) {
+ nsAutoCString cookie;
+ if (NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
+ SetCookie(cookie.get());
+ }
+ if ((httpStatus < 500) && (httpStatus != 421)) {
+ ProcessAltService();
+ }
+ }
+
+ if (mConcurrentCacheAccess && mCachedContentIsPartial && 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)
+ mAuthProvider->CheckForSuperfluousAuth();
+ if (mCanceled)
+ return CallOnStartRequest();
+
+ // reset the authentication's current continuation state because our
+ // last authentication attempt has been completed successfully
+ mAuthProvider->Disconnect(NS_ERROR_ABORT);
+ mAuthProvider = nullptr;
+ LOG((" continuation state has been reset"));
+ }
+
+ if (mAPIRedirectToURI && !mCanceled) {
+ MOZ_ASSERT(!mOnStartRequestCalled);
+ nsCOMPtr<nsIURI> redirectTo;
+ mAPIRedirectToURI.swap(redirectTo);
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse2);
+ rv = StartRedirectChannelToURI(redirectTo, nsIChannelEventSink::REDIRECT_TEMPORARY);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse2);
+ }
+
+ // Hack: ContinueProcessResponse2 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 ContinueProcessResponse2(NS_BINDING_FAILED);
+}
+
+nsresult
+nsHttpChannel::ContinueProcessResponse2(nsresult 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 successfulReval = false;
+
+ // 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 (mResuming && 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 (mCachedContentIsPartial) // an internal byte range request...
+ rv = ProcessPartialContent();
+ 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::ContinueProcessResponse3);
+ rv = AsyncProcessRedirection(httpStatus);
+ if (NS_FAILED(rv)) {
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
+ LOG(("AsyncProcessRedirection failed [rv=%x]\n", rv));
+ // don't cache failed redirect responses.
+ if (mCacheEntry)
+ mCacheEntry->AsyncDoom(nullptr);
+ if (DoNotRender3xxBody(rv)) {
+ mStatus = rv;
+ DoNotifyListener();
+ } else {
+ rv = ContinueProcessResponse3(rv);
+ }
+ }
+ break;
+ case 304:
+ if (!ShouldBypassProcessNotModified()) {
+ rv = ProcessNotModified();
+ if (NS_SUCCEEDED(rv)) {
+ successfulReval = true;
+ break;
+ }
+
+ LOG(("ProcessNotModified failed [rv=%x]\n", rv));
+
+ // 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;
+ }
+
+ rv = StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
+ rv = ProcessNormal();
+ }
+ break;
+ case 401:
+ case 407:
+ if (MOZ_UNLIKELY(mCustomAuthHeader) && 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->ProxyConnectFailed());
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // authentication prompt has been invoked and result
+ // is expected asynchronously
+ mAuthRetryPending = true;
+ if (httpStatus == 407 || mTransaction->ProxyConnectFailed())
+ mProxyAuthPending = 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();
+ rv = NS_OK;
+ } else if (NS_FAILED(rv)) {
+ LOG(("ProcessAuthentication failed [rv=%x]\n", rv));
+ if (mTransaction->ProxyConnectFailed())
+ return ProcessFailedProxyConnect(httpStatus);
+ if (!mAuthRetryPending)
+ mAuthProvider->CheckForSuperfluousAuth();
+ rv = ProcessNormal();
+ } else {
+ mAuthRetryPending = true; // see DoAuthRetry
+ }
+ break;
+ default:
+ rv = ProcessNormal();
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ break;
+ }
+
+ if (gHttpHandler->IsTelemetryEnabled()) {
+ CacheDisposition cacheDisposition;
+ if (!mDidReval) {
+ cacheDisposition = kCacheMissed;
+ } else if (successfulReval) {
+ cacheDisposition = kCacheHitViaReval;
+ } else {
+ cacheDisposition = kCacheMissedViaReval;
+ }
+ AccumulateCacheHitTelemetry(cacheDisposition);
+
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
+ mResponseHead->Version());
+
+ if (mResponseHead->Version() == NS_HTTP_VERSION_0_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);
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsHttpChannel::ContinueProcessResponse3(nsresult rv)
+{
+ bool doNotRender = DoNotRender3xxBody(rv);
+
+ if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
+ bool isHTTP = false;
+ if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP)))
+ isHTTP = false;
+ if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP)))
+ isHTTP = false;
+
+ 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(("ContinueProcessResponse3 detected rejected Non-HTTP Redirection"));
+ doNotRender = true;
+ rv = NS_ERROR_CORRUPTED_CONTENT;
+ }
+ }
+
+ if (doNotRender) {
+ Cancel(rv);
+ DoNotifyListener();
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ UpdateInhibitPersistentCachingFlag();
+
+ InitCacheEntry();
+ CloseCacheEntry(false);
+
+ if (mApplicationCacheForWrite) {
+ // Store response in the offline cache
+ InitOfflineCacheEntry();
+ CloseOfflineCacheEntry();
+ }
+ return NS_OK;
+ }
+
+ LOG(("ContinueProcessResponse3 got failure result [rv=%x]\n", 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;
+ (void)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)
+{
+ 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 (mFallingBack) {
+ // 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).
+ mCachedContentIsPartial = false;
+
+ ClearBogusContentEncodingIfNeeded();
+
+ UpdateInhibitPersistentCachingFlag();
+
+ // 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 (mResuming) {
+ // 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 && !mCacheEntryIsReadOnly) {
+ 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;
+
+ nsXPIDLString messageString;
+ rv = stringBundle->GetStringFromName(u"RepostFormData", getter_Copies(messageString));
+ // GetStringFromName can return NS_OK and nullptr messageString.
+ if (NS_SUCCEEDED(rv) && messageString) {
+ bool repost = false;
+
+ nsCOMPtr<nsIPrompt> prompt;
+ GetCallback(prompt);
+ if (!prompt)
+ return NS_ERROR_NO_INTERFACE;
+
+ prompt->Confirm(nullptr, messageString, &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::HandleAsyncRedirectChannelToHttps()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async redirect to https [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps;
+ return;
+ }
+
+ nsresult rv = StartRedirectChannelToHttps();
+ if (NS_FAILED(rv))
+ ContinueAsyncRedirectChannelToURI(rv);
+}
+
+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()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+ NS_PRECONDITION(mAPIRedirectToURI, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncAPIRedirect;
+ return;
+ }
+
+ nsresult rv = StartRedirectChannelToURI(mAPIRedirectToURI,
+ nsIChannelEventSink::REDIRECT_PERMANENT);
+ if (NS_FAILED(rv))
+ ContinueAsyncRedirectChannelToURI(rv);
+
+ return;
+}
+
+nsresult
+nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI, uint32_t flags)
+{
+ nsresult rv = NS_OK;
+ LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
+
+ nsCOMPtr<nsIChannel> newChannel;
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
+ upgradedURI,
+ mLoadInfo,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ if (!(flags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) &&
+ mInterceptCache == INTERCEPTED) {
+ // Mark the channel as intercepted in order to propagate the response URL.
+ nsCOMPtr<nsIHttpChannelInternal> httpRedirect = do_QueryInterface(mRedirectChannel);
+ if (httpRedirect) {
+ httpRedirect->ForceIntercepted(mInterceptionID);
+ }
+ }
+
+ 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)
+{
+ // 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)) {
+ // Fill the failure status here, the update to https had been vetoed
+ // but from the security reasons we have to discard the whole channel
+ // load.
+ mStatus = rv;
+ }
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+
+ if (NS_FAILED(rv)) {
+ // 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);
+
+ // And now, notify observers the deprecated way
+ nsCOMPtr<nsIHttpEventSink> httpEventSink;
+ GetCallback(httpEventSink);
+ if (httpEventSink) {
+ // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
+ // versions.
+ rv = httpEventSink->OnRedirect(this, mRedirectChannel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // open new channel
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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->NewProxiedChannel2(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;
+
+ NS_PRECONDITION(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
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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, getter_AddRefs(mProxyRequest));
+ } else {
+ rv = pps->AsyncResolve(static_cast<nsIChannel*>(this), mProxyResolveFlags,
+ this, getter_AddRefs(mProxyRequest));
+ }
+
+ return rv;
+}
+
+bool
+nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry)
+{
+ nsresult rv;
+ nsAutoCString buf, metaKey;
+ mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
+ if (!buf.IsEmpty()) {
+ NS_NAMED_LITERAL_CSTRING(prefix, "request-");
+
+ // enumerate the elements of the Vary header...
+ char *val = buf.BeginWriting(); // going to munge buf
+ char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
+ while (token) {
+ LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] " \
+ "processing %s\n",
+ this, token));
+ //
+ // 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 == '*')
+ return true; // if we encounter this, just get out of here
+
+ // build cache meta data key...
+ metaKey = prefix + nsDependentCString(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.
+ nsXPIDLCString 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;
+ }
+
+ // next token...
+ token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
+ }
+ }
+ 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();
+}
+
+
+nsresult
+nsHttpChannel::EnsureAssocReq()
+{
+ // Confirm Assoc-Req response header on pipelined transactions
+ // per draft-nottingham-http-pipeline-01.txt
+ // of the form: GET http://blah.com/foo/bar?qv
+ // return NS_OK as long as we don't find a violation
+ // (i.e. no header is ok, as are malformed headers, as are
+ // transactions that have not been pipelined (unless those have been
+ // opted in via pragma))
+
+ if (!mResponseHead)
+ return NS_OK;
+
+ nsAutoCString assoc_val;
+ if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_val))) {
+ return NS_OK;
+ }
+
+ if (!mTransaction || !mURI)
+ return NS_OK;
+
+ if (!mTransaction->PipelinePosition()) {
+ // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined
+ // transactions. It is used by test harness.
+
+ nsAutoCString pragma_val;
+ mResponseHead->GetHeader(nsHttp::Pragma, pragma_val);
+ if (pragma_val.IsEmpty() ||
+ !nsHttp::FindToken(pragma_val.get(), "X-Verify-Assoc-Req",
+ HTTP_HEADER_VALUE_SEPS))
+ return NS_OK;
+ }
+
+ char *method = net_FindCharNotInSet(assoc_val.get(), HTTP_LWS);
+ if (!method)
+ return NS_OK;
+
+ bool equals;
+ char *endofmethod;
+
+ char * assoc_valChar = nullptr;
+ endofmethod = net_FindCharInSet(method, HTTP_LWS);
+ if (endofmethod)
+ assoc_valChar = net_FindCharNotInSet(endofmethod, HTTP_LWS);
+ if (!assoc_valChar)
+ return NS_OK;
+
+ // check the method
+ nsAutoCString methodHead;
+ mRequestHead.Method(methodHead);
+ if ((((int32_t)methodHead.Length()) != (endofmethod - method)) ||
+ PL_strncmp(method,
+ methodHead.get(),
+ endofmethod - method)) {
+ LOG((" Assoc-Req failure Method %s", method));
+ if (mConnectionInfo)
+ gHttpHandler->ConnMgr()->
+ PipelineFeedbackInfo(mConnectionInfo,
+ nsHttpConnectionMgr::RedCorruptedContent,
+ nullptr, 0);
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ nsAutoString message
+ (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
+ nsAutoCString assoc_req;
+ mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_req);
+ AppendASCIItoUTF16(assoc_req, message);
+ message += NS_LITERAL_STRING(" expected method ");
+ AppendASCIItoUTF16(methodHead, message);
+ consoleService->LogStringMessage(message.get());
+ }
+
+ if (gHttpHandler->EnforceAssocReq())
+ return NS_ERROR_CORRUPTED_CONTENT;
+ return NS_OK;
+ }
+
+ // check the URL
+ nsCOMPtr<nsIURI> assoc_url;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_valChar)) ||
+ !assoc_url)
+ return NS_OK;
+
+ mURI->Equals(assoc_url, &equals);
+ if (!equals) {
+ LOG((" Assoc-Req failure URL %s", assoc_valChar));
+ if (mConnectionInfo)
+ gHttpHandler->ConnMgr()->
+ PipelineFeedbackInfo(mConnectionInfo,
+ nsHttpConnectionMgr::RedCorruptedContent,
+ nullptr, 0);
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ nsAutoString message
+ (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
+ nsAutoCString assoc_req;
+ mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_req);
+ AppendASCIItoUTF16(assoc_req, message);
+ message += NS_LITERAL_STRING(" expected URL ");
+ AppendASCIItoUTF16(mSpec.get(), message);
+ consoleService->LogStringMessage(message.get());
+ }
+
+ if (gHttpHandler->EnforceAssocReq())
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <byte-range>
+//-----------------------------------------------------------------------------
+
+bool
+nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen) const
+{
+ bool hasContentEncoding =
+ mCachedResponseHead->HasHeader(nsHttp::Content_Encoding);
+
+ nsAutoCString etag;
+ mCachedResponseHead->GetHeader(nsHttp::ETag, etag);
+ bool hasWeakEtag = !etag.IsEmpty() &&
+ StringBeginsWith(etag, NS_LITERAL_CSTRING("W/"));
+
+ return (partialLen < contentLength) &&
+ (partialLen > 0 || ignoreMissingPartialLen) &&
+ !hasContentEncoding && !hasWeakEtag &&
+ mCachedResponseHead->IsResumable() &&
+ !mCustomConditionalRequest &&
+ !mCachedResponseHead->NoStore();
+}
+
+nsresult
+nsHttpChannel::MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen)
+{
+ // Be pesimistic
+ mIsPartialRequest = 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;
+ mCachedResponseHead->GetHeader(nsHttp::ETag, val);
+ if (val.IsEmpty())
+ 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.
+ NS_NOTREACHED("no cache validator");
+ mIsPartialRequest = false;
+ return NS_ERROR_FAILURE;
+ }
+
+ char buf[64];
+ SprintfLiteral(buf, "bytes=%" PRId64 "-", partialLen);
+
+ mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
+ mRequestHead.SetHeader(nsHttp::If_Range, val);
+ mIsPartialRequest = true;
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::UntieByteRangeRequest()
+{
+ mRequestHead.ClearHeader(nsHttp::Range);
+ mRequestHead.ClearHeader(nsHttp::If_Range);
+}
+
+nsresult
+nsHttpChannel::ProcessPartialContent()
+{
+ // 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;
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ 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;
+ mResponseHead->GetHeader(nsHttp::Content_Range, contentRange);
+ LOG(("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
+ "original content-length %lld, entity-size %lld, 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 (mConcurrentCacheAccess) {
+ // 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
+ rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
+ if (NS_FAILED(rv)) return rv;
+
+ // 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 = 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 (mConcurrentCacheAccess) {
+ mCachedContentIsPartial = false;
+ // Leave the mConcurrentCacheAccess 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.
+
+ // Now we continue reading the network response.
+ } else {
+ // the cached content is valid, although incomplete.
+ mCachedContentIsValid = true;
+ rv = ReadFromCache(false);
+ }
+
+ return 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.
+ mCachedContentIsPartial = 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
+ NS_NOTREACHED("no transaction");
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <cache>
+//-----------------------------------------------------------------------------
+
+bool
+nsHttpChannel::ShouldBypassProcessNotModified()
+{
+ if (mCustomConditionalRequest) {
+ 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()
+{
+ 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 as well as disabling pipelining for that host.
+
+ 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);
+ if (mConnectionInfo)
+ gHttpHandler->ConnMgr()->
+ PipelineFeedbackInfo(mConnectionInfo,
+ nsHttpConnectionMgr::RedCorruptedContent,
+ nullptr, 0);
+ Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
+ }
+
+ // merge any new headers with the cached response headers
+ rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
+ if (NS_FAILED(rv)) return rv;
+
+ // 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 = 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;
+
+ rv = ReadFromCache(false);
+ if (NS_FAILED(rv)) return rv;
+
+ mTransactionReplaced = true;
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback)
+{
+ LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
+ nsresult rv;
+
+ *waitingForRedirectCallback = false;
+ mFallingBack = 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() || mFallbackChannel) {
+ LOG((" choosing not to fallback [%p,%s,%d]",
+ mApplicationCache.get(), mFallbackKey.get(), mFallbackChannel));
+ 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->NewChannel2(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;
+
+ NS_PRECONDITION(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);
+
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ MaybeWarnAboutAppCache();
+ }
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ mFallingBack = 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)
+{
+ // Handle correctly mCacheEntriesToWaitFor
+ AutoCacheWaitFlags waitFlags(this);
+
+ // Drop this flag here
+ mConcurrentCacheAccess = 0;
+
+ nsresult rv;
+
+ mLoadedFromApplicationCache = false;
+ mHasQueryString = HasQueryString(mRequestHead.ParsedMethod(), mURI);
+
+ LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
+
+ // make sure we're not abusing this function
+ NS_PRECONDITION(!mCacheEntry, "cache entry already open");
+
+ nsAutoCString cacheKey;
+ nsAutoCString extension;
+
+ 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 (!PossiblyIntercepted() && !mRequestHead.IsGet() && !mRequestHead.IsHead()) {
+ // don't use the cache for other types of requests
+ return NS_OK;
+ }
+
+ if (mResuming) {
+ // 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;
+
+ // Pick up an application cache from the notification
+ // callbacks if available and if we are not an intercepted channel.
+ if (!PossiblyIntercepted() && !mApplicationCache &&
+ mInheritApplicationCache) {
+ nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
+ GetCallback(appCacheContainer);
+
+ if (appCacheContainer) {
+ appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
+ }
+ }
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ nsCOMPtr<nsIURI> openURI;
+ if (!mFallbackKey.IsEmpty() && mFallbackChannel) {
+ // This is a fallback channel, open fallback URI instead
+ rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // In the case of intercepted channels, we need to construct the cache
+ // entry key based on the original URI, so that in case the intercepted
+ // channel is redirected, the cache entry key before and after the
+ // redirect is the same.
+ if (PossiblyIntercepted()) {
+ openURI = mOriginalURI;
+ } else {
+ openURI = mURI;
+ }
+ }
+
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t cacheEntryOpenFlags;
+ bool offline = gIOService->IsOffline();
+
+ nsAutoCString cacheControlRequestHeader;
+ mRequestHead.GetHeader(nsHttp::Cache_Control, cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+ if (cacheControlRequest.NoStore() && !PossiblyIntercepted()) {
+ goto bypassCacheEntryOpen;
+ }
+
+ if (offline || (mLoadFlags & INHIBIT_CACHING)) {
+ if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline && !PossiblyIntercepted()) {
+ goto bypassCacheEntryOpen;
+ }
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
+ mCacheEntryIsReadOnly = true;
+ }
+ else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !mApplicationCache) {
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
+ }
+ else {
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_NORMALLY
+ | nsICacheStorage::CHECK_MULTITHREADED;
+ }
+
+ if (!mPostID && mApplicationCache) {
+ rv = cacheStorageService->AppCacheStorage(info,
+ mApplicationCache,
+ getter_AddRefs(cacheStorage));
+ } else if (PossiblyIntercepted()) {
+ // The synthesized cache has less restrictions on file size and so on.
+ rv = cacheStorageService->SynthesizedCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ } else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
+ rv = cacheStorageService->MemoryCacheStorage(info, // ? choose app cache as well...
+ getter_AddRefs(cacheStorage));
+ }
+ else if (mPinCacheContent) {
+ rv = cacheStorageService->PinningCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ }
+ else {
+ rv = cacheStorageService->DiskCacheStorage(info,
+ !mPostID && (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)),
+ 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 (PossiblyIntercepted()) {
+ extension.Append(nsPrintfCString("u%lld", mInterceptionID));
+ } else if (mPostID) {
+ extension.Append(nsPrintfCString("%d", mPostID));
+ }
+
+ // If this channel should be intercepted, we do not open a cache entry for this channel
+ // until the interception process is complete and the consumer decides what to do with it.
+ if (mInterceptCache == MAYBE_INTERCEPT) {
+ DebugOnly<bool> exists;
+ MOZ_ASSERT(NS_FAILED(cacheStorage->Exists(openURI, extension, &exists)) || !exists,
+ "The entry must not exist in the cache before we create it here");
+
+ nsCOMPtr<nsICacheEntry> entry;
+ rv = cacheStorage->OpenTruncate(openURI, extension, getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ RefPtr<InterceptedChannelChrome> intercepted =
+ new InterceptedChannelChrome(this, controller, entry);
+ intercepted->NotifyController();
+ } else {
+ if (mInterceptCache == INTERCEPTED) {
+ cacheEntryOpenFlags |= nsICacheStorage::OPEN_INTERCEPTED;
+ // Clear OPEN_TRUNCATE for the fake cache entry, since otherwise
+ // cache storage will close the current entry which breaks the
+ // response synthesis.
+ cacheEntryOpenFlags &= ~nsICacheStorage::OPEN_TRUNCATE;
+ DebugOnly<bool> exists;
+ MOZ_ASSERT(NS_SUCCEEDED(cacheStorage->Exists(openURI, extension, &exists)) && exists,
+ "The entry must exist in the cache after we create it here");
+ }
+
+ mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
+ mCacheQueueSizeWhenOpen = CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
+
+ rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
+
+bypassCacheEntryOpen:
+ if (!mApplicationCacheForWrite)
+ 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
+ NS_PRECONDITION(!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, EmptyCString(), 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)
+{
+ nsresult rv;
+
+ rv = aEntry->GetDataSize(aSize);
+
+ if (NS_ERROR_IN_PROGRESS == rv) {
+ *aSize = -1;
+ rv = NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsHttpResponseHead* responseHead = mCachedResponseHead
+ ? mCachedResponseHead
+ : mResponseHead;
+
+ if (!responseHead)
+ return NS_ERROR_UNEXPECTED;
+
+ *aContentLength = responseHead->ContentLength();
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::UntieValidationRequest()
+{
+ // Make the request unconditional again.
+ mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
+ mRequestHead.ClearHeader(nsHttp::If_None_Match);
+ mRequestHead.ClearHeader(nsHttp::ETag);
+}
+
+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));
+
+ nsAutoCString cacheControlRequestHeader;
+ 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;
+ }
+
+ // Remember the request is a custom conditional request so that we can
+ // process any 304 response correctly.
+ mCustomConditionalRequest =
+ 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);
+
+ // Be pessimistic: assume the cache entry has no useful data.
+ *aResult = ENTRY_WANTED;
+ mCachedContentIsValid = false;
+
+ nsXPIDLCString 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()) {
+ return NS_OK;
+ }
+ }
+ buf.Adopt(0);
+
+ // 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 = new nsHttpResponseHead();
+
+ // 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.
+ rv = entry->GetMetaDataElement("original-response-headers", getter_Copies(buf));
+ if (NS_SUCCEEDED(rv)) {
+ mCachedResponseHead->ParseCachedOriginalHeaders((char *) buf.get());
+ }
+
+ buf.Adopt(0);
+ // 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 = mCachedResponseHead->ParseCachedHead(buf.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ buf.Adopt(0);
+
+ 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() && mCacheEntryIsReadOnly) {
+ // 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 ||
+ (mCacheEntryIsReadOnly && !(mLoadFlags & nsIRequest::INHIBIT_CACHING)) ||
+ mFallbackChannel)) {
+ 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)) {
+ LOG((" wait for entry completion, "
+ "response is not resumable"));
+
+ wantCompleteEntry = true;
+ }
+ else {
+ mConcurrentCacheAccess = 1;
+ }
+ }
+ else if (contentLength != int64_t(-1) && contentLength != size) {
+ LOG(("Cached data size does not match the Content-Length header "
+ "[content-length=%lld size=%lld]\n", contentLength, size));
+
+ rv = MaybeSetupByteRangeRequest(size, contentLength);
+ mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest;
+ if (mCachedContentIsPartial) {
+ rv = OpenCacheInputStream(entry, false, !!appCache);
+ if (NS_FAILED(rv)) {
+ UntieByteRangeRequest();
+ return rv;
+ }
+
+ *aResult = ENTRY_NEEDS_REVALIDATION;
+ return NS_OK;
+ }
+
+ if (size == 0 && mCacheOnlyMetadata) {
+ // Don't break cache entry load when the entry's data size
+ // is 0 and mCacheOnlyMetadata 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 if (mInterceptCache != INTERCEPTED) {
+ return rv;
+ }
+ }
+ }
+
+ bool isHttps = false;
+ rv = mURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool doValidation = false;
+ bool canAddImsHeader = true;
+
+ bool isForcedValid = false;
+ entry->GetIsForcedValid(&isForcedValid);
+
+ nsXPIDLCString framedBuf;
+ 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
+ bool weaklyFramed = NS_SUCCEEDED(rv) && framedBuf.EqualsLiteral("0");
+ bool isImmutable = !weaklyFramed && isHttps && mCachedResponseHead->Immutable();
+
+ // 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;
+ }
+ // 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
+ else if (isForcedValid &&
+ (!mCachedResponseHead->ExpiresInPast() ||
+ !mCachedResponseHead->MustValidateIfExpired())) {
+ LOG(("NOT validating based on isForcedValid being true.\n"));
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES_USED> used;
+ ++used;
+ doValidation = false;
+ }
+ // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
+ else if (mLoadFlags & nsIRequest::LOAD_FROM_CACHE || mAllowStaleCacheContent) {
+ LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
+ doValidation = false;
+ }
+ // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
+ // it's revalidated with the server.
+ else if ((mLoadFlags & nsIRequest::VALIDATE_ALWAYS) && !isImmutable) {
+ LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
+ doValidation = 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.
+ else if (mLoadFlags & nsIRequest::VALIDATE_NEVER) {
+ LOG(("VALIDATE_NEVER set\n"));
+ // if no-store validate cached response (see bug 112564)
+ if (mCachedResponseHead->NoStore()) {
+ LOG(("Validating based on no-store logic\n"));
+ doValidation = true;
+ }
+ else {
+ LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
+ doValidation = false;
+ }
+ }
+ // check if validation is strictly required...
+ else if (mCachedResponseHead->MustValidate()) {
+ LOG(("Validating based on MustValidate() returning TRUE\n"));
+ doValidation = true;
+ } else {
+ // 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...
+
+ uint32_t now = NowInSeconds();
+
+ uint32_t age = 0;
+ rv = mCachedResponseHead->ComputeCurrentAge(now, now, &age);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t freshness = 0;
+ rv = mCachedResponseHead->ComputeFreshnessLifetime(&freshness);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t expiration = 0;
+ rv = entry->GetExpirationTime(&expiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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)) {
+ 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 (mCachedResponseHead->MustValidateIfExpired()) {
+ doValidation = true;
+ } else if (mLoadFlags & 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"));
+ }
+
+
+ // If a content signature is expected to be valid in this load,
+ // set doValidation to force a signature check.
+ if (!doValidation &&
+ mLoadInfo && mLoadInfo->GetVerifySignedContent()) {
+ doValidation = true;
+ }
+
+ nsAutoCString requestedETag;
+ if (!doValidation &&
+ NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) &&
+ (methodWasGet || methodWasHead)) {
+ nsAutoCString cachedETag;
+ mCachedResponseHead->GetHeader(nsHttp::ETag, cachedETag);
+ if (!cachedETag.IsEmpty() &&
+ (StringBeginsWith(cachedETag, NS_LITERAL_CSTRING("W/")) ||
+ !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;
+ }
+ }
+
+ 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;
+ GenerateCacheKey(mPostID, cacheKey);
+
+ if (!mRedirectedCachekeys)
+ mRedirectedCachekeys = new 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);
+ }
+
+ if (doValidation && mInterceptCache == INTERCEPTED) {
+ doValidation = false;
+ }
+
+ 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()) &&
+ !mCustomConditionalRequest && !weaklyFramed && !isImmutable &&
+ (mCachedResponseHead->Status() < 400)) {
+
+ if (mConcurrentCacheAccess) {
+ // 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.
+ mConcurrentCacheAccess = 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) {
+ mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
+ if (!val.IsEmpty())
+ mRequestHead.SetHeader(nsHttp::If_Modified_Since, val);
+ }
+ // Add If-None-Match header if an ETag was given in the response
+ mCachedResponseHead->GetHeader(nsHttp::ETag, val);
+ if (!val.IsEmpty())
+ mRequestHead.SetHeader(nsHttp::If_None_Match, val);
+ 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 (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=%x mAppCache=%p mAppCacheForWrite=%p]\n",
+ this, entry, aNew, aAppCache, status,
+ mApplicationCache.get(), mApplicationCacheForWrite.get()));
+
+ // if the channel's already fired onStopRequest, then we should ignore
+ // this event.
+ if (!mIsPending) {
+ mCacheInputStream.CloseAndRelease();
+ return NS_OK;
+ }
+
+ rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status);
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ 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=%x]\n", this, mStatus));
+ return mStatus;
+ }
+
+ 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 (!mFallbackChannel && !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;
+ }
+
+ return TryHSTSPriming();
+}
+
+nsresult
+nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
+ bool aNew,
+ nsresult aEntryStatus)
+{
+ mCacheEntriesToWaitFor &= ~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 (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;
+ mCacheEntryIsWriteOnly = aNew;
+
+ 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);
+
+ mCacheEntriesToWaitFor &= ~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.
+ mLoadedFromApplicationCache = true;
+ mCacheEntryIsReadOnly = true;
+ mCacheEntry = aEntry;
+ mCacheEntryIsWriteOnly = false;
+
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
+ MaybeWarnAboutAppCache();
+ }
+
+ return NS_OK;
+ }
+
+ if (!mApplicationCacheForWrite && !mFallbackChannel) {
+ 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);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(nsICacheEntry *aEntry,
+ nsIApplicationCache* aAppCache,
+ nsresult aEntryStatus)
+{
+ MOZ_ASSERT(mApplicationCacheForWrite && aAppCache == mApplicationCacheForWrite);
+
+ mCacheEntriesToWaitFor &= ~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(mFallbackChannel ? 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()) {
+ uint32_t freshnessLifetime = 0;
+
+ rv = aResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
+ if (NS_FAILED(rv)) return rv;
+
+ if (freshnessLifetime > 0) {
+ uint32_t now = NowInSeconds(), 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;
+ }
+ else
+ aExpirationTime = now;
+ }
+ }
+
+ 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, expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mOfflineCacheEntry) {
+ rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+/*static*/ inline bool
+nsHttpChannel::HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri)
+{
+ // Must be called on the main thread because nsIURI does not implement
+ // thread-safe QueryInterface.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (method != nsHttpRequestHead::kMethod_Get &&
+ method != nsHttpRequestHead::kMethod_Head)
+ return false;
+
+ nsAutoCString query;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+ nsresult rv = url->GetQuery(query);
+ return NS_SUCCEEDED(rv) && !query.IsEmpty();
+}
+
+bool
+nsHttpChannel::ShouldUpdateOfflineCacheEntry()
+{
+ if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
+ return false;
+ }
+
+ // if we're updating the cache entry, update the offline cache entry too
+ if (mCacheEntry && mCacheEntryIsWriteOnly) {
+ 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;
+
+ bool isHttps = false;
+ rv = mURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isHttps) {
+ 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 = !mLoadedFromApplicationCache && !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) &&
+ !mCachedContentIsPartial) {
+ // 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 (!mPreferredCachedAltDataType.IsEmpty()) {
+ rv = cacheEntry->OpenAlternativeInputStream(mPreferredCachedAltDataType,
+ getter_AddRefs(stream));
+ if (NS_SUCCEEDED(rv)) {
+ // We have succeeded.
+ mAvailableCachedAltDataType = mPreferredCachedAltDataType;
+ // Clear the header.
+ mCachedResponseHead->SetContentLength(-1);
+ // Set the correct data size on the channel.
+ int64_t altDataSize;
+ if (NS_SUCCEEDED(cacheEntry->GetAltDataSize(&altDataSize))) {
+ mCachedResponseHead->SetContentLength(altDataSize);
+ }
+ }
+ }
+
+ 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 =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1),
+ 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);
+
+ LOG(("nsHttpChannel::ReadFromCache [this=%p] "
+ "Using cached copy of: %s\n", this, mSpec.get()));
+
+ if (mCachedResponseHead)
+ mResponseHead = 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 && !mCachedContentIsPartial) {
+ // 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) && !mCachedContentIsPartial) {
+ 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,
+ int64_t(-1), int64_t(-1), 0, 0, true);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ rv = mCachePump->AsyncRead(this, mListenerContext);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mTimingEnabled)
+ mCacheReadStart = TimeStamp::Now();
+
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--)
+ mCachePump->Suspend();
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::CloseCacheEntry(bool doomOnFailure)
+{
+ mCacheInputStream.CloseAndRelease();
+
+ if (!mCacheEntry)
+ return;
+
+ LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%x mCacheEntryIsWriteOnly=%x",
+ this, mStatus, mCacheEntryIsWriteOnly));
+
+ // 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 (mInitedCacheEntry) {
+ MOZ_ASSERT(mResponseHead, "oops");
+ if (NS_FAILED(mStatus) && doomOnFailure &&
+ mCacheEntryIsWriteOnly && !mResponseHead->IsResumable())
+ doom = true;
+ }
+ else if (mCacheEntryIsWriteOnly)
+ 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;
+ mCacheEntry = nullptr;
+ mCacheEntryIsWriteOnly = false;
+ mInitedCacheEntry = 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;
+}
+
+
+// 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 (mCacheEntryIsReadOnly)
+ 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 = !mCacheEntryIsWriteOnly;
+ 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"));
+ 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;
+ }
+
+ mCacheEntryIsWriteOnly = 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;
+
+ mInitedCacheEntry = true;
+
+ // Don't perform the check when writing (doesn't make sense)
+ mConcurrentCacheAccess = 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
+ bool isHttps;
+ if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
+ NS_SUCCEEDED(mURI->SchemeIs("https", &isHttps)) && isHttps) {
+ 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;
+ responseHead->GetHeader(nsHttp::Vary, buf);
+ if (!buf.IsEmpty()) {
+ NS_NAMED_LITERAL_CSTRING(prefix, "request-");
+
+ char *bufData = buf.BeginWriting(); // going to munge buf
+ char *token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
+ while (token) {
+ LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
+ "processing %s", self, token));
+ if (*token != '*') {
+ 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 = NS_LITERAL_CSTRING("<hash failed>");
+ } else {
+ val = hash;
+ }
+
+ LOG((" hashed to %s\n", val.get()));
+ }
+
+ // build cache meta data key and set meta data element...
+ metaKey = prefix + nsDependentCString(token);
+ entry->SetMetaDataElement(metaKey.get(), val.get());
+ } else {
+ LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
+ "clearing metadata for %s", self, token));
+ metaKey = prefix + nsDependentCString(token);
+ entry->SetMetaDataElement(metaKey.get(), nullptr);
+ }
+ }
+ token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
+ }
+ }
+ }
+
+ // 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, 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 (mStronglyFramed && !mCachedContentIsValid && mCacheEntry) {
+ LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p] Is Strongly Framed\n", this));
+ mCacheEntry->SetMetaDataElement("strongly-framed", "1");
+ }
+
+ if (mResponseHead && mResponseHeadersModified) {
+ // 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(mCacheEntryIsWriteOnly || mCachedContentIsPartial);
+ MOZ_ASSERT(mListener);
+
+ nsAutoCString contentEncoding, contentType;
+ 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();
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = mCacheEntry->OpenOutputStream(offset, 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 (NS_FAILED(rv)) return rv;
+
+ if (mCacheOnlyMetadata) {
+ 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;
+
+ nsCOMPtr<nsIEventTarget> cacheIOTarget;
+ if (!CacheObserver::UseNewCache()) {
+ nsCOMPtr<nsICacheStorageService> serv =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ serv->GetIoTarget(getter_AddRefs(cacheIOTarget));
+ }
+
+ if (!cacheIOTarget) {
+ LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%x "
+ "cacheIOTarget=%p", tee.get(), rv, cacheIOTarget.get()));
+ rv = tee->Init(mListener, out, nullptr);
+ } else {
+ LOG(("nsHttpChannel::InstallCacheListener async tee %p", tee.get()));
+ rv = tee->InitAsync(mListener, cacheIOTarget, 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, 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;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel)
+ return NS_OK; // no other options to set
+
+ // convey the mApplyConversion flag (bug 91862)
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
+ if (encodedChannel)
+ encodedChannel->SetApplyConversion(mApplyConversion);
+
+ // transfer the resume information
+ if (mResuming) {
+ 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);
+ }
+
+ if (!(redirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) &&
+ mInterceptCache != INTERCEPTED) {
+ // Ensure that internally-redirected channels, or loads with manual
+ // redirect mode cannot be intercepted, which would look like two
+ // separate requests to the nsINetworkInterceptController.
+ if (mRedirectMode != nsIHttpChannelInternal::REDIRECT_MODE_MANUAL ||
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
+ nsIChannelEventSink::REDIRECT_PERMANENT)) == 0) {
+ 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);
+ }
+ }
+
+ 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;
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII, locationBuf))
+ location = locationBuf;
+
+ if (mRedirectionLimit == 0) {
+ LOG(("redirection limit reached!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ 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;
+ (void)ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback)
+ return NS_OK;
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
+ }
+ }
+
+ return ContinueProcessRedirectionAfterFallback(NS_OK);
+}
+
+nsresult
+nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv)
+{
+ if (NS_SUCCEEDED(rv) && mFallingBack) {
+ // 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 && mCacheEntryIsWriteOnly &&
+ NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
+ redirectingBackToSameURI)
+ mCacheEntry->AsyncDoom(nullptr);
+
+ bool hasRef = false;
+ rv = mRedirectURI->GetHasRef(&hasRef);
+
+ // move the reference of the old location to the new one if the new
+ // one has none.
+ if (NS_SUCCEEDED(rv) && !hasRef) {
+ nsAutoCString ref;
+ mURI->GetRef(ref);
+ if (!ref.IsEmpty()) {
+ // NOTE: SetRef will fail if mRedirectURI is immutable
+ // (e.g. an about: URI)... Oh well.
+ mRedirectURI->SetRef(ref);
+ }
+ }
+
+ 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;
+ }
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
+ mRedirectURI,
+ mLoadInfo,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t redirectFlags;
+ if (nsHttp::IsPermanentRedirect(mRedirectType))
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ else
+ redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+
+ 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=%x,this=%p]\n", rv,
+ this));
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_PRECONDITION(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);
+
+ // And now, the deprecated way
+ nsCOMPtr<nsIHttpEventSink> httpEventSink;
+ GetCallback(httpEventSink);
+ if (httpEventSink) {
+ // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
+ // versions.
+ rv = httpEventSink->OnRedirect(this, mRedirectChannel);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ // 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
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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
+ mAuthRetryPending = true;
+ mProxyAuthPending = false;
+ LOG(("Resuming the transaction, we got credentials from user"));
+ 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 (mProxyAuthPending)
+ 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);
+ }
+
+ mProxyAuthPending = 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 (!mIsPending) {
+ 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->Caps() & NS_HTTP_STICKY_CONNECTION)) {
+ LOG((" not sticky"));
+ return NS_OK;
+ }
+
+ RefPtr<nsAHttpConnection> conn = mTransaction->GetConnectionReference();
+ if (!conn) {
+ LOG((" no connection"));
+ return NS_OK;
+ }
+
+ // This turns the IsPersistent() indicator on the connection to false,
+ // and makes us throw it away in OnStopRequest.
+ conn->DontReuse();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::ConnectionRestartable(bool aRestartable)
+{
+ LOG(("nsHttpChannel::ConnectionRestartable this=%p, restartable=%d",
+ this, aRestartable));
+ mAuthConnectionRestartable = 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(nsIHstsPrimingCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelWithDivertableParentListener)
+ // we have no macro that covers this case.
+ if (aIID.Equals(NS_GET_IID(nsHttpChannel)) ) {
+ AddRef();
+ *aInstancePtr = this;
+ return NS_OK;
+ } else
+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);
+
+ LOG(("nsHttpChannel::Cancel [this=%p status=%x]\n", this, status));
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return NS_OK;
+ }
+ if (mWaitingForRedirectCallback) {
+ LOG(("channel canceled during wait for redirect callback"));
+ }
+ mCanceled = true;
+ mStatus = status;
+ if (mProxyRequest)
+ mProxyRequest->Cancel(status);
+ if (mTransaction)
+ gHttpHandler->CancelTransaction(mTransaction, status);
+ if (mTransactionPump)
+ mTransactionPump->Cancel(status);
+ mCacheInputStream.CloseAndRelease();
+ if (mCachePump)
+ mCachePump->Cancel(status);
+ if (mAuthProvider)
+ mAuthProvider->Cancel(status);
+ if (mPreflightChannel)
+ mPreflightChannel->Cancel(status);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Suspend()
+{
+ nsresult rv = SuspendInternal();
+
+ nsresult rvParentChannel = NS_OK;
+ if (mParentChannel) {
+ rvParentChannel = mParentChannel->SuspendMessageDiversion();
+ }
+
+ return NS_FAILED(rv) ? rv : rvParentChannel;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Resume()
+{
+ nsresult rv = ResumeInternal();
+
+ nsresult rvParentChannel = NS_OK;
+ if (mParentChannel) {
+ rvParentChannel = mParentChannel->ResumeMessageDiversion();
+ }
+
+ return NS_FAILED(rv) ? rv : rvParentChannel;
+}
+
+//-----------------------------------------------------------------------------
+// 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 *listener, nsISupports *context)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
+
+ NS_CompareLoadInfoAndLoadContext(this);
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ nsresult rv;
+
+ 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 (mInterceptCache != INTERCEPTED && ShouldIntercept()) {
+ mInterceptCache = MAYBE_INTERCEPT;
+ SetCouldBeSynthesized();
+ }
+
+ // Remember the cookie header that was set, if any
+ nsAutoCString cookieHeader;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) {
+ mUserSetCookieHeader = cookieHeader;
+ }
+
+ AddCookiesToRequest();
+
+ // 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);
+ }
+
+ // Set user agent override
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ mIsPending = true;
+ mWasOpened = true;
+
+ mListener = listener;
+ mListenerContext = context;
+
+ 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.
+ mAsyncOpenTime = TimeStamp::Now();
+
+ // Remember we have Authorization header set here. We need to check on it
+ // just once and early, AsyncOpen is the best place.
+ mCustomAuthHeader = mRequestHead.HasHeader(nsHttp::Authorization);
+
+ // 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.
+ if (!mProxyInfo && NS_SUCCEEDED(ResolveProxy())) {
+ return NS_OK;
+ }
+
+ rv = BeginConnect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReleaseListeners();
+ return rv;
+ }
+ return AsyncOpen(listener, nullptr);
+}
+
+// BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by
+// functions that called BeginConnect if needed. Only AsyncOpen 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 = false;
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_SUCCEEDED(rv))
+ rv = mURI->SchemeIs("https", &isHttps);
+ if (NS_SUCCEEDED(rv))
+ rv = mURI->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv))
+ rv = mURI->GetPort(&port);
+ if (NS_SUCCEEDED(rv))
+ mURI->GetUsername(mUsername);
+ if (NS_SUCCEEDED(rv))
+ rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // 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);
+
+ SetDoNotTrack();
+
+ NeckoOriginAttributes originAttributes;
+ NS_GetOriginAttributes(this, originAttributes);
+
+ RefPtr<AltSvcMapping> mapping;
+ if (!mConnectionInfo && mAllowAltSvc && // per channel
+ !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
+ (scheme.Equals(NS_LITERAL_CSTRING("http")) ||
+ scheme.Equals(NS_LITERAL_CSTRING("https"))) &&
+ (!proxyInfo || proxyInfo->IsDirect()) &&
+ (mapping = gHttpHandler->GetAltServiceMapping(scheme,
+ host, port,
+ mPrivateBrowsing))) {
+ 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());
+ }
+ mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine);
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ nsAutoString message(NS_LITERAL_STRING("Alternate Service Mapping found: "));
+ AppendASCIItoUTF16(scheme.get(), message);
+ message.Append(NS_LITERAL_STRING("://"));
+ AppendASCIItoUTF16(host.get(), message);
+ message.Append(NS_LITERAL_STRING(":"));
+ message.AppendInt(port);
+ message.Append(NS_LITERAL_STRING(" to "));
+ AppendASCIItoUTF16(scheme.get(), message);
+ message.Append(NS_LITERAL_STRING("://"));
+ AppendASCIItoUTF16(mapping->AlternateHost().get(), message);
+ message.Append(NS_LITERAL_STRING(":"));
+ message.AppendInt(mapping->AlternatePort());
+ consoleService->LogStringMessage(message.get());
+ }
+
+ LOG(("nsHttpChannel %p Using connection info from altsvc mapping", this));
+ mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo, originAttributes);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
+ } else if (mConnectionInfo) {
+ LOG(("nsHttpChannel %p Using channel supplied connection info", this));
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ } else {
+ LOG(("nsHttpChannel %p Using default connection info", this));
+
+ mConnectionInfo = new nsHttpConnectionInfo(host, port, EmptyCString(), mUsername, proxyInfo,
+ originAttributes, isHttps);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ }
+
+ // Set network interface id only when it's not empty to avoid
+ // rebuilding hash key.
+ if (!mNetworkInterfaceId.IsEmpty()) {
+ mConnectionInfo->SetNetworkInterfaceId(mNetworkInterfaceId);
+ }
+
+ mAuthProvider =
+ do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1",
+ &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = mAuthProvider->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // check to see if authorization headers should be included
+ // mCustomAuthHeader is set in AsyncOpen if we find Authorization header
+ mAuthProvider->AddAuthorizationHeaders(mCustomAuthHeader);
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ SetLoadGroupUserAgentOverride();
+
+ // Check if request was cancelled during on-modify-request or on-useragent.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume BeginConnect [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ mCallOnResume = &nsHttpChannel::HandleBeginConnectContinue;
+ return NS_OK;
+ }
+
+ return BeginConnectContinue();
+}
+
+void
+nsHttpChannel::HandleBeginConnectContinue()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume BeginConnect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleBeginConnectContinue;
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleBeginConnectContinue [this=%p]\n", this));
+ rv = BeginConnectContinue();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+}
+
+nsresult
+nsHttpChannel::BeginConnectContinue()
+{
+ nsresult rv;
+
+ // Check if request was cancelled during suspend AFTER on-modify-request or
+ // on-useragent.
+ 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);
+ }
+ // Check to see if this principal exists on local blocklists.
+ RefPtr<nsChannelClassifier> channelClassifier = new nsChannelClassifier();
+ if (mLoadFlags & LOAD_CLASSIFY_URI) {
+ nsCOMPtr<nsIURIClassifier> classifier = do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID);
+ bool tpEnabled = false;
+ channelClassifier->ShouldEnableTrackingProtection(this, &tpEnabled);
+ if (classifier && tpEnabled) {
+ // We skip speculative connections by setting mLocalBlocklist only
+ // when tracking protection is enabled. Though we could do this for
+ // both phishing and malware, it is not necessary for correctness,
+ // since no network events will be received while the
+ // nsChannelClassifier is in progress. See bug 1122691.
+ nsCOMPtr<nsIURI> uri;
+ rv = GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv) && uri) {
+ nsAutoCString tables;
+ Preferences::GetCString("urlclassifier.trackingTable", &tables);
+ nsAutoCString results;
+ rv = classifier->ClassifyLocalWithTables(uri, tables, results);
+ if (NS_SUCCEEDED(rv) && !results.IsEmpty()) {
+ LOG(("nsHttpChannel::ClassifyLocalWithTables found "
+ "uri on local tracking blocklist [this=%p]",
+ this));
+ mLocalBlocklist = true;
+ } else {
+ LOG(("nsHttpChannel::ClassifyLocalWithTables no result "
+ "found [this=%p]", this));
+ }
+ }
+ }
+ }
+
+ // If mTimingEnabled flag is not set after OnModifyRequest() then
+ // clear the already recorded AsyncOpen value for consistency.
+ if (!mTimingEnabled)
+ mAsyncOpenTime = TimeStamp();
+
+ // when proxying only use the pipeline bit if ProxyPipelining() allows it.
+ if (!mConnectionInfo->UsingConnect() && mConnectionInfo->UsingHttpProxy()) {
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ if (gHttpHandler->ProxyPipelining())
+ mCaps |= NS_HTTP_ALLOW_PIPELINING;
+ }
+
+ // if this somehow fails we can go on without it
+ gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
+
+ if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags))
+ mCaps |= NS_HTTP_REFRESH_DNS;
+
+ if (!mLocalBlocklist && !mConnectionInfo->UsingHttpProxy() &&
+ !(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) {
+ // 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.
+ LOG(("nsHttpChannel::BeginConnect [this=%p] prefetching%s\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
+ mDNSPrefetch = new nsDNSPrefetch(mURI, this, mTimingEnabled);
+ mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
+ }
+
+ // 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 | NS_HTTP_ALLOW_PIPELINING);
+
+ if (gHttpHandler->CriticalRequestPrioritization()) {
+ if (mClassOfService & nsIClassOfService::Leader) {
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ }
+ if (mClassOfService & nsIClassOfService::Unblocked) {
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+ }
+ }
+
+ // 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->ConnMgr()->ClearAltServiceMappings();
+ gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(mConnectionInfo);
+ }
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ }
+
+ // 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;
+ }
+
+ if (!(mLoadFlags & LOAD_CLASSIFY_URI)) {
+ return ContinueBeginConnectWithResult();
+ }
+
+ // mLocalBlocklist is true only if tracking protection is enabled and the
+ // URI is a tracking domain, it makes no guarantees about phishing or
+ // malware, so if LOAD_CLASSIFY_URI is true we must call
+ // nsChannelClassifier to catch phishing and malware URIs.
+ bool callContinueBeginConnect = true;
+ if (!mLocalBlocklist) {
+ // Here we call ContinueBeginConnectWithResult and not
+ // ContinueBeginConnect so that in the case of an error we do not start
+ // channelClassifier.
+ rv = ContinueBeginConnectWithResult();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ callContinueBeginConnect = false;
+ }
+ // nsChannelClassifier calls ContinueBeginConnect if it has not already
+ // been called, after optionally cancelling the channel once we have a
+ // remote verdict. We call a concrete class instead of an nsI* that might
+ // be overridden.
+ LOG(("nsHttpChannel::Starting nsChannelClassifier %p [this=%p]",
+ channelClassifier.get(), this));
+ channelClassifier->Start(this);
+ if (callContinueBeginConnect) {
+ return ContinueBeginConnectWithResult();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize)
+{
+ if (mCacheEntry && !mCacheEntryIsWriteOnly) {
+ 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));
+ mFallbackChannel = true;
+ mFallbackKey = aFallbackKey;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ForceIntercepted(uint64_t aInterceptionID)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ if (NS_WARN_IF(mLoadFlags & LOAD_BYPASS_SERVICE_WORKER)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MarkIntercepted();
+ mResponseCouldBeSynthesized = true;
+ mInterceptionID = aInterceptionID;
+ return NS_OK;
+}
+
+mozilla::net::nsHttpChannel*
+nsHttpChannel::QueryHttpChannelImpl(void)
+{
+ return this;
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+ mPriority = newValue;
+ if (mTransaction)
+ gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::ContinueBeginConnectWithResult()
+{
+ LOG(("nsHttpChannel::ContinueBeginConnectWithResult [this=%p]", this));
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async connect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::ContinueBeginConnect;
+ 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 = Connect();
+ }
+
+ LOG(("nsHttpChannel::ContinueBeginConnectWithResult result [this=%p rv=%x "
+ "mCanceled=%i]\n", this, rv, mCanceled));
+ return rv;
+}
+
+void
+nsHttpChannel::ContinueBeginConnect()
+{
+ nsresult rv = ContinueBeginConnectWithResult();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ AsyncAbort(rv);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannel::nsIClassOfService
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsHttpChannel::SetClassFlags(uint32_t inFlags)
+{
+ mClassOfService = inFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::AddClassFlags(uint32_t inFlags)
+{
+ mClassOfService |= inFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ClearClassFlags(uint32_t inFlags)
+{
+ mClassOfService &= ~inFlags;
+ 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=%x mStatus=%x]\n",
+ this, pi, status, 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);
+ 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::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)
+{
+ return mRequestHead.SetHeader(nsHttp::Authorization, value);
+}
+
+//-----------------------------------------------------------------------------
+// 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, nsISupports *ctxt)
+{
+ nsresult rv;
+
+ PROFILER_LABEL("nsHttpChannel", "OnStartRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ if (!(mCanceled || NS_FAILED(mStatus))) {
+ // capture the request's status, so our consumers will know ASAP of any
+ // connection failures, etc - bug 93581
+ request->GetStatus(&mStatus);
+ }
+
+ LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%x]\n",
+ this, request, mStatus));
+
+ // Make sure things are what we expect them to be...
+ MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
+ "Unexpected request");
+ MOZ_ASSERT(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
+ "If we have both pumps, the cache content must be partial");
+
+ mAfterOnStartRequestBegun = true;
+ mOnStartRequestTimestamp = TimeStamp::Now();
+
+ if (!mSecurityInfo && !mCachePump && mTransaction) {
+ // grab the security info from the connection object; the transaction
+ // is guaranteed to own a reference to the connection.
+ mSecurityInfo = mTransaction->SecurityInfo();
+ }
+
+ // 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();
+ // 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) {
+ NS_NOTREACHED("mListener is null");
+ return NS_OK;
+ }
+
+ // 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(!mOnStartRequestCalled);
+
+ nsCOMPtr<nsIURI> redirectTo;
+ mAPIRedirectToURI.swap(redirectTo);
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
+ rv = StartRedirectChannelToURI(redirectTo, nsIChannelEventSink::REDIRECT_TEMPORARY);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
+ }
+
+ // Hack: ContinueOnStartRequest1 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 ContinueOnStartRequest1(NS_BINDING_FAILED);
+}
+
+nsresult
+nsHttpChannel::ContinueOnStartRequest1(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::ContinueOnStartRequest2);
+ if (NS_SUCCEEDED(ProxyFailover()))
+ 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 other request errors, try to fall back
+ if (NS_FAILED(mStatus)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+ bool waitingForRedirectCallback;
+ ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback)
+ return NS_OK;
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+ }
+
+ return ContinueOnStartRequest3(NS_OK);
+}
+
+nsresult
+nsHttpChannel::ContinueOnStartRequest3(nsresult result)
+{
+ if (mFallingBack)
+ return NS_OK;
+
+ return CallOnStartRequest();
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
+{
+ PROFILER_LABEL("nsHttpChannel", "OnStopRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%x]\n",
+ this, request, status));
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "OnStopRequest should only be called from the main thread");
+
+ if (NS_FAILED(status)) {
+ ProcessSecurityReport(status);
+ }
+
+ // 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)) {
+ gIOService->RecheckCaptivePortal();
+ }
+
+ if (mTimingEnabled && 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 (mCachedContentIsPartial) {
+ 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(mConcurrentCacheAccess);
+ }
+ else
+ NS_NOTREACHED("unexpected request");
+ }
+ // Do not to leave the transaction in a suspended state in error cases.
+ if (NS_FAILED(status) && mTransaction)
+ gHttpHandler->CancelTransaction(mTransaction, status);
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ if (mTransaction) {
+ // determine if we should call DoAuthRetry
+ bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
+ mStronglyFramed = mTransaction->ResponseIsComplete();
+ LOG(("nsHttpChannel %p has a strongly framed transaction: %d",
+ this, mStronglyFramed));
+
+ //
+ // grab references to connection in case we need to retry an
+ // authentication request over it or use it for an upgrade
+ // to another protocol.
+ //
+ // 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.
+ //
+ RefPtr<nsAHttpConnection> conn;
+ LOG((" authRetry=%d, sticky conn cap=%d", authRetry, 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 (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION ||
+ mTransaction->Caps() & NS_HTTP_STICKY_CONNECTION)) {
+ conn = mTransaction->GetConnectionReference();
+ LOG((" transaction %p provides connection %p", mTransaction.get(), conn.get()));
+ // This is so far a workaround to fix leak when reusing unpersistent
+ // connection for authentication retry. See bug 459620 comment 4
+ // for details.
+ if (conn && !conn->IsPersistent()) {
+ LOG((" connection is not persistent, not reusing it"));
+ conn = nullptr;
+ }
+ // We do not use a sticky connection in case of a nsHttpPipeline as
+ // well (see bug 1337826). This is a quick fix, because
+ // nsHttpPipeline is turned off by default.
+ RefPtr<nsAHttpTransaction> tranConn = do_QueryObject(conn);
+ if (tranConn && tranConn->QueryPipeline()) {
+ LOG(("Do not use this connection, it is a nsHttpPipeline."));
+ conn = nullptr;
+ }
+ }
+
+ RefPtr<nsAHttpConnection> stickyConn;
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ stickyConn = mTransaction->GetConnectionReference();
+ }
+
+ mTransferSize = mTransaction->GetTransferSize();
+
+ // 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.
+ if (request == mTransactionPump && mCacheEntry &&
+ !mAsyncOpenTime.IsNull() && !mOnStartRequestTimestamp.IsNull()) {
+ nsAutoCString onStartTime;
+ onStartTime.AppendInt( (uint64_t) (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds());
+ mCacheEntry->SetMetaDataElement("net-response-time-onstart", onStartTime.get());
+
+ nsAutoCString responseTime;
+ responseTime.AppendInt( (uint64_t) (TimeStamp::Now() - mAsyncOpenTime).ToMilliseconds());
+ mCacheEntry->SetMetaDataElement("net-response-time-onstop", responseTime.get());
+ }
+
+ // 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;
+ status = DoAuthRetry(conn);
+ if (NS_SUCCEEDED(status))
+ 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 (authRetry || (mAuthRetryPending && NS_FAILED(status))) {
+ MOZ_ASSERT(NS_FAILED(status), "should have a failure code here");
+ // NOTE: since we have a failure status, we can ignore the return
+ // value from onStartRequest.
+ if (mListener) {
+ MOZ_ASSERT(!mOnStartRequestCalled,
+ "We should not call OnStartRequest twice.");
+ mListener->OnStartRequest(this, mListenerContext);
+ mOnStartRequestCalled = true;
+ } else {
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ }
+ }
+
+ // if this transaction has been replaced, then bail.
+ if (mTransactionReplaced)
+ return NS_OK;
+
+ if (mUpgradeProtocolCallback && stickyConn &&
+ mResponseHead && mResponseHead->Status() == 101) {
+ gHttpHandler->ConnMgr()->CompleteUpgrade(stickyConn,
+ mUpgradeProtocolCallback);
+ }
+ }
+
+ // 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 0.9 is more likely to be an error than really 0.9, so count it that way
+ if (mCanceled) {
+ chanDisposition = kHttpCanceled;
+ } else if (!mUsedNetwork) {
+ chanDisposition = kHttpDisk;
+ } else if (NS_SUCCEEDED(status) &&
+ mResponseHead &&
+ mResponseHead->Version() != NS_HTTP_VERSION_0_9) {
+ chanDisposition = kHttpNetOK;
+ } else if (!mTransferSize) {
+ chanDisposition = kHttpNetEarlyFail;
+ } else {
+ chanDisposition = kHttpNetLateFail;
+ }
+ if (IsHTTPS()) {
+ // shift http to https disposition enums
+ chanDisposition = static_cast<ChannelDisposition>(chanDisposition + kHttpsCanceled);
+ }
+ LOG((" nsHttpChannel::OnStopRequest ChannelDisposition %d\n", chanDisposition));
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_DISPOSITION, chanDisposition);
+
+ // if needed, check cache entry has all data we expect
+ if (mCacheEntry && mCachePump &&
+ mConcurrentCacheAccess && contentComplete) {
+ 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 = 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) && mIsPartialRequest) {
+ // Prevent read from cache again
+ mCachedContentIsValid = 0;
+ mCachedContentIsPartial = 1;
+
+ // Perform the range request
+ rv = ContinueConnect();
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" performing range request"));
+ mCachePump = nullptr;
+ return NS_OK;
+ } else {
+ LOG((" but range request perform failed 0x%08x", rv));
+ status = NS_ERROR_NET_INTERRUPT;
+ }
+ }
+ else {
+ LOG((" but range request setup failed rv=0x%08x, failing load", rv));
+ }
+ }
+ }
+ }
+
+ mIsPending = false;
+ mStatus = status;
+
+ // perform any final cache operations before we close the cache entry.
+ if (mCacheEntry && mRequestTimeInitialized) {
+ bool writeAccess;
+ // New implementation just returns value of the !mCacheEntryIsReadOnly flag passed in.
+ // Old implementation checks on nsICache::ACCESS_WRITE flag.
+ mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess);
+ if (writeAccess) {
+ FinalizeCacheEntry();
+ }
+ }
+
+ // Register entry to the Performance resource timing
+ mozilla::dom::Performance* documentPerformance = GetPerformance();
+ if (documentPerformance) {
+ documentPerformance->AddEntry(this, this);
+ }
+
+ if (mListener) {
+ LOG((" calling OnStopRequest\n"));
+ MOZ_ASSERT(!mOnStopRequestCalled,
+ "We should not call OnStopRequest twice");
+ mListener->OnStopRequest(this, mListenerContext, status);
+ mOnStopRequestCalled = true;
+ }
+
+ // 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 (!mPreferredCachedAltDataType.IsEmpty()) {
+ mAltDataCacheEntry = mCacheEntry;
+ }
+
+ CloseCacheEntry(!contentComplete);
+
+ if (mOfflineCacheEntry)
+ CloseOfflineCacheEntry();
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, status);
+
+ // 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)
+ : 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, nsISupports *ctxt,
+ nsIInputStream *input,
+ uint64_t offset, uint32_t count)
+{
+ PROFILER_LABEL("nsHttpChannel", "OnDataAvailable",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%llu count=%u]\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!!");
+
+ MOZ_ASSERT(!(mCachedContentIsPartial && (request == mTransactionPump)),
+ "transaction pump not suspended");
+
+ if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) {
+ uint32_t n;
+ return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n);
+ }
+
+ 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(mResponseHead->ContentLength());
+ 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 {
+ nsresult 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,
+ mListenerContext,
+ 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
+ (NS_LITERAL_STRING(
+ "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 == NS_GetCurrentThread()) {
+ 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<nsIThread> mainThread;
+ rv = NS_GetMainThread(getter_AddRefs(mainThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = retargetableCachePump->RetargetDeliveryTo(mainThread);
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// 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) {
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(mSelfAddr, mPeerAddr);
+ } else {
+ nsCOMPtr<nsISocketTransport> socketTransport =
+ do_QueryInterface(trans);
+ if (socketTransport) {
+ socketTransport->GetSelfAddr(&mSelfAddr);
+ socketTransport->GetPeerAddr(&mPeerAddr);
+ }
+ }
+ }
+
+ // block socket status event after Cancel or OnStopRequest has been called.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending) {
+ LOG(("sending progress%s notification [this=%p status=%x"
+ " progress=%lld/%lld]\n",
+ (mLoadFlags & LOAD_BACKGROUND)? "" : " and status",
+ this, status, progress, progressMax));
+
+ if (!(mLoadFlags & LOAD_BACKGROUND)) {
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(this, nullptr, 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, nullptr, progress, progressMax);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::IsFromCache(bool *value)
+{
+ if (!mIsPending)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // return false if reading a partial cache entry; the data isn't entirely
+ // from the cache!
+
+ *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
+ mCachedContentIsValid && !mCachedContentIsPartial;
+
+ return NS_OK;
+}
+
+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::GetCacheTokenCachedCharset(nsACString &_retval)
+{
+ nsresult rv;
+
+ if (!mCacheEntry)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsXPIDLCString cachedCharset;
+ rv = mCacheEntry->GetMetaDataElement("charset",
+ getter_Copies(cachedCharset));
+ if (NS_SUCCEEDED(rv))
+ _retval = cachedCharset;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheTokenCachedCharset(const nsACString &aCharset)
+{
+ if (!mCacheEntry)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return mCacheEntry->SetMetaDataElement("charset",
+ PromiseFlatCString(aCharset).get());
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetAllowStaleCacheContent(bool aAllowStaleCacheContent)
+{
+ LOG(("nsHttpChannel::SetAllowStaleCacheContent [this=%p, allow=%d]",
+ this, aAllowStaleCacheContent));
+ mAllowStaleCacheContent = aAllowStaleCacheContent;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent)
+{
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = mAllowStaleCacheContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::PreferAlternativeDataType(const nsACString & aType)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataType = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetAlternativeDataType(nsACString & aType)
+{
+ // must be called during or after OnStartRequest
+ if (!mAfterOnStartRequestBegun) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_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;
+ }
+ return cacheEntry->OpenAlternativeOutputStream(type, _retval);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsICachingChannel
+//-----------------------------------------------------------------------------
+
+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(nsISupports **key)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(key);
+
+ LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
+
+ *key = nullptr;
+
+ nsCOMPtr<nsISupportsPRUint32> container =
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+
+ if (!container)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = container->SetData(mPostID);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(container.get(), key);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheKey(nsISupports *key)
+{
+ nsresult rv;
+
+ LOG(("nsHttpChannel::SetCacheKey [this=%p key=%p]\n", this, key));
+
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ if (!key)
+ mPostID = 0;
+ else {
+ // extract the post id
+ nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = container->GetData(&mPostID);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheOnlyMetadata(bool *aOnlyMetadata)
+{
+ NS_ENSURE_ARG(aOnlyMetadata);
+ *aOnlyMetadata = mCacheOnlyMetadata;
+ 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();
+
+ mCacheOnlyMetadata = aOnlyMetadata;
+ if (aOnlyMetadata) {
+ mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetPin(bool *aPin)
+{
+ NS_ENSURE_ARG(aPin);
+ *aPin = mPinCacheContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetPin(bool aPin)
+{
+ LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n",
+ this, aPin));
+
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mPinCacheContent = 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=%llu id='%s']\n",
+ this, aStartPos, PromiseFlatCString(aEntityID).get()));
+ mEntityID = aEntityID;
+ mStartPos = aStartPos;
+ mResuming = true;
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
+{
+ LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this));
+
+ MOZ_ASSERT(!mTransaction, "should not have a transaction");
+ nsresult rv;
+
+ // toggle mIsPending to allow nsIObserver implementations to modify
+ // the request headers (bug 95044).
+ mIsPending = 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();
+
+ mIsPending = 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 (mAuthConnectionRestartable) {
+ LOG((" connection made restartable"));
+ mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
+ mAuthConnectionRestartable = false;
+ } else {
+ LOG((" connection made non-restartable"));
+ mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
+ }
+
+ // and create a new one...
+ rv = SetupTransaction();
+ if (NS_FAILED(rv)) return rv;
+
+ // transfer ownership of connection to transaction
+ if (conn)
+ mTransaction->SetConnection(conn);
+
+ rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mTransactionPump->AsyncRead(this, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--)
+ mTransactionPump->Suspend();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// 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 = mLoadedFromApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetInheritApplicationCache(bool *aInherit)
+{
+ *aInherit = mInheritApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetInheritApplicationCache(bool aInherit)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mInheritApplicationCache = aInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetChooseApplicationCache(bool *aChoose)
+{
+ *aChoose = mChooseApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetChooseApplicationCache(bool aChoose)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mChooseApplicationCache = 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 = mCacheURI->CloneIgnoringRef(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;
+
+ nsAutoPtr<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);
+ }
+
+ mWaitingForRedirectCallback = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnRedirectVerifyCallback(nsresult result)
+{
+ LOG(("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
+ "result=%x stack=%d mWaitingForRedirectCallback=%u\n",
+ this, result, mRedirectFuncStack.Length(), mWaitingForRedirectCallback));
+ MOZ_ASSERT(mWaitingForRedirectCallback,
+ "Someone forgot to call WaitForRedirectCallback() ?!");
+ mWaitingForRedirectCallback = 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[i];
+ mRedirectFuncStack.RemoveElementAt(mRedirectFuncStack.Length() - 1);
+
+ // 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 (mWaitingForRedirectCallback)
+ 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 (!mWaitingForRedirectCallback) {
+ // 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[mRedirectFuncStack.Length() - 1],
+ "Trying to pop wrong method from redirect async stack!");
+
+ mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1);
+}
+
+//-----------------------------------------------------------------------------
+// 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%x]\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
+ NS_SUCCEEDED(status) ? "success" : "failure", 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+
+ // the new uri should inherit the origin charset of the current uri
+ nsAutoCString originCharset;
+ rv = mURI->GetOriginCharset(originCharset);
+ if (NS_FAILED(rv))
+ originCharset.Truncate();
+
+ return ioService->NewURI(nsDependentCString(loc),
+ originCharset.get(),
+ 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;
+ mResponseHead->GetHeader(nsHttp::Location, location);
+ if (!location.IsEmpty()) {
+ LOG((" Location-header=%s\n", location.get()));
+ InvalidateCacheEntryForLocation(location.get());
+ }
+
+ // Invalidate Content-Location-header if set
+ 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 =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+
+ 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, EmptyCString(), 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,
+ NS_GetCurrentThread(),
+ 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;
+}
+
+void
+nsHttpChannel::MarkIntercepted()
+{
+ mInterceptCache = INTERCEPTED;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetResponseSynthesized(bool* aSynthesized)
+{
+ NS_ENSURE_ARG_POINTER(aSynthesized);
+ *aSynthesized = (mInterceptCache == INTERCEPTED);
+ return NS_OK;
+}
+
+bool
+nsHttpChannel::AwaitingCacheCallbacks()
+{
+ return mCacheEntriesToWaitFor != 0;
+}
+
+void
+nsHttpChannel::SetPushedStream(Http2PushedStream *stream)
+{
+ MOZ_ASSERT(stream);
+ MOZ_ASSERT(!mPushedStream);
+ mPushedStream = stream;
+}
+
+nsresult
+nsHttpChannel::OnPush(const nsACString &url, Http2PushedStream *pushedStream)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpChannel::OnPush [this=%p]\n", this));
+
+ MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+
+ MOZ_ASSERT(pushListener);
+ if (!pushListener) {
+ LOG(("nsHttpChannel::OnPush [this=%p] notification callbacks do not "
+ "implement nsIHttpPushListener\n", this));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIURI> pushResource;
+ nsresult rv;
+
+ // Create a Channel for the Push Resource
+ rv = NS_NewURI(getter_AddRefs(pushResource), url);
+ 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, // 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(
+ pushedStream->GetRequestString().BeginWriting());
+
+ channel->mLoadGroup = mLoadGroup;
+ channel->mLoadInfo = mLoadInfo;
+ channel->mCallbacks = mCallbacks;
+
+ // Link the pushed stream with the new channel and call listener
+ channel->SetPushedStream(pushedStream);
+ 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());
+ mResponseCouldBeSynthesized = true;
+}
+
+void
+nsHttpChannel::SetConnectionInfo(nsHttpConnectionInfo *aCI)
+{
+ mConnectionInfo = aCI ? aCI->Clone() : nullptr;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnPreflightSucceeded()
+{
+ MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
+ mIsCorsPreflightDone = 1;
+ mPreflightChannel = nullptr;
+
+ return ContinueConnect();
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnPreflightFailed(nsresult aError)
+{
+ MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
+ mIsCorsPreflightDone = 1;
+ mPreflightChannel = nullptr;
+
+ CloseCacheEntry(false);
+ AsyncAbort(aError);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIHstsPrimingCallback functions
+//-----------------------------------------------------------------------------
+
+/*
+ * May be invoked synchronously if HSTS priming has already been performed
+ * for the host.
+ */
+nsresult
+nsHttpChannel::OnHSTSPrimingSucceeded(bool aCached)
+{
+ if (nsMixedContentBlocker::sUseHSTS) {
+ // redirect the channel to HTTPS if the pref
+ // "security.mixed_content.use_hsts" is true
+ LOG(("HSTS Priming succeeded, redirecting to HTTPS [this=%p]", this));
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ (aCached) ? HSTSPrimingResult::eHSTS_PRIMING_CACHED_DO_UPGRADE :
+ HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED);
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
+ }
+
+ // If "security.mixed_content.use_hsts" is false, record the result of
+ // HSTS priming and block or proceed with the load as required by
+ // mixed-content blocking
+ bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
+
+ // preserve the mixed-content-before-hsts order and block if required
+ if (wouldBlock) {
+ LOG(("HSTS Priming succeeded, blocking for mixed-content [this=%p]",
+ this));
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED_BLOCK);
+ CloseCacheEntry(false);
+ return AsyncAbort(NS_ERROR_CONTENT_BLOCKED);
+ }
+
+ LOG(("HSTS Priming succeeded, loading insecure: [this=%p]", this));
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED_HTTP);
+
+ nsresult rv = ContinueConnect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ return AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * May be invoked synchronously if HSTS priming has already been performed
+ * for the host.
+ */
+nsresult
+nsHttpChannel::OnHSTSPrimingFailed(nsresult aError, bool aCached)
+{
+ bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
+
+ LOG(("HSTS Priming Failed [this=%p], %s the load", this,
+ (wouldBlock) ? "blocking" : "allowing"));
+ if (aCached) {
+ // Between the time we marked for priming and started the priming request,
+ // the host was found to not allow the upgrade, probably from another
+ // priming request.
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ (wouldBlock) ? HSTSPrimingResult::eHSTS_PRIMING_CACHED_BLOCK :
+ HSTSPrimingResult::eHSTS_PRIMING_CACHED_NO_UPGRADE);
+ } else {
+ // A priming request was sent, and no HSTS header was found that allows
+ // the upgrade.
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ (wouldBlock) ? HSTSPrimingResult::eHSTS_PRIMING_FAILED_BLOCK :
+ HSTSPrimingResult::eHSTS_PRIMING_FAILED_ACCEPT);
+ }
+
+ // Don't visit again for at least
+ // security.mixed_content.hsts_priming_cache_timeout seconds.
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = sss->CacheNegativeHSTSResult(mURI,
+ nsMixedContentBlocker::sHSTSPrimingCacheTimeout);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsISiteSecurityService::CacheNegativeHSTSResult failed");
+ }
+
+ // If we would block, go ahead and abort with the error provided
+ if (wouldBlock) {
+ CloseCacheEntry(false);
+ return AsyncAbort(aError);
+ }
+
+ // we can continue the load and the UI has been updated as mixed content
+ rv = ContinueConnect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ return AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// AChannelHasDivertableParentChannelAsListener internal functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::MessageDiversionStarted(ADivertableParentChannel *aParentChannel)
+{
+ LOG(("nsHttpChannel::MessageDiversionStarted [this=%p]", this));
+ MOZ_ASSERT(!mParentChannel);
+ mParentChannel = aParentChannel;
+ // If the channel is suspended, propagate that info to the parent's mEventQ.
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--) {
+ mParentChannel->SuspendMessageDiversion();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::MessageDiversionStop()
+{
+ LOG(("nsHttpChannel::MessageDiversionStop [this=%p]", this));
+ MOZ_ASSERT(mParentChannel);
+ mParentChannel = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SuspendInternal()
+{
+ NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
+
+ ++mSuspendCount;
+
+ 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::ResumeInternal()
+{
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
+
+ if (--mSuspendCount == 0 && mCallOnResume) {
+ nsresult rv = AsyncCall(mCallOnResume);
+ mCallOnResume = nullptr;
+ NS_ENSURE_SUCCESS(rv, 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;
+}
+
+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(nsIDocument::eAppCache, false);
+ }
+}
+
+void
+nsHttpChannel::SetLoadGroupUserAgentOverride()
+{
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+ nsAutoCString uriScheme;
+ if (uri) {
+ uri->GetScheme(uriScheme);
+ }
+
+ // We don't need a UA for file: protocols.
+ if (uriScheme.EqualsLiteral("file")) {
+ gHttpHandler->OnUserAgentRequest(this);
+ return;
+ }
+
+ nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
+ nsCOMPtr<nsIRequestContext> rc;
+ if (rcsvc) {
+ rcsvc->GetRequestContext(mRequestContextID,
+ getter_AddRefs(rc));
+ }
+
+ nsAutoCString ua;
+ if (nsContentUtils::IsNonSubresourceRequest(this)) {
+ gHttpHandler->OnUserAgentRequest(this);
+ if (rc) {
+ GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
+ rc->SetUserAgentOverride(ua);
+ }
+ } else {
+ GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
+ // Don't overwrite the UA if it is already set (eg by an XHR with explicit UA).
+ if (ua.IsEmpty()) {
+ if (rc) {
+ rc->GetUserAgentOverride(ua);
+ SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua, false);
+ } else {
+ gHttpHandler->OnUserAgentRequest(this);
+ }
+ }
+ }
+}
+
+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()) ||
+ nsContentUtils::DoNotTrackEnabled()) {
+ mRequestHead.SetHeader(nsHttp::DoNotTrack,
+ NS_LITERAL_CSTRING("1"),
+ false);
+ }
+}
+
+
+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;
+ }
+
+ nsXPIDLCString tmpStr;
+ rv = mCacheEntry->GetMetaDataElement("net-response-time-onstart",
+ getter_Copies(tmpStr));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ uint64_t onStartNetTime = tmpStr.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ tmpStr.Truncate();
+ rv = mCacheEntry->GetMetaDataElement("net-response-time-onstop",
+ getter_Copies(tmpStr));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ uint64_t onStopNetTime = tmpStr.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ uint64_t onStartCacheTime = (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
+ int64_t onStartDiff = onStartNetTime - onStartCacheTime;
+ onStartDiff += 500; // We offset the difference by 500 ms to report positive values in telemetry
+
+ uint64_t onStopCacheTime = (mCacheReadEnd - mAsyncOpenTime).ToMilliseconds();
+ int64_t onStopDiff = onStopNetTime - onStopCacheTime;
+ onStopDiff += 500; // We offset the difference by 500 ms
+
+ if (mDidReval) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_REVALIDATED, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_REVALIDATED, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_NOTREVALIDATED, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_NOTREVALIDATED, onStopDiff);
+ }
+
+ if (mDidReval) {
+ // We don't report revalidated probes as the data would be skewed.
+ return;
+ }
+
+ uint32_t diskStorageSizeK = 0;
+ rv = mCacheEntry->GetDiskStorageSizeInKB(&diskStorageSizeK);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsAutoCString contentType;
+ if (mResponseHead && mResponseHead->HasContentType()) {
+ mResponseHead->ContentType(contentType);
+ }
+ bool isImage = StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"));
+ if (isImage) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_ISIMG, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_ISIMG, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_NOTIMG, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_NOTIMG, onStopDiff);
+ }
+
+ if (mCacheOpenWithPriority) {
+ if (mCacheQueueSizeWhenOpen < 5) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_HIGHPRI, onStopDiff);
+ } else if (mCacheQueueSizeWhenOpen < 10) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_HIGHPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_HIGHPRI, onStopDiff);
+ }
+ } else { // The limits are higher for normal priority cache queues
+ if (mCacheQueueSizeWhenOpen < 10) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_NORMALPRI, onStopDiff);
+ } else if (mCacheQueueSizeWhenOpen < 50) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_NORMALPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_NORMALPRI, onStopDiff);
+ }
+ }
+
+ if (diskStorageSizeK < 32) {
+ if (mCacheOpenWithPriority) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_SMALL_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_SMALL_HIGHPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_SMALL_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_SMALL_NORMALPRI, onStopDiff);
+ }
+ } else if (diskStorageSizeK < 256) {
+ if (mCacheOpenWithPriority) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_MED_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_MED_HIGHPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_MED_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_MED_NORMALPRI, onStopDiff);
+ }
+ } else {
+ if (mCacheOpenWithPriority) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_LARGE_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_LARGE_HIGHPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_LARGE_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_LARGE_NORMALPRI, onStopDiff);
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h
new file mode 100644
index 0000000000..ad8156ec03
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -0,0 +1,620 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et cin ts=4 sw=4 sts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "HttpBaseChannel.h"
+#include "nsTArray.h"
+#include "nsICachingChannel.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsIDNSListener.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIChannelWithDivertableParentListener.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsWeakReference.h"
+#include "TimingStruct.h"
+#include "ADivertableParentChannel.h"
+#include "AutoClose.h"
+#include "nsIStreamListener.h"
+#include "nsISupportsPrimitives.h"
+#include "nsICorsPreflightCallback.h"
+#include "AlternateServices.h"
+#include "nsIHstsPrimingCallback.h"
+
+class nsDNSPrefetch;
+class nsICancelable;
+class nsIHttpChannelAuthProvider;
+class nsInputStreamPump;
+class nsISSLStatus;
+
+namespace mozilla { namespace net {
+
+class Http2PushedStream;
+
+class HttpChannelSecurityWarningReporter
+{
+public:
+ virtual nsresult ReportSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) = 0;
+};
+
+//-----------------------------------------------------------------------------
+// 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 nsIChannelWithDivertableParentListener
+ , public nsIHstsPrimingCallback
+{
+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_NSIHSTSPRIMINGCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSICHANNELWITHDIVERTABLEPARENTLISTENER
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCHANNEL_IID)
+
+ // 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();
+
+ virtual nsresult Init(nsIURI *aURI, uint32_t aCaps, nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI,
+ const nsID& aChannelId) override;
+
+ nsresult OnPush(const nsACString &uri, Http2PushedStream *pushedStream);
+
+ static bool IsRedirectStatus(uint32_t status);
+
+
+ // 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 *listener, nsISupports *aContext) override;
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener) override;
+ // nsIHttpChannel
+ NS_IMETHOD GetEncodedBodySize(uint64_t *aEncodedBodySize) override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override;
+ NS_IMETHOD ForceIntercepted(uint64_t aInterceptionID) override;
+ virtual mozilla::net::nsHttpChannel * QueryHttpChannelImpl(void) 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 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;
+
+ nsresult AddSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) override;
+
+ void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter)
+ { mWarningReporter = aReporter; }
+
+public: /* internal necko use only */
+
+ void InternalSetUploadStream(nsIInputStream *uploadStream)
+ { mUploadStream = uploadStream; }
+ void SetUploadStreamHasHeaders(bool hasHeaders)
+ { mUploadStreamHasHeaders = hasHeaders; }
+
+ nsresult SetReferrerWithPolicyInternal(nsIURI *referrer,
+ uint32_t referrerPolicy) {
+ nsAutoCString spec;
+ nsresult rv = referrer->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+ mReferrer = referrer;
+ mReferrerPolicy = referrerPolicy;
+ mRequestHead.SetHeader(nsHttp::Referer, spec);
+ return NS_OK;
+ }
+
+ nsresult SetTopWindowURI(nsIURI* aTopWindowURI) {
+ mTopWindowURI = aTopWindowURI;
+ return NS_OK;
+ }
+
+ uint32_t GetRequestTime() const
+ {
+ return mRequestTime;
+ }
+
+ nsresult OpenCacheEntry(bool usingSSL);
+ nsresult ContinueConnect();
+
+ // If the load is mixed-content, build and send an HSTS priming request.
+ nsresult TryHSTSPriming();
+
+ 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->mCacheEntriesToWaitFor =
+ 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->mCacheEntriesToWaitFor &= mKeep;
+ }
+
+ private:
+ nsHttpChannel* mChannel;
+ uint32_t mKeep : 2;
+ };
+
+ void MarkIntercepted();
+ NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override;
+ bool AwaitingCacheCallbacks();
+ void SetCouldBeSynthesized();
+
+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; }
+
+protected:
+ virtual ~nsHttpChannel();
+
+private:
+ typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
+
+ bool RequestIsConditional();
+ nsresult BeginConnect();
+ void HandleBeginConnectContinue();
+ MOZ_MUST_USE nsresult BeginConnectContinue();
+ nsresult ContinueBeginConnectWithResult();
+ void ContinueBeginConnect();
+ nsresult Connect();
+ void SpeculativeConnect();
+ nsresult SetupTransaction();
+ void SetupTransactionRequestContext();
+ nsresult CallOnStartRequest();
+ nsresult ProcessResponse();
+ void AsyncContinueProcessResponse();
+ nsresult ContinueProcessResponse1();
+ nsresult ContinueProcessResponse2(nsresult);
+ nsresult ContinueProcessResponse3(nsresult);
+ nsresult ProcessNormal();
+ nsresult ContinueProcessNormal(nsresult);
+ void ProcessAltService();
+ bool ShouldBypassProcessNotModified();
+ nsresult ProcessNotModified();
+ nsresult AsyncProcessRedirection(uint32_t httpStatus);
+ nsresult ContinueProcessRedirection(nsresult);
+ nsresult ContinueProcessRedirectionAfterFallback(nsresult);
+ nsresult ProcessFailedProxyConnect(uint32_t httpStatus);
+ nsresult ProcessFallback(bool *waitingForRedirectCallback);
+ nsresult ContinueProcessFallback(nsresult);
+ void HandleAsyncAbort();
+ nsresult EnsureAssocReq();
+ void ProcessSSLInformation();
+ bool IsHTTPS();
+
+ nsresult ContinueOnStartRequest1(nsresult);
+ nsresult ContinueOnStartRequest2(nsresult);
+ nsresult ContinueOnStartRequest3(nsresult);
+
+ // redirection specific methods
+ void HandleAsyncRedirect();
+ void HandleAsyncAPIRedirect();
+ nsresult ContinueHandleAsyncRedirect(nsresult);
+ void HandleAsyncNotModified();
+ void HandleAsyncFallback();
+ nsresult ContinueHandleAsyncFallback(nsresult);
+ nsresult PromptTempRedirect();
+ virtual nsresult SetupReplacementChannel(nsIURI *, nsIChannel *,
+ bool preserveMethod,
+ uint32_t redirectFlags) override;
+
+ // proxy specific methods
+ nsresult ProxyFailover();
+ nsresult AsyncDoReplaceWithProxy(nsIProxyInfo *);
+ nsresult ContinueDoReplaceWithProxy(nsresult);
+ nsresult ResolveProxy();
+
+ // cache specific methods
+ nsresult OnOfflineCacheEntryAvailable(nsICacheEntry *aEntry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult aResult);
+ nsresult OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
+ bool aNew,
+ nsresult aResult);
+ nsresult OpenOfflineCacheEntryForWriting();
+ nsresult OnOfflineCacheEntryForWritingAvailable(nsICacheEntry *aEntry,
+ nsIApplicationCache* aAppCache,
+ nsresult aResult);
+ nsresult OnCacheEntryAvailableInternal(nsICacheEntry *entry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult status);
+ nsresult GenerateCacheKey(uint32_t postID, nsACString &key);
+ nsresult UpdateExpirationTime();
+ nsresult CheckPartial(nsICacheEntry* aEntry, int64_t *aSize, int64_t *aContentLength);
+ bool ShouldUpdateOfflineCacheEntry();
+ nsresult ReadFromCache(bool alreadyMarkedValid);
+ void CloseCacheEntry(bool doomOnFailure);
+ void CloseOfflineCacheEntry();
+ nsresult InitCacheEntry();
+ void UpdateInhibitPersistentCachingFlag();
+ nsresult InitOfflineCacheEntry();
+ nsresult AddCacheEntryHeaders(nsICacheEntry *entry);
+ nsresult FinalizeCacheEntry();
+ nsresult InstallCacheListener(int64_t offset = 0);
+ 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
+ nsresult ProcessPartialContent();
+ nsresult OnDoneReadingPartialCacheEntry(bool *streamDone);
+
+ nsresult DoAuthRetry(nsAHttpConnection *);
+
+ void HandleAsyncRedirectChannelToHttps();
+ nsresult StartRedirectChannelToHttps();
+ nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
+ nsresult OpenRedirectChannel(nsresult rv);
+
+ /**
+ * 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.
+ */
+ 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
+ */
+ nsresult ProcessContentSignatureHeader(nsHttpResponseHead *aResponseHead);
+
+ /**
+ * A function that will, if the feature is enabled, send security reports.
+ */
+ void ProcessSecurityReport(nsresult status);
+
+ /**
+ * 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.
+ */
+ nsresult ProcessSingleSecurityHeader(uint32_t aType,
+ nsISSLStatus *aSSLStatus,
+ uint32_t aFlags);
+
+ void InvalidateCacheEntryForLocation(const char *location);
+ void AssembleCacheKey(const char *spec, uint32_t postID, nsACString &key);
+ 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;
+ }
+
+ // Report net vs cache time telemetry
+ void ReportNetVSCacheTelemetry();
+
+ // 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;
+ nsresult MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen = false);
+ nsresult SetupByteRangeRequest(int64_t partialLen);
+ void UntieByteRangeRequest();
+ void UntieValidationRequest();
+ nsresult OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering,
+ bool checkingAppCacheEntry);
+
+ void SetPushedStream(Http2PushedStream *stream);
+
+ void MaybeWarnAboutAppCache();
+
+ void SetLoadGroupUserAgentOverride();
+
+ void SetDoNotTrack();
+
+private:
+ nsCOMPtr<nsICancelable> mProxyRequest;
+
+ RefPtr<nsInputStreamPump> mTransactionPump;
+ RefPtr<nsHttpTransaction> 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;
+ // We must close mCacheInputStream explicitly to avoid leaks.
+ AutoClose<nsIInputStream> mCacheInputStream;
+ RefPtr<nsInputStreamPump> mCachePump;
+ nsAutoPtr<nsHttpResponseHead> mCachedResponseHead;
+ nsCOMPtr<nsISupports> mCachedSecurityInfo;
+ uint32_t mPostID;
+ uint32_t mRequestTime;
+
+ nsCOMPtr<nsICacheEntry> mOfflineCacheEntry;
+ uint32_t mOfflineCacheLastModifiedTime;
+ nsCOMPtr<nsIApplicationCache> mApplicationCacheForWrite;
+
+ // auth specific data
+ nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
+
+ mozilla::TimeStamp mOnStartRequestTimestamp;
+
+ // States of channel interception
+ enum {
+ DO_NOT_INTERCEPT, // no interception will occur
+ MAYBE_INTERCEPT, // interception in progress, but can be cancelled
+ INTERCEPTED, // a synthesized response has been provided
+ } mInterceptCache;
+ // ID of this channel for the interception purposes. Unique unless this
+ // channel is replacing an intercepted one via an redirection.
+ uint64_t mInterceptionID;
+
+ bool PossiblyIntercepted() {
+ return mInterceptCache != DO_NOT_INTERCEPT;
+ }
+
+ // 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>;
+
+ nsCOMPtr<nsIURI> mRedirectURI;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ 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;
+
+ // state flags
+ uint32_t mCachedContentIsValid : 1;
+ uint32_t mCachedContentIsPartial : 1;
+ uint32_t mCacheOnlyMetadata : 1;
+ uint32_t mTransactionReplaced : 1;
+ uint32_t mAuthRetryPending : 1;
+ uint32_t mProxyAuthPending : 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 mCustomAuthHeader : 1;
+ uint32_t mResuming : 1;
+ uint32_t mInitedCacheEntry : 1;
+ // True if we are loading a fallback cache entry from the
+ // application cache.
+ uint32_t mFallbackChannel : 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 mCustomConditionalRequest : 1;
+ uint32_t mFallingBack : 1;
+ uint32_t mWaitingForRedirectCallback : 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 mRequestTimeInitialized : 1;
+ uint32_t mCacheEntryIsReadOnly : 1;
+ uint32_t mCacheEntryIsWriteOnly : 1;
+ // see WAIT_FOR_* constants above
+ uint32_t mCacheEntriesToWaitFor : 2;
+ uint32_t mHasQueryString : 1;
+ // whether cache entry data write was in progress during cache entry check
+ // when true, after we finish read from cache we must check all data
+ // had been loaded from cache. If not, then an error has to be propagated
+ // to the consumer.
+ uint32_t mConcurrentCacheAccess : 1;
+ // whether the request is setup be byte-range
+ uint32_t mIsPartialRequest : 1;
+ // true iff there is AutoRedirectVetoNotifier on the stack
+ uint32_t mHasAutoRedirectVetoNotifier : 1;
+ // consumers set this to true to use cache pinning, this has effect
+ // only when the channel is in an app context (load context has an appid)
+ uint32_t mPinCacheContent : 1;
+ // True if CORS preflight has been performed
+ uint32_t mIsCorsPreflightDone : 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 mStronglyFramed : 1;
+
+ // true if an HTTP transaction is created for the socket thread
+ uint32_t mUsedNetwork : 1;
+
+ // the next authentication request can be sent on a whole new connection
+ uint32_t mAuthConnectionRestartable : 1;
+
+ nsCOMPtr<nsIChannel> mPreflightChannel;
+
+ nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
+
+ // Needed for accurate DNS timing
+ RefPtr<nsDNSPrefetch> mDNSPrefetch;
+
+ Http2PushedStream *mPushedStream;
+ // True if the channel's principal was found on a phishing, malware, or
+ // tracking (if tracking protection is enabled) blocklist
+ bool mLocalBlocklist;
+
+ nsresult WaitForRedirectCallback();
+ void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
+ void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
+
+ nsCString mUsername;
+
+ // If non-null, warnings should be reported to this object.
+ HttpChannelSecurityWarningReporter* mWarningReporter;
+
+ RefPtr<ADivertableParentChannel> mParentChannel;
+protected:
+ virtual void DoNotifyListenerCleanup() override;
+
+private: // cache telemetry
+ bool mDidReval;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpChannel, NS_HTTPCHANNEL_IID)
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpChannel_h__
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
new file mode 100644
index 0000000000..9a22752870
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -0,0 +1,1682 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/Preferences.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 "nsIPrompt.h"
+#include "netCore.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsILoadContext.h"
+#include "nsIURL.h"
+#include "mozilla/Telemetry.h"
+#include "nsIProxiedChannel.h"
+#include "nsIProxyInfo.h"
+
+namespace mozilla {
+namespace 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 0
+#define HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE 1
+#define HTTP_AUTH_DIALOG_CROSS_ORIGIN_SUBRESOURCE 2
+#define HTTP_AUTH_DIALOG_XHR 3
+
+#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
+
+static void
+GetOriginAttributesSuffix(nsIChannel* aChan, nsACString &aSuffix)
+{
+ NeckoOriginAttributes oa;
+
+ // Deliberately ignoring the result and going with defaults
+ if (aChan) {
+ NS_GetOriginAttributes(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");
+}
+
+uint32_t nsHttpChannelAuthProvider::sAuthAllowPref =
+ SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL;
+
+void
+nsHttpChannelAuthProvider::InitializePrefs()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::Preferences::AddUintVarCache(&sAuthAllowPref,
+ "network.auth.subresource-http-auth-allow",
+ SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL);
+}
+
+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;
+
+ mAuthChannel->GetIsSSL(&mUsingSSL);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxiedChannel> proxied(do_QueryInterface(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(NS_LITERAL_STRING("SuperfluousAuth"), true)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ 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);
+
+ ident.Set(domain, user, passBuf);
+}
+
+// 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);
+}
+
+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)
+{
+ 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);
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ 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;
+
+}
+
+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).
+
+ nsAutoCString contractId;
+ contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
+ contractId.Append(mProxyAuthType);
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpAuthenticator> precedingAuth =
+ do_GetService(contractId.get(), &rv);
+ 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(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ LOG((" cleared proxy authorization header"));
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannelAuthProvider::GetCredentials(const char *challenges,
+ bool proxyAuth,
+ nsAFlatCString &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;
+ }
+ else 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,
+ nsCSubstring& scheme,
+ const char*& host,
+ int32_t& port,
+ nsCSubstring& 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,
+ nsAFlatCString &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;
+
+ if (!proxyAuth) {
+ // 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;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ GetOriginAttributesSuffix(chan, suffix);
+
+ //
+ // 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;
+ 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.
+ mAuthChannel->CloseStickyConnection();
+ }
+
+ 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(mConnectionBased && !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.
+ ident->Set(entry->Identity());
+ 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 (gHttpHandler->IsTelemetryEnabled()) {
+ if (NS_LITERAL_CSTRING("basic").LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_BASIC_SECURE : HTTP_AUTH_BASIC_INSECURE);
+ } else if (NS_LITERAL_CSTRING("digest").LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_DIGEST_SECURE : HTTP_AUTH_DIGEST_INSECURE);
+ } else if (NS_LITERAL_CSTRING("ntlm").LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NTLM_SECURE : HTTP_AUTH_NTLM_INSECURE);
+ } else if (NS_LITERAL_CSTRING("negotiate").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()) {
+ LOG(("nsHttpChannelAuthProvider::GetCredentialsForChallenge: "
+ "Prompt is blocked [this=%p pref=%d]\n",
+ this, sAuthAllowPref));
+ 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(NS_LITERAL_STRING("AutomaticAuth"), false)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ mAuthChannel->Cancel(NS_ERROR_ABORT);
+ // 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.
+ //
+ nsXPIDLCString 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()
+{
+ // 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;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ chan->GetLoadInfo(getter_AddRefs(loadInfo));
+
+ // We will treat loads w/o loadInfo as a top level document.
+ bool topDoc = true;
+ bool xhr = false;
+
+ if (loadInfo) {
+ if (loadInfo->GetExternalContentPolicyType() !=
+ nsIContentPolicy::TYPE_DOCUMENT) {
+ topDoc = false;
+ }
+ if (loadInfo->GetExternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_XMLHTTPREQUEST) {
+ xhr = true;
+ }
+
+ if (!topDoc && !xhr) {
+ nsCOMPtr<nsIURI> topURI;
+ chanInternal->GetTopWindowURI(getter_AddRefs(topURI));
+
+ if (!topURI) {
+ // If we do not have topURI try the loadingPrincipal.
+ nsCOMPtr<nsIPrincipal> loadingPrinc = loadInfo->LoadingPrincipal();
+ if (loadingPrinc) {
+ loadingPrinc->GetURI(getter_AddRefs(topURI));
+ }
+ }
+
+ if (!NS_SecurityCompareURIs(topURI, mURI, true)) {
+ mCrossOrigin = true;
+ }
+ }
+ }
+
+ if (gHttpHandler->IsTelemetryEnabled()) {
+ if (topDoc) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS,
+ HTTP_AUTH_DIALOG_TOP_LEVEL_DOC);
+ } else if (xhr) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS,
+ HTTP_AUTH_DIALOG_XHR);
+ } else if (!mCrossOrigin) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS,
+ HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS,
+ HTTP_AUTH_DIALOG_CROSS_ORIGIN_SUBRESOURCE);
+ }
+ }
+
+ switch (sAuthAllowPref) {
+ 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.
+ 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);
+
+ nsAutoCString contractid;
+ contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
+ contractid.Append(authType);
+
+ return CallGetService(contractid.get(), auth);
+}
+
+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);
+ CopyASCIItoUTF16(buf, userBuf);
+ mURI->GetPassword(buf);
+ if (!buf.IsEmpty()) {
+ NS_UnescapeURL(buf);
+ CopyASCIItoUTF16(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)
+{
+ identity.Set(Domain().get(), User().get(), Password().get());
+}
+
+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.
+ mAuthChannel->CloseStickyConnection();
+ }
+
+ 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;
+ GetOriginAttributesSuffix(chan, suffix);
+
+ nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
+ nsHttpAuthEntry *entry = nullptr;
+ authCache->GetAuthEntryForDomain(scheme.get(), host, port,
+ realm.get(), suffix,
+ &entry);
+
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry)
+ sessionStateGrip = entry->mMetaData;
+
+ nsAuthInformationHolder* holder =
+ static_cast<nsAuthInformationHolder*>(aAuthInfo);
+ ident->Set(holder->Domain().get(),
+ holder->User().get(),
+ holder->Password().get());
+
+ 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;
+ }
+
+ nsXPIDLCString 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.
+ if (mConnectionBased) {
+ mAuthChannel->CloseStickyConnection();
+ mConnectionBased = false;
+ }
+
+ if (userCancel) {
+ if (!mRemainingChallenges.IsEmpty()) {
+ // there are still some challenges to process, do so
+ nsresult rv;
+
+ // 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);
+ }
+ else 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();
+ }
+
+ mAuthChannel->OnAuthCancelled(userCancel);
+
+ 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;
+
+ UpdateCache(auth, scheme.get(), host, port, directory.get(), realm.get(),
+ mCurrentChallenge.get(), *ident, aGeneratedCreds, aFlags, aSessionState);
+ mCurrentChallenge.Truncate();
+
+ ContinueOnAuthAvailable(nsDependentCString(aGeneratedCreds));
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannelAuthProvider::ContinueOnAuthAvailable(const nsCSubstring& 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();
+
+ mAuthChannel->OnAuthAvailable();
+
+ return NS_OK;
+}
+
+bool
+nsHttpChannelAuthProvider::ConfirmAuth(const nsString &bundleKey,
+ bool doYesNoPrompt)
+{
+ // skip prompting the user if
+ // 1) we've already prompted the user
+ // 2) we're not a toplevel channel
+ // 3) the userpass length is less than the "phishy" threshold
+
+ 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);
+ const char16_t *strs[2] = { ucsHost.get(), ucsUser.get() };
+
+ nsXPIDLString msg;
+ bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg));
+ if (!msg)
+ 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<nsIPrompt> prompt;
+ NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_IID(nsIPrompt),
+ getter_AddRefs(prompt));
+ if (!prompt)
+ return true;
+
+ // do not prompt again
+ mSuppressDefensiveAuth = true;
+
+ bool confirmed;
+ if (doYesNoPrompt) {
+ int32_t choice;
+ bool checkState = false;
+ rv = prompt->ConfirmEx(nullptr, msg,
+ nsIPrompt::BUTTON_POS_1_DEFAULT +
+ nsIPrompt::STD_YES_NO_BUTTONS,
+ nullptr, nullptr, nullptr, nullptr,
+ &checkState, &choice);
+ if (NS_FAILED(rv))
+ return true;
+
+ confirmed = choice == 0;
+ }
+ else {
+ rv = prompt->Confirm(nullptr, msg, &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;
+
+ if (header == nsHttp::Proxy_Authorization) {
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ continuationState = &mAuthContinuationState;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ 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()) {
+ ident.Set(entry->Identity());
+ identFromURI = false;
+ }
+ else
+ identFromURI = true;
+
+ nsXPIDLCString temp;
+ 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)
+ mAuthChannel->SetProxyCredentials(nsDependentCString(creds));
+ else
+ mAuthChannel->SetWWWCredentials(nsDependentCString(creds));
+
+ // 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->GetPath(path);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
+ nsIHttpChannelAuthProvider, nsIAuthPromptCallback, nsIHttpAuthenticatorCallback)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.h b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
new file mode 100644
index 0000000000..44d79b22b7
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et cin ts=4 sw=4 sts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 : public nsIHttpChannelAuthProvider
+ , public nsIAuthPromptCallback
+ , public nsIHttpAuthenticatorCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSIHTTPCHANNELAUTHPROVIDER
+ NS_DECL_NSIAUTHPROMPTCALLBACK
+ NS_DECL_NSIHTTPAUTHENTICATORCALLBACK
+
+ nsHttpChannelAuthProvider();
+ static void InitializePrefs();
+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()); }
+
+ nsresult PrepareForAuthentication(bool proxyAuth);
+ 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);
+ 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.
+ */
+ nsresult GetCredentials(const char *challenges, bool proxyAuth,
+ nsAFlatCString &creds);
+ nsresult GetCredentialsForChallenge(const char *challenge,
+ const char *scheme, bool proxyAuth,
+ nsIHttpAuthenticator *auth,
+ nsAFlatCString &creds);
+ nsresult PromptForIdentity(uint32_t level, bool proxyAuth,
+ const char *realm, const char *authType,
+ uint32_t authFlags, nsHttpAuthIdentity &);
+
+ bool ConfirmAuth(const nsString &bundleKey, bool doYesNoPrompt);
+ void SetAuthorizationHeader(nsHttpAuthCache *, nsHttpAtom header,
+ const char *scheme, const char *host,
+ int32_t port, const char *path,
+ nsHttpAuthIdentity &ident);
+ 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).
+ */
+ nsresult GetAuthorizationMembers(bool proxyAuth, nsCSubstring& scheme,
+ const char*& host, int32_t& port,
+ nsCSubstring& 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.
+ */
+ nsresult ContinueOnAuthAvailable(const nsCSubstring& creds);
+
+ 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.
+ */
+ 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();
+
+ // Store credentials to the cache when appropriate aFlags are set.
+ 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);
+
+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
+
+ // A variable holding the preference settings to whether to open HTTP
+ // authentication credentials dialogs for sub-resources and cross-origin
+ // sub-resources.
+ static uint32_t sAuthAllowPref;
+ 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..a532c137d9
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
@@ -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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+#include <errno.h>
+#include "nsHttpChunkedDecoder.h"
+#include <algorithm>
+#include "plstr.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 RFC2617 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)
+{
+ NS_PRECONDITION(mChunkRemaining == 0, "chunk remaining should be zero");
+ NS_PRECONDITION(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 = new nsHttpHeaderArray();
+ }
+ mTrailers->ParseHeaderLine(nsDependentCSubstring(buf, count));
+ }
+ 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..64c8fbf648
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.h
@@ -0,0 +1,56 @@
+/* -*- 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 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() { delete mTrailers; }
+
+ bool ReachedEOF() { return mReachedEOF; }
+
+ // called by the transaction to handle chunked content.
+ nsresult HandleChunkedContent(char *buf,
+ uint32_t count,
+ uint32_t *contentRead,
+ uint32_t *contentRemaining);
+
+ nsHttpHeaderArray *Trailers() { return mTrailers; }
+
+ nsHttpHeaderArray *TakeTrailers() { nsHttpHeaderArray *h = mTrailers;
+ mTrailers = nullptr;
+ return h; }
+
+ uint32_t GetChunkRemaining() { return mChunkRemaining; }
+
+private:
+ nsresult ParseChunkRemaining(char *buf,
+ uint32_t count,
+ uint32_t *countRead);
+
+private:
+ 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..916d1249c0
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -0,0 +1,2319 @@
+/* -*- 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"
+
+// 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 "nsHttpPipeline.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.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 "sslt.h"
+#include "TunnelUtils.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <public>
+//-----------------------------------------------------------------------------
+
+nsHttpConnection::nsHttpConnection()
+ : mTransaction(nullptr)
+ , mHttpHandler(gHttpHandler)
+ , mCallbacksLock("nsHttpConnection::mCallbacksLock")
+ , mConsiderReusedAfterInterval(0)
+ , mConsiderReusedAfterEpoch(0)
+ , mCurrentBytesRead(0)
+ , mMaxBytesRead(0)
+ , mTotalBytesRead(0)
+ , mTotalBytesWritten(0)
+ , mContentBytesWritten(0)
+ , mConnectedTransport(false)
+ , mKeepAlive(true) // assume to keep-alive by default
+ , mKeepAliveMask(true)
+ , mDontReuse(false)
+ , mSupportsPipelining(false) // assume low-grade server
+ , mIsReused(false)
+ , mCompletedProxyConnect(false)
+ , mLastTransactionExpectedNoContent(false)
+ , mIdleMonitoring(false)
+ , mProxyConnectInProgress(false)
+ , mExperienced(false)
+ , mInSpdyTunnel(false)
+ , mForcePlainText(false)
+ , mTrafficStamp(false)
+ , mHttp1xTransactionCount(0)
+ , mRemainingConnectionUses(0xffffffff)
+ , mClassification(nsAHttpTransaction::CLASS_GENERAL)
+ , mNPNComplete(false)
+ , mSetupSSLCalled(false)
+ , mUsingSpdyVersion(0)
+ , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
+ , mReportedSpdy(false)
+ , mEverUsedSpdy(false)
+ , mLastHttpResponseVersion(NS_HTTP_VERSION_1_1)
+ , mTransactionCaps(0)
+ , mResponseTimeoutEnabled(false)
+ , mTCPKeepaliveConfig(kTCPKeepaliveDisabled)
+ , mForceSendPending(false)
+ , m0RTTChecked(false)
+ , mWaitingFor0RTTResponse(false)
+ , mContentBytesWritten0RTT(0)
+ , mEarlyDataNegotiated(false)
+{
+ 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();
+}
+
+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);
+ }
+
+ 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_CONN :
+ Telemetry::HTTP_KBREAD_PER_CONN,
+ totalKBRead);
+ }
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+}
+
+nsresult
+nsHttpConnection::Init(nsHttpConnectionInfo *info,
+ uint16_t maxHangTime,
+ nsISocketTransport *transport,
+ nsIAsyncInputStream *instream,
+ nsIAsyncOutputStream *outstream,
+ bool connectedTransport,
+ nsIInterfaceRequestor *callbacks,
+ PRIntervalTime rtt)
+{
+ LOG(("nsHttpConnection::Init this=%p", this));
+ NS_ENSURE_ARG_POINTER(info);
+ NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
+
+ mConnectedTransport = connectedTransport;
+ mConnInfo = info;
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+ mSupportsPipelining =
+ gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
+ 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>(callbacks, false);
+
+ mSocketTransport->SetEventSink(this, nullptr);
+ mSocketTransport->SetSecurityCallbacks(this);
+
+ return NS_OK;
+}
+
+void
+nsHttpConnection::StartSpdy(uint8_t spdyVersion)
+{
+ LOG(("nsHttpConnection::StartSpdy [this=%p]\n", this));
+
+ MOZ_ASSERT(!mSpdySession);
+
+ mUsingSpdyVersion = spdyVersion;
+ mEverUsedSpdy = true;
+ mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport);
+
+ 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 pipeline 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 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ bool spdyProxy = mConnInfo->UsingHttpsProxy() && !mTLSFilter;
+ if (spdyProxy) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCi;
+ mConnInfo->CreateWildCard(getter_AddRefs(wildCardProxyCi));
+ gHttpHandler->ConnMgr()->MoveToWildCardConnEntry(mConnInfo,
+ wildCardProxyCi, this);
+ mConnInfo = wildCardProxyCi;
+ }
+
+ if (NS_FAILED(rv)) { // 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::StartSpdy moves single transaction %p "
+ "into SpdySession %p\n", mTransaction.get(), mSpdySession.get()));
+ rv = AddTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ } else {
+ int32_t count = list.Length();
+
+ LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d "
+ "into SpdySession %p\n", count, mSpdySession.get()));
+
+ if (!count) {
+ mTransaction->Close(NS_ERROR_ABORT);
+ return;
+ }
+
+ for (int32_t index = 0; index < count; ++index) {
+ rv = AddTransaction(list[index], mPriority);
+ 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%x]", this, rv));
+ }
+
+ mSupportsPipelining = false; // don't use http/1 pipelines with spdy
+ mIdleTimeout = gHttpHandler->SpdyTimeout();
+
+ if (!mTLSFilter) {
+ mTransaction = mSpdySession;
+ } else {
+ mTLSFilter->SetProxiedTransaction(mSpdySession);
+ }
+ 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;
+ nsCOMPtr<nsISupports> securityInfo;
+ nsCOMPtr<nsISSLSocketControl> ssl;
+ nsAutoCString negotiatedNPN;
+
+ GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (!securityInfo) {
+ goto npnComplete;
+ }
+
+ ssl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv))
+ goto npnComplete;
+
+ rv = ssl->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;
+ nsAutoCString earlyNegotiatedNPN;
+ nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+ 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.
+ LOG(("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 = ssl->GetNegotiatedNPN(negotiatedNPN);
+ if (rv == NS_ERROR_NOT_CONNECTED) {
+ rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+ }
+ }
+
+ if (NS_FAILED(rvEarlyAlpn)) {
+ LOG(("nsHttpConnection::EnsureNPNComplete %p - "
+ "early selected alpn not available", this));
+ mEarlyDataNegotiated = false;
+ } else {
+ LOG(("nsHttpConnection::EnsureNPNComplete %p -"
+ "early selected alpn: %s", this, earlyNegotiatedNPN.get()));
+ uint32_t infoIndex;
+ const SpdyInformation *info = gHttpHandler->SpdyInfo();
+ // We are doing 0RTT only with Http/1 right now!
+ if (NS_FAILED(info->GetNPNIndex(earlyNegotiatedNPN, &infoIndex))) {
+ // Check if early-data is allowed for this transaction.
+ if (mTransaction->Do0RTT()) {
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - We "
+ "can do 0RTT!", this));
+ mWaitingFor0RTTResponse = true;
+ }
+ 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;
+ }
+
+ rv = ssl->DriveHandshake();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ goto npnComplete;
+ }
+
+ return false;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
+ this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
+ mTLSFilter ? " [Double Tunnel]" : ""));
+
+ bool ealyDataAccepted = false;
+ if (mWaitingFor0RTTResponse) {
+ // Check if early data has been accepted.
+ rv = ssl->GetEarlyDataAccepted(&ealyDataAccepted);
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - early data "
+ "that was sent during 0RTT %s been accepted.",
+ this, ealyDataAccepted ? "has" : "has not"));
+
+ if (NS_FAILED(rv) ||
+ NS_FAILED(mTransaction->Finish0RTT(!ealyDataAccepted))) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ goto npnComplete;
+ }
+ }
+
+ int16_t tlsVersion;
+ ssl->GetSSLVersionUsed(&tlsVersion);
+ // 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,
+ ealyDataAccepted);
+ }
+ if (ealyDataAccepted) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_BYTES_WRITTEN,
+ mContentBytesWritten0RTT);
+ }
+ }
+ mWaitingFor0RTTResponse = false;
+
+ if (!ealyDataAccepted) {
+ uint32_t infoIndex;
+ const SpdyInformation *info = gHttpHandler->SpdyInfo();
+ if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) {
+ StartSpdy(info->Version[infoIndex]);
+ }
+ } else {
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - %d bytes "
+ "has been sent during 0RTT.", this, mContentBytesWritten0RTT));
+ mContentBytesWritten = mContentBytesWritten0RTT;
+ }
+
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
+ }
+
+npnComplete:
+ LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
+ mNPNComplete = true;
+ if (mWaitingFor0RTTResponse) {
+ mWaitingFor0RTTResponse = false;
+ if (NS_FAILED(mTransaction->Finish0RTT(true))) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ }
+ mContentBytesWritten0RTT = 0;
+ }
+ return true;
+}
+
+void
+nsHttpConnection::OnTunnelNudged(TLSFilterTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnection::OnTunnelNudged %p\n", this));
+ if (trans != mTLSFilter) {
+ return;
+ }
+ LOG(("nsHttpConnection::OnTunnelNudged %p Calling OnSocketWritable\n", this));
+ OnSocketWritable();
+}
+
+// called on the socket thread
+nsresult
+nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnection::Activate [this=%p trans=%p caps=%x]\n",
+ this, trans, caps));
+
+ if (!trans->IsNullTransaction())
+ mExperienced = true;
+
+ mTransactionCaps = caps;
+ mPriority = pri;
+ if (mTransaction && mUsingSpdyVersion) {
+ return AddTransaction(trans, pri);
+ }
+
+ NS_ENSURE_ARG_POINTER(trans);
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
+
+ // reset the read timers to wash away any idle time
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+
+ // Connection failures are Activated() just like regular transacions.
+ // If we don't have a confirmation of a connected socket then test it
+ // with a write() to get relevant error code.
+ if (!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 %x\n",
+ this, 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%x]",
+ this, rv));
+ }
+
+ if (mTLSFilter) {
+ mTLSFilter->SetProxiedTransaction(trans);
+ mTransaction = mTLSFilter;
+ }
+
+ rv = OnOutputStreamReady(mSocketOut);
+
+failed_activation:
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ }
+
+ return rv;
+}
+
+void
+nsHttpConnection::SetupSSL()
+{
+ LOG(("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.
+ if (mInSpdyTunnel) {
+ InitSSLParams(false, true);
+ } else {
+ bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
+ InitSSLParams(usingHttpsProxy, usingHttpsProxy);
+ }
+}
+
+// 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(NS_LITERAL_CSTRING("http/1.1"));
+
+ 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 %x\n",this, rv));
+ return rv;
+}
+
+nsresult
+nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction,
+ int32_t priority)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mSpdySession && mUsingSpdyVersion,
+ "AddTransaction to live http connection without spdy");
+
+ // 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();
+
+ LOG(("nsHttpConnection::AddTransaction for SPDY%s",
+ needTunnel ? " over tunnel" : ""));
+
+ if (!mSpdySession->AddStream(httpTransaction, priority,
+ needTunnel, mCallbacks)) {
+ MOZ_ASSERT(false); // this cannot happen!
+ httpTransaction->Close(NS_ERROR_ABORT);
+ return NS_ERROR_FAILURE;
+ }
+
+ ResumeSend();
+ return NS_OK;
+}
+
+void
+nsHttpConnection::Close(nsresult reason, bool aIsShutdown)
+{
+ LOG(("nsHttpConnection::Close [this=%p reason=%x]\n", this, reason));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // Ensure TCP keepalive timer is stopped.
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ 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->ConnMgr()->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(PR_GetCurrentThread() == gSocketThread);
+
+ 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();
+}
+
+// Checked by the Connection Manager before scheduling a pipelined transaction
+bool
+nsHttpConnection::SupportsPipelining()
+{
+ if (mTransaction &&
+ mTransaction->PipelineDepth() >= mRemainingConnectionUses) {
+ LOG(("nsHttpConnection::SupportsPipelining this=%p deny pipeline "
+ "because current depth %d exceeds max remaining uses %d\n",
+ this, mTransaction->PipelineDepth(), mRemainingConnectionUses));
+ return false;
+ }
+ return mSupportsPipelining && IsKeepAlive() && !mDontReuse;
+}
+
+bool
+nsHttpConnection::CanReuse()
+{
+ if (mDontReuse)
+ return false;
+
+ if ((mTransaction ? mTransaction->PipelineDepth() : 0) >=
+ 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 && mHttp1xTransactionCount &&
+ NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
+ LOG(("nsHttpConnection::CanReuse %p %s"
+ "Socket not reusable because read data pending (%llu) 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()
+{
+ 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;
+}
+
+bool
+nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead)
+{
+ // SPDY supports infinite parallelism, so no need to pipeline.
+ if (mUsingSpdyVersion)
+ return false;
+
+ // assuming connection is HTTP/1.1 with keep-alive enabled
+ if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingConnect()) {
+ // XXX check for bad proxy servers...
+ return true;
+ }
+
+ // check for bad origin servers
+ nsAutoCString val;
+ responseHead->GetHeader(nsHttp::Server, val);
+
+ // If there is no server header we will assume it should not be banned
+ // as facebook and some other prominent sites do this
+ if (val.IsEmpty())
+ return true;
+
+ // The blacklist is indexed by the first character. All of these servers are
+ // known to return their identifier as the first thing in the server string,
+ // so we can do a leading match.
+
+ static const char *bad_servers[26][6] = {
+ { nullptr }, { nullptr }, { nullptr }, { nullptr }, // a - d
+ { "EFAServer/", nullptr }, // e
+ { nullptr }, { nullptr }, { nullptr }, { nullptr }, // f - i
+ { nullptr }, { nullptr }, { nullptr }, // j - l
+ { "Microsoft-IIS/4.", "Microsoft-IIS/5.", nullptr }, // m
+ { "Netscape-Enterprise/3.", "Netscape-Enterprise/4.",
+ "Netscape-Enterprise/5.", "Netscape-Enterprise/6.", nullptr }, // n
+ { nullptr }, { nullptr }, { nullptr }, { nullptr }, // o - r
+ { nullptr }, { nullptr }, { nullptr }, { nullptr }, // s - v
+ { "WebLogic 3.", "WebLogic 4.","WebLogic 5.", "WebLogic 6.",
+ "Winstone Servlet Engine v0.", nullptr }, // w
+ { nullptr }, { nullptr }, { nullptr } // x - z
+ };
+
+ int index = val.get()[0] - 'A'; // the whole table begins with capital letters
+ if ((index >= 0) && (index <= 25))
+ {
+ for (int i = 0; bad_servers[index][i] != nullptr; i++) {
+ if (val.Equals(bad_servers[index][i])) {
+ LOG(("looks like this server does not support pipelining"));
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedBannedServer, this , 0);
+ return false;
+ }
+ }
+ }
+
+ // ok, let's allow pipelining to this server
+ return true;
+}
+
+//----------------------------------------------------------------------------
+// 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(PR_GetCurrentThread() == gSocketThread);
+ NS_ENSURE_ARG_POINTER(trans);
+ MOZ_ASSERT(responseHead, "No response head?");
+
+ if (mInSpdyTunnel) {
+ responseHead->SetHeader(nsHttp::X_Firefox_Spdy_Proxy,
+ NS_LITERAL_CSTRING("true"));
+ }
+
+ // 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;
+ }
+
+ // reset to default (the server may have changed since we last checked)
+ mSupportsPipelining = false;
+
+ if ((responseHead->Version() < NS_HTTP_VERSION_1_1) ||
+ (requestHead->Version() < NS_HTTP_VERSION_1_1)) {
+ // HTTP/1.0 connections are by default NOT persistent
+ if (explicitKeepAlive)
+ mKeepAlive = true;
+ else
+ mKeepAlive = false;
+
+ // We need at least version 1.1 to use pipelines
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedVersionTooLow, this, 0);
+ }
+ else {
+ // HTTP/1.1 connections are by default persistent
+ if (explicitClose) {
+ mKeepAlive = false;
+
+ // persistent connections are required for pipelining to work - if
+ // this close was not pre-announced then generate the negative
+ // BadExplicitClose feedback
+ if (mRemainingConnectionUses > 1)
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadExplicitClose, this, 0);
+ }
+ else {
+ mKeepAlive = true;
+
+ // Do not support pipelining when we are establishing
+ // an SSL tunnel though an HTTP proxy. Pipelining support
+ // determination must be based on comunication with the
+ // target server in this case. See bug 422016 for futher
+ // details.
+ if (!mProxyConnectStream)
+ mSupportsPipelining = SupportsPipelining(responseHead);
+ }
+ }
+ mKeepAliveMask = mKeepAlive;
+
+ // Update the pipelining status in the connection info object
+ // and also read it back. It is possible the ci status is
+ // locked to false if pipelining has been banned on this ci due to
+ // some kind of observed flaky behavior
+ if (mSupportsPipelining) {
+ // report the pipelining-compatible header to the connection manager
+ // as positive feedback. This will undo 1 penalty point the host
+ // may have accumulated in the past.
+
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::NeutralExpectedOK, this, 0);
+
+ mSupportsPipelining =
+ gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
+ }
+
+ // If this connection is reserved for revalidations and we are
+ // receiving a document that failed revalidation then switch the
+ // classification to general to avoid pipelining more revalidations behind
+ // it.
+ if (mClassification == nsAHttpTransaction::CLASS_REVALIDATION &&
+ responseStatus != 304) {
+ mClassification = nsAHttpTransaction::CLASS_GENERAL;
+ }
+
+ // 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;
+ responseHead->GetHeader(nsHttp::Keep_Alive, keepAlive);
+
+ if (!mUsingSpdyVersion) {
+ const char *cp = PL_strcasestr(keepAlive.get(), "timeout=");
+ if (cp)
+ mIdleTimeout = PR_SecondsToInterval((uint32_t) atoi(cp + 8));
+ else
+ mIdleTimeout = gHttpHandler->IdleTimeout();
+
+ cp = PL_strcasestr(keepAlive.get(), "max=");
+ if (cp) {
+ int maxUses = atoi(cp + 4);
+ if (maxUses > 0) {
+ foundKeepAliveMax = true;
+ mRemainingConnectionUses = static_cast<uint32_t>(maxUses);
+ }
+ }
+ }
+ else {
+ mIdleTimeout = gHttpHandler->SpdyTimeout();
+ }
+
+ LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n",
+ this, PR_IntervalToSeconds(mIdleTimeout)));
+ }
+
+ if (!foundKeepAliveMax && mRemainingConnectionUses && !mUsingSpdyVersion)
+ --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.
+ if (mProxyConnectStream) {
+ MOZ_ASSERT(!mUsingSpdyVersion,
+ "SPDY NPN Complete while using proxy connect stream");
+ mProxyConnectStream = nullptr;
+ bool isHttps =
+ mTransaction ? mTransaction->ConnectionInfo()->EndToEndSSL() :
+ mConnInfo->EndToEndSSL();
+
+ if (responseStatus == 200) {
+ LOG(("proxy CONNECT succeeded! endtoendssl=%d\n", isHttps));
+ *reset = true;
+ nsresult rv;
+ if (isHttps) {
+ if (mConnInfo->UsingHttpsProxy()) {
+ LOG(("%p new TLSFilterTransaction %s %d\n",
+ this, mConnInfo->Origin(), mConnInfo->OriginPort()));
+ SetupSecondaryTLS();
+ }
+
+ rv = InitSSLParams(false, true);
+ LOG(("InitSSLParams [rv=%x]\n", rv));
+ }
+ 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\n", isHttps));
+ 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.
+ if (hasUpgradeReq && responseStatus != 401 && responseStatus != 407) {
+ LOG(("HTTP Upgrade in play - disable keepalive\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)
+ 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%x]", this, 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(PR_GetCurrentThread() == gSocketThread);
+
+ // 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);
+ }
+
+ if (!gHttpHandler->GetPipelineRescheduleOnTimeout())
+ return nextTickAfter;
+
+ PRIntervalTime delta = now - mLastReadTime;
+
+ // we replicate some of the checks both here and in OnSocketReadable() as
+ // they will be discovered under different conditions. The ones here
+ // will generally be discovered if we are totally hung and OSR does
+ // not get called at all, however OSR discovers them with lower latency
+ // if the issue is just very slow (but not stalled) reading.
+ //
+ // Right now we only take action if pipelining is involved, but this would
+ // be the place to add general read timeout handling if it is desired.
+
+ uint32_t pipelineDepth = mTransaction->PipelineDepth();
+ if (pipelineDepth > 1) {
+ // if we have pipelines outstanding (not just an idle connection)
+ // then get a fairly quick tick
+ nextTickAfter = 1;
+ }
+
+ if (delta >= gHttpHandler->GetPipelineRescheduleTimeout() &&
+ pipelineDepth > 1) {
+
+ // this just reschedules blocked transactions. no transaction
+ // is aborted completely.
+ LOG(("cancelling pipeline due to a %ums stall - depth %d\n",
+ PR_IntervalToMilliseconds(delta), pipelineDepth));
+
+ nsHttpPipeline *pipeline = mTransaction->QueryPipeline();
+ MOZ_ASSERT(pipeline, "pipelinedepth > 1 without pipeline");
+ // code this defensively for the moment and check for null in opt build
+ // This will reschedule blocked members of the pipeline, but the
+ // blocking transaction (i.e. response 0) will not be changed.
+ if (pipeline) {
+ pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT);
+ LOG(("Rescheduling the head of line blocked members of a pipeline "
+ "because reschedule-timeout idle interval exceeded"));
+ }
+ }
+
+ if (delta < gHttpHandler->GetPipelineTimeout())
+ return nextTickAfter;
+
+ if (pipelineDepth <= 1 && !mTransaction->PipelinePosition())
+ return nextTickAfter;
+
+ // nothing has transpired on this pipelined socket for many
+ // seconds. Call that a total stall and close the transaction.
+ // There is a chance the transaction will be restarted again
+ // depending on its state.. that will come back araound
+ // without pipelining on, so this won't loop.
+
+ LOG(("canceling transaction stalled for %ums on a pipeline "
+ "of depth %d and scheduled originally at pos %d\n",
+ PR_IntervalToMilliseconds(delta),
+ pipelineDepth, mTransaction->PipelinePosition()));
+
+ // This will also close the connection
+ CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
+ return UINT32_MAX;
+}
+
+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)) {
+ 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%x]",
+ self, rv));
+ }
+}
+
+void
+nsHttpConnection::GetSecurityInfo(nsISupports **secinfo)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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;
+}
+
+void
+nsHttpConnection::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>(aCallbacks, false);
+}
+
+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;
+}
+
+nsresult
+nsHttpConnection::ResumeSend()
+{
+ LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mSocketOut)
+ return mSocketOut->AsyncWait(this, 0, 0, nullptr);
+
+ NS_NOTREACHED("no socket output stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+nsHttpConnection::ResumeRecv()
+{
+ LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // 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);
+
+ NS_NOTREACHED("no socket input stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+class HttpConnectionForceIO : public Runnable
+{
+public:
+ HttpConnectionForceIO(nsHttpConnection *aConn, bool doRecv)
+ : mConn(aConn)
+ , mDoRecv(doRecv)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mDoRecv) {
+ if (!mConn->mSocketIn)
+ return NS_OK;
+ return mConn->OnInputStreamReady(mConn->mSocketIn);
+ }
+
+ MOZ_ASSERT(mConn->mForceSendPending);
+ mConn->mForceSendPending = false;
+ if (!mConn->mSocketOut) {
+ return NS_OK;
+ }
+ return mConn->OnOutputStreamReady(mConn->mSocketOut);
+ }
+private:
+ RefPtr<nsHttpConnection> mConn;
+ bool mDoRecv;
+};
+
+void
+nsHttpConnection::ForceSendIO(nsITimer *aTimer, void *aClosure)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpConnection *self = static_cast<nsHttpConnection *>(aClosure);
+ MOZ_ASSERT(aTimer == self->mForceSendTimer);
+ self->mForceSendTimer = nullptr;
+ NS_DispatchToCurrentThread(new HttpConnectionForceIO(self, false));
+}
+
+nsresult
+nsHttpConnection::MaybeForceSendIO()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ // 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;
+ mForceSendTimer = do_CreateInstance("@mozilla.org/timer;1");
+ return mForceSendTimer->InitWithFuncCallback(
+ nsHttpConnection::ForceSendIO, this, kForceDelay, nsITimer::TYPE_ONE_SHOT);
+}
+
+// trigger an asynchronous read
+nsresult
+nsHttpConnection::ForceRecv()
+{
+ LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ return NS_DispatchToCurrentThread(new HttpConnectionForceIO(this, true));
+}
+
+// trigger an asynchronous write
+nsresult
+nsHttpConnection::ForceSend()
+{
+ LOG(("nsHttpConnection::ForceSend [this=%p]\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTLSFilter) {
+ return mTLSFilter->NudgeTunnel(this);
+ }
+ return MaybeForceSendIO();
+}
+
+void
+nsHttpConnection::BeginIdleMonitoring()
+{
+ LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mTransaction, "BeginIdleMonitoring() while active");
+ MOZ_ASSERT(!mUsingSpdyVersion, "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(PR_GetCurrentThread() == gSocketThread);
+ 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);
+ }
+}
+
+uint32_t
+nsHttpConnection::Version()
+{
+ return mUsingSpdyVersion ? mUsingSpdyVersion : mLastHttpResponseVersion;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <private>
+//-----------------------------------------------------------------------------
+
+void
+nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason,
+ bool aIsShutdown)
+{
+ LOG(("nsHttpConnection::CloseTransaction[this=%p trans=%p reason=%x]\n",
+ this, trans, reason));
+
+ MOZ_ASSERT((trans == mTransaction) ||
+ (mTLSFilter && mTLSFilter->Transaction() == trans));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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) {
+ DontReuse();
+ // if !mSpdySession then mUsingSpdyVersion must be false for canreuse()
+ mUsingSpdyVersion = 0;
+ mSpdySession = nullptr;
+ }
+
+ if (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)
+{
+ 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;
+
+ do {
+ 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 {
+
+ // 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=%x read=%u sock-cond=%x]\n",
+ this, rv, transactionBytes, mSocketOutCondition));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
+ rv = NS_OK;
+ transactionBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ rv = NS_OK;
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ if (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 (mTransaction && !mWaitingFor0RTTResponse) { // in case the ReadSegments stack called CloseTransaction()
+ //
+ // at this point we've written out the entire transaction, and now we
+ // must wait for the server's response. we manufacture a status message
+ // here to reflect the fact that we are waiting. this message will be
+ // trumped (overwritten) if the server responds quickly.
+ //
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR,
+ 0);
+
+ rv = ResumeRecv(); // start reading
+ }
+ 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
+
+ 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 (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;
+ gHttpHandler->ProcessPendingQ(mConnInfo);
+ }
+
+ // Look for data being sent in bursts with large pauses. If the pauses
+ // are caused by server bottlenecks such as think-time, disk i/o, or
+ // cpu exhaustion (as opposed to network latency) then we generate negative
+ // pipelining feedback to prevent head of line problems
+
+ // 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.
+
+ if (delta > mRtt)
+ delta -= mRtt;
+ else
+ delta = 0;
+
+ static const PRIntervalTime k400ms = PR_MillisecondsToInterval(400);
+
+ if (delta >= (mRtt + gHttpHandler->GetPipelineRescheduleTimeout())) {
+ LOG(("Read delta ms of %u causing slow read major "
+ "event and pipeline cancellation",
+ PR_IntervalToMilliseconds(delta)));
+
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadSlowReadMajor, this, 0);
+
+ if (gHttpHandler->GetPipelineRescheduleOnTimeout() &&
+ mTransaction->PipelineDepth() > 1) {
+ nsHttpPipeline *pipeline = mTransaction->QueryPipeline();
+ MOZ_ASSERT(pipeline, "pipelinedepth > 1 without pipeline");
+ // code this defensively for the moment and check for null
+ // This will reschedule blocked members of the pipeline, but the
+ // blocking transaction (i.e. response 0) will not be changed.
+ if (pipeline) {
+ pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT);
+ LOG(("Rescheduling the head of line blocked members of a "
+ "pipeline because reschedule-timeout idle interval "
+ "exceeded"));
+ }
+ }
+ }
+ else if (delta > k400ms) {
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadSlowReadMinor, this, 0);
+ }
+
+ 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=%x n=%d socketin=%x\n",
+ this, rv, n, mSocketInCondition));
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to take any more data, then
+ // wait for the transaction to call ResumeRecv.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else {
+ mCurrentBytesRead += n;
+ mTotalBytesRead += n;
+ if (NS_FAILED(mSocketInCondition)) {
+ // continue waiting for the socket if necessary...
+ if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = ResumeRecv();
+ } else {
+ rv = mSocketInCondition;
+ }
+ again = false;
+ }
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+void
+nsHttpConnection::SetupSecondaryTLS()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mTLSFilter);
+ LOG(("nsHttpConnection %p SetupSecondaryTLS %s %d\n",
+ this, mConnInfo->Origin(), mConnInfo->OriginPort()));
+
+ nsHttpConnectionInfo *ci = nullptr;
+ if (mTransaction) {
+ ci = mTransaction->ConnectionInfo();
+ }
+ if (!ci) {
+ ci = mConnInfo;
+ }
+ MOZ_ASSERT(ci);
+
+ mTLSFilter = new TLSFilterTransaction(mTransaction,
+ ci->Origin(), ci->OriginPort(), this, this);
+
+ if (mTransaction) {
+ mTransaction = mTLSFilter;
+ }
+}
+
+void
+nsHttpConnection::SetInSpdyTunnel(bool arg)
+{
+ MOZ_ASSERT(mTLSFilter);
+ mInSpdyTunnel = arg;
+
+ // don't setup another tunnel :)
+ mProxyConnectStream = nullptr;
+ mCompletedProxyConnect = true;
+ mProxyConnectInProgress = false;
+}
+
+nsresult
+nsHttpConnection::MakeConnectString(nsAHttpTransaction *trans,
+ nsHttpRequestHead *request,
+ nsACString &result)
+{
+ result.Truncate();
+ if (!trans->ConnectionInfo()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsHttpHandler::GenerateHostPort(
+ nsDependentCString(trans->ConnectionInfo()->Origin()),
+ trans->ConnectionInfo()->OriginPort(), result);
+
+ // CONNECT host:port HTTP/1.1
+ request->SetMethod(NS_LITERAL_CSTRING("CONNECT"));
+ request->SetVersion(gHttpHandler->HttpVersion());
+ request->SetRequestURI(result);
+ request->SetHeader(nsHttp::User_Agent, gHttpHandler->UserAgent());
+
+ // a CONNECT is always persistent
+ request->SetHeader(nsHttp::Proxy_Connection, NS_LITERAL_CSTRING("keep-alive"));
+ request->SetHeader(nsHttp::Connection, NS_LITERAL_CSTRING("keep-alive"));
+
+ // all HTTP/1.1 requests must include a Host header (even though it
+ // may seem redundant in this case; see bug 82388).
+ request->SetHeader(nsHttp::Host, result);
+
+ 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.
+ request->SetHeader(nsHttp::Proxy_Authorization, val);
+ }
+
+ result.Truncate();
+ request->Flatten(result, false);
+ 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,
+ "SPDY NPN Complete while using proxy connect stream");
+
+ nsAutoCString buf;
+ nsHttpRequestHead request;
+ nsresult rv = MakeConnectString(mTransaction, &request, buf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_NewCStringInputStream(getter_AddRefs(mProxyConnectStream), buf);
+}
+
+nsresult
+nsHttpConnection::StartShortLivedTCPKeepalives()
+{
+ if (mUsingSpdyVersion) {
+ 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 =
+ do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ 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->InitWithFuncCallback(
+ nsHttpConnection::UpdateTCPKeepalive,
+ this,
+ (uint32_t)time*1000,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("nsHttpConnection::StartShortLivedTCPKeepalives failed to "
+ "create timer.");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpConnection::StartLongLivedTCPKeepalives()
+{
+ MOZ_ASSERT(!mUsingSpdyVersion, "Don't use TCP Keepalive with SPDY!");
+ if (NS_WARN_IF(mUsingSpdyVersion)) {
+ 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_ISUPPORTS(nsHttpConnection,
+ nsIInputStreamCallback,
+ nsIOutputStreamCallback,
+ nsITransportEventSink,
+ nsIInterfaceRequestor)
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIInputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket transport thread
+NS_IMETHODIMP
+nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream *in)
+{
+ MOZ_ASSERT(in == mSocketIn, "unexpected stream");
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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));
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() != gSocketThread);
+
+ 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;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h
new file mode 100644
index 0000000000..783b080b32
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -0,0 +1,378 @@
+/* -*- 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 nsHttpConnection_h__
+#define nsHttpConnection_h__
+
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "TunnelUtils.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.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;
+
+//-----------------------------------------------------------------------------
+// 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 nsAHttpSegmentReader
+ , public nsAHttpSegmentWriter
+ , public nsIInputStreamCallback
+ , public nsIOutputStreamCallback
+ , public nsITransportEventSink
+ , public nsIInterfaceRequestor
+ , public NudgeTunnelCallback
+ , public ARefBase
+{
+ virtual ~nsHttpConnection();
+
+public:
+ 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();
+
+ // 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.
+ nsresult Init(nsHttpConnectionInfo *info, uint16_t maxHangTime,
+ nsISocketTransport *, nsIAsyncInputStream *,
+ nsIAsyncOutputStream *, bool connectedTransport,
+ nsIInterfaceRequestor *, PRIntervalTime);
+
+ // 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
+ nsresult Activate(nsAHttpTransaction *, uint32_t caps, int32_t pri);
+
+ // Close the underlying socket transport.
+ void Close(nsresult reason, bool aIsShutdown = false);
+
+ //-------------------------------------------------------------------------
+ // XXX document when these are ok to call
+
+ bool SupportsPipelining();
+ bool IsKeepAlive()
+ {
+ return mUsingSpdyVersion || (mKeepAliveMask && mKeepAlive);
+ }
+ bool CanReuse(); // can this connection be reused?
+ bool CanDirectlyActivate();
+
+ // Returns time in seconds for how long connection can be reused.
+ uint32_t TimeToLive();
+
+ void DontReuse();
+
+ bool IsProxyConnectInProgress()
+ {
+ return mProxyConnectInProgress;
+ }
+
+ bool LastTransactionExpectedNoContent()
+ {
+ return mLastTransactionExpectedNoContent;
+ }
+
+ void SetLastTransactionExpectedNoContent(bool val)
+ {
+ mLastTransactionExpectedNoContent = val;
+ }
+
+ 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;
+ }
+
+ nsISocketTransport *Transport() { return mSocketTransport; }
+ nsAHttpTransaction *Transaction() { return mTransaction; }
+ nsHttpConnectionInfo *ConnectionInfo() { return mConnInfo; }
+
+ // nsAHttpConnection compatible methods (non-virtual):
+ nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset);
+ void CloseTransaction(nsAHttpTransaction *, nsresult reason, bool aIsShutdown = false);
+ void GetConnectionInfo(nsHttpConnectionInfo **ci) { NS_IF_ADDREF(*ci = mConnInfo); }
+ nsresult TakeTransport(nsISocketTransport **,
+ nsIAsyncInputStream **,
+ nsIAsyncOutputStream **);
+ void GetSecurityInfo(nsISupports **);
+ bool IsPersistent() { return IsKeepAlive() && !mDontReuse; }
+ bool IsReused();
+ void SetIsReusedAfter(uint32_t afterMilliseconds);
+ nsresult PushBack(const char *data, uint32_t length);
+ nsresult ResumeSend();
+ nsresult ResumeRecv();
+ int64_t MaxBytesRead() {return mMaxBytesRead;}
+ uint8_t GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }
+
+ friend class HttpConnectionForceIO;
+ nsresult ForceSend();
+ nsresult ForceRecv();
+
+ 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() { return !!mUsingSpdyVersion; }
+ uint8_t GetSpdyVersion() { return mUsingSpdyVersion; }
+ bool EverUsedSpdy() { return mEverUsedSpdy; }
+ PRIntervalTime Rtt() { return mRtt; }
+
+ // 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);
+
+ nsAHttpTransaction::Classifier Classification() { return mClassification; }
+ void Classify(nsAHttpTransaction::Classifier newclass)
+ {
+ mClassification = newclass;
+ }
+
+ // When the connection is active this is called every second
+ void ReadTimeoutTick();
+
+ int64_t BytesWritten() { return mTotalBytesWritten; } // includes TLS
+ int64_t ContentBytesWritten() { return mContentBytesWritten; }
+
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
+ void PrintDiagnostics(nsCString &log);
+
+ void SetTransactionCaps(uint32_t aCaps) { mTransactionCaps = aCaps; }
+
+ // IsExperienced() returns true when the connection has started at least one
+ // non null HTTP transaction of any version.
+ bool IsExperienced() { return mExperienced; }
+
+ static nsresult MakeConnectString(nsAHttpTransaction *trans,
+ nsHttpRequestHead *request,
+ nsACString &result);
+ void SetupSecondaryTLS();
+ 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));
+ }
+ // override of nsAHttpConnection
+ virtual uint32_t Version();
+
+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
+ nsresult InitSSLParams(bool connectingToProxy, bool ProxyStartSSL);
+ nsresult SetupNPNList(nsISSLSocketControl *ssl, uint32_t caps);
+
+ nsresult OnTransactionDone(nsresult reason);
+ nsresult OnSocketWritable();
+ nsresult OnSocketReadable();
+
+ nsresult SetupProxyConnect();
+
+ PRIntervalTime IdleTime();
+ bool IsAlive();
+ bool SupportsPipelining(nsHttpResponseHead *);
+
+ // Makes certain the SSL handshake is complete and NPN negotiation
+ // has had a chance to happen
+ bool EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
+ uint32_t &aOut0RTTBytesWritten);
+ void SetupSSL();
+
+ // Start the Spdy transaction handler when NPN indicates spdy/*
+ void StartSpdy(uint8_t versionLevel);
+
+ // Directly Add a transaction to an active connection for SPDY
+ 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.
+ nsresult StartShortLivedTCPKeepalives();
+ nsresult StartLongLivedTCPKeepalives();
+ nsresult DisableTCPKeepalives();
+
+private:
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsresult mSocketInCondition;
+ nsresult mSocketOutCondition;
+
+ nsCOMPtr<nsIInputStream> mProxyConnectStream;
+ nsCOMPtr<nsIInputStream> mRequestStream;
+
+ // mTransaction only points to the HTTP Transaction callbacks if the
+ // transaction is open, otherwise it is null.
+ RefPtr<nsAHttpTransaction> mTransaction;
+ RefPtr<TLSFilterTransaction> mTLSFilter;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ Mutex mCallbacksLock;
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mCallbacks;
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ 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 mTotalBytesWritten; // does not include CONNECT tunnel
+ int64_t mContentBytesWritten; // does not include CONNECT tunnel or TLS
+
+ RefPtr<nsIAsyncInputStream> mInputOverflow;
+
+ PRIntervalTime mRtt;
+
+ bool mConnectedTransport;
+ bool mKeepAlive;
+ bool mKeepAliveMask;
+ bool mDontReuse;
+ bool mSupportsPipelining;
+ bool mIsReused;
+ bool mCompletedProxyConnect;
+ bool mLastTransactionExpectedNoContent;
+ bool mIdleMonitoring;
+ bool mProxyConnectInProgress;
+ bool mExperienced;
+ 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;
+
+ nsAHttpTransaction::Classifier mClassification;
+
+ // SPDY related
+ bool mNPNComplete;
+ bool mSetupSSLCalled;
+
+ // version level in use, 0 if unused
+ uint8_t 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.
+ uint8_t mLastHttpResponseVersion;
+
+ // The capabailities associated with the most recent transaction
+ uint32_t mTransactionCaps;
+
+ 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);
+ 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
+};
+
+} // 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..e965fd1cc2
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -0,0 +1,339 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 "prnetdb.h"
+#include "nsICryptoHash.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIProtocolProxyService.h"
+
+static nsresult
+SHA256(const char* aPlainText, nsAutoCString& aResult)
+{
+ static nsICryptoHash* hasher = nullptr;
+ nsresult rv;
+ if (!hasher) {
+ rv = CallCreateInstance("@mozilla.org/security/hash;1", &hasher);
+ 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);
+
+ rv = hasher->Finish(false, aResult);
+ return rv;
+}
+
+namespace mozilla {
+namespace net {
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo *proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ bool endToEndSSL)
+ : mRoutedPort(443)
+{
+ Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, endToEndSSL);
+}
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo *proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ const nsACString &routedHost,
+ int32_t routedPort)
+{
+ mEndToEndSSL = true; // so DefaultPort() works
+ mRoutedPort = routedPort == -1 ? DefaultPort() : routedPort;
+
+ if (!originHost.Equals(routedHost) || (originPort != routedPort)) {
+ mRoutedHost = routedHost;
+ }
+ Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, true);
+}
+
+void
+nsHttpConnectionInfo::Init(const nsACString &host, int32_t port,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo* proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ bool e2eSSL)
+{
+ LOG(("Init nsHttpConnectionInfo @%p\n", this));
+
+ mUsername = username;
+ mProxyInfo = proxyInfo;
+ mEndToEndSSL = e2eSSL;
+ mUsingConnect = false;
+ mNPNToken = npnToken;
+ mOriginAttributes = originAttributes;
+
+ 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::SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId)
+{
+ mNetworkInterfaceId = aNetworkInterfaceId;
+ BuildHashKey();
+}
+
+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
+
+ mHashKey.AssignLiteral(".......");
+ mHashKey.Append(keyHost);
+ if (!mNetworkInterfaceId.IsEmpty()) {
+ mHashKey.Append('(');
+ mHashKey.Append(mNetworkInterfaceId);
+ mHashKey.Append(')');
+ }
+ 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("}");
+ }
+
+ nsAutoCString originAttributes;
+ mOriginAttributes.CreateSuffix(originAttributes);
+ mHashKey.Append(originAttributes);
+}
+
+void
+nsHttpConnectionInfo::SetOriginServer(const nsACString &host, int32_t port)
+{
+ mOrigin = host;
+ mOriginPort = port == -1 ? DefaultPort() : port;
+ BuildHashKey();
+}
+
+nsHttpConnectionInfo*
+nsHttpConnectionInfo::Clone() const
+{
+ nsHttpConnectionInfo *clone;
+ if (mRoutedHost.IsEmpty()) {
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername, mProxyInfo,
+ mOriginAttributes, mEndToEndSSL);
+ } else {
+ MOZ_ASSERT(mEndToEndSSL);
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername, mProxyInfo,
+ mOriginAttributes, mRoutedHost, mRoutedPort);
+ }
+
+ if (!mNetworkInterfaceId.IsEmpty()) {
+ clone->SetNetworkInterfaceId(mNetworkInterfaceId);
+ }
+
+ // 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());
+ MOZ_ASSERT(clone->Equals(this));
+
+ return clone;
+}
+
+void
+nsHttpConnectionInfo::CloneAsDirectRoute(nsHttpConnectionInfo **outCI)
+{
+ if (mRoutedHost.IsEmpty()) {
+ *outCI = Clone();
+ return;
+ }
+
+ RefPtr<nsHttpConnectionInfo> clone =
+ new nsHttpConnectionInfo(mOrigin, mOriginPort,
+ EmptyCString(), mUsername, mProxyInfo,
+ mOriginAttributes, mEndToEndSSL);
+ // 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());
+ if (!mNetworkInterfaceId.IsEmpty()) {
+ clone->SetNetworkInterfaceId(mNetworkInterfaceId);
+ }
+ 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_LITERAL_CSTRING("*"), 0,
+ mNPNToken, mUsername, mProxyInfo,
+ mOriginAttributes, true);
+ // Make sure the anonymous and private flags are transferred!
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone.forget(outParam);
+ return NS_OK;
+}
+
+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;
+ PRNetAddrToNetAddr(&prAddr, &netAddr);
+ return IsIPAddrLocal(&netAddr);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h
new file mode 100644
index 0000000000..9c5d29d728
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 "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.
+
+namespace mozilla { namespace net {
+
+extern LazyLogModule gHttpLog;
+
+class nsHttpConnectionInfo: public ARefBase
+{
+public:
+ nsHttpConnectionInfo(const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo *proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ bool endToEndSSL = false);
+
+ // this version must use TLS and you may supply separate
+ // connection (aka routing) information than the authenticated
+ // origin information
+ nsHttpConnectionInfo(const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo *proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ const nsACString &routedHost,
+ int32_t routedPort);
+
+private:
+ virtual ~nsHttpConnectionInfo()
+ {
+ MOZ_LOG(gHttpLog, LogLevel::Debug, ("Destroying nsHttpConnectionInfo @%x\n", this));
+ }
+
+ void BuildHashKey();
+
+public:
+ const nsAFlatCString &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; }
+
+ // With overhead rebuilding the hash key. The initial
+ // network interface is empty. So you can reduce one call
+ // if there's no explicit route after ctor.
+ void SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId);
+
+ // OK to treat these as an infalible allocation
+ nsHttpConnectionInfo* Clone() const;
+ void CloneAsDirectRoute(nsHttpConnectionInfo **outParam);
+ 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; }
+
+ // 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'; }
+
+ const nsCString &GetNetworkInterfaceId() const { return mNetworkInterfaceId; }
+
+ const nsCString &GetNPNToken() { return mNPNToken; }
+ const nsCString &GetUsername() { return mUsername; }
+
+ const NeckoOriginAttributes &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;
+
+private:
+ void Init(const nsACString &host,
+ int32_t port,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo* proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ bool EndToEndSSL);
+ void SetOriginServer(const nsACString &host, int32_t port);
+
+ nsCString mOrigin;
+ int32_t mOriginPort;
+ nsCString mRoutedHost;
+ int32_t mRoutedPort;
+
+ nsCString mHashKey;
+ nsCString mNetworkInterfaceId;
+ nsCString mUsername;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ bool mUsingHttpProxy;
+ bool mUsingHttpsProxy;
+ bool mEndToEndSSL;
+ bool mUsingConnect; // if will use CONNECT with http proxy
+ nsCString mNPNToken;
+ NeckoOriginAttributes mOriginAttributes;
+
+// for RefPtr
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo)
+};
+
+} // 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..abae51e2f3
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -0,0 +1,4006 @@
+/* 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"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpConnection.h"
+#include "nsHttpPipeline.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsNetCID.h"
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "mozilla/net/DNS.h"
+#include "nsISocketTransport.h"
+#include "nsISSLSocketControl.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "NullHttpTransaction.h"
+#include "nsIDNSRecord.h"
+#include "nsITransport.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsIRequestContext.h"
+#include "nsISocketTransportService.h"
+#include <algorithm>
+#include "mozilla/ChaosMode.h"
+#include "mozilla/Unused.h"
+#include "nsIURI.h"
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver)
+
+static void
+InsertTransactionSorted(nsTArray<RefPtr<nsHttpTransaction> > &pendingQ, nsHttpTransaction *trans)
+{
+ // insert into queue with smallest valued number first. search in reverse
+ // order under the assumption that many of the existing transactions will
+ // have the same priority (usually 0).
+
+ for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) {
+ nsHttpTransaction *t = pendingQ[i];
+ if (trans->Priority() >= t->Priority()) {
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
+ int32_t samePriorityCount;
+ for (samePriorityCount = 0; i - samePriorityCount >= 0; ++samePriorityCount) {
+ if (pendingQ[i - samePriorityCount]->Priority() != trans->Priority()) {
+ break;
+ }
+ }
+ // skip over 0...all of the elements with the same priority.
+ i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1);
+ }
+ pendingQ.InsertElementAt(i+1, trans);
+ return;
+ }
+ }
+ pendingQ.InsertElementAt(0, trans);
+}
+
+//-----------------------------------------------------------------------------
+
+nsHttpConnectionMgr::nsHttpConnectionMgr()
+ : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
+ , mMaxConns(0)
+ , mMaxPersistConnsPerHost(0)
+ , mMaxPersistConnsPerProxy(0)
+ , mIsShuttingDown(false)
+ , mNumActiveConns(0)
+ , mNumIdleConns(0)
+ , mNumSpdyActiveConns(0)
+ , mNumHalfOpenConns(0)
+ , mTimeOfNextWakeUp(UINT64_MAX)
+ , mPruningNoTraffic(false)
+ , mTimeoutTickArmed(false)
+ , mTimeoutTickNext(1)
+{
+ LOG(("Creating nsHttpConnectionMgr @%p\n", this));
+}
+
+nsHttpConnectionMgr::~nsHttpConnectionMgr()
+{
+ LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
+ if (mTimeoutTick)
+ mTimeoutTick->Cancel();
+}
+
+nsresult
+nsHttpConnectionMgr::EnsureSocketThreadTarget()
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv))
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already initialized or if we've shut down
+ if (mSocketThreadTarget || mIsShuttingDown)
+ return NS_OK;
+
+ mSocketThreadTarget = sts;
+
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::Init(uint16_t maxConns,
+ uint16_t maxPersistConnsPerHost,
+ uint16_t maxPersistConnsPerProxy,
+ uint16_t maxRequestDelay,
+ uint16_t maxPipelinedRequests,
+ uint16_t maxOptimisticPipelinedRequests)
+{
+ LOG(("nsHttpConnectionMgr::Init\n"));
+
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ mMaxConns = maxConns;
+ mMaxPersistConnsPerHost = maxPersistConnsPerHost;
+ mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
+ mMaxRequestDelay = maxRequestDelay;
+ mMaxPipelinedRequests = maxPipelinedRequests;
+ mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests;
+
+ mIsShuttingDown = false;
+ }
+
+ return EnsureSocketThreadTarget();
+}
+
+class BoolWrapper : public ARefBase
+{
+public:
+ BoolWrapper() : mBool(false) {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper)
+
+public: // intentional!
+ bool mBool;
+
+private:
+ virtual ~BoolWrapper() {}
+};
+
+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
+ while (!shutdownWrapper->mBool) {
+ NS_ProcessNextEvent(NS_GetCurrentThread());
+ }
+
+ return NS_OK;
+}
+
+class ConnEvent : public Runnable
+{
+public:
+ ConnEvent(nsHttpConnectionMgr *mgr,
+ nsConnEventHandler handler, int32_t iparam, ARefBase *vparam)
+ : mMgr(mgr)
+ , mHandler(handler)
+ , mIParam(iparam)
+ , mVParam(vparam) {}
+
+ NS_IMETHOD Run() override
+ {
+ (mMgr->*mHandler)(mIParam, mVParam);
+ return NS_OK;
+ }
+
+private:
+ virtual ~ConnEvent() {}
+
+ RefPtr<nsHttpConnectionMgr> mMgr;
+ nsConnEventHandler mHandler;
+ int32_t mIParam;
+ RefPtr<ARefBase> mVParam;
+};
+
+nsresult
+nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
+ int32_t iparam, ARefBase *vparam)
+{
+ 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 = do_CreateInstance("@mozilla.org/timer;1");
+
+ // 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) {
+ PruneDeadConnections();
+ }
+ else if (timer == mTimeoutTick) {
+ TimeoutTick();
+ } else if (timer == mTrafficTimer) {
+ PruneNoTraffic();
+ }
+ else {
+ MOZ_ASSERT(false, "unexpected timer-callback");
+ LOG(("Unexpected timer object\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpConnectionMgr::AddTransaction(nsHttpTransaction *trans, int32_t priority)
+{
+ LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans);
+}
+
+nsresult
+nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
+{
+ LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans, priority));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
+}
+
+nsresult
+nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
+{
+ LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%x]\n", trans, reason));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
+ static_cast<int32_t>(reason), trans);
+}
+
+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(nsHttpConnectionInfo *aCI)
+{
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
+ 0, aCI);
+}
+
+class SpeculativeConnectArgs : public ARefBase
+{
+public:
+ SpeculativeConnectArgs() { mOverridesOK = false; }
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs)
+
+public: // intentional!
+ RefPtr<NullHttpTransaction> mTrans;
+
+ bool mOverridesOK;
+ uint32_t mParallelSpeculativeConnectLimit;
+ bool mIgnoreIdle;
+ bool mIsFromPredictor;
+ bool mAllow1918;
+
+private:
+ virtual ~SpeculativeConnectArgs() {}
+ NS_DECL_OWNINGTHREAD
+};
+
+nsresult
+nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps,
+ NullHttpTransaction *nullTransaction)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!");
+
+ if (!IsNeckoChild()) {
+ // 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 =
+ nullTransaction ? nullTransaction : new NullHttpTransaction(ci, wrappedCallbacks, caps);
+
+ if (overrider) {
+ args->mOverridesOK = true;
+ args->mParallelSpeculativeConnectLimit =
+ overrider->GetParallelSpeculativeConnectLimit();
+ args->mIgnoreIdle = overrider->GetIgnoreIdle();
+ args->mIsFromPredictor = overrider->GetIsFromPredictor();
+ args->mAllow1918 = overrider->GetAllow1918();
+ }
+
+ return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
+}
+
+nsresult
+nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
+{
+ EnsureSocketThreadTarget();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
+ temp.forget(target);
+ return NS_OK;
+}
+
+nsresult
+nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
+{
+ LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
+}
+
+// A structure used to marshall 2 pointers across the various necessary
+// threads to complete an HTTP upgrade.
+class nsCompleteUpgradeData : public ARefBase
+{
+public:
+ nsCompleteUpgradeData(nsAHttpConnection *aConn,
+ nsIHttpUpgradeListener *aListener)
+ : mConn(aConn)
+ , mUpgradeListener(aListener) { }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData)
+
+ RefPtr<nsAHttpConnection> mConn;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+private:
+ virtual ~nsCompleteUpgradeData() { }
+};
+
+nsresult
+nsHttpConnectionMgr::CompleteUpgrade(nsAHttpConnection *aConn,
+ nsIHttpUpgradeListener *aUpgradeListener)
+{
+ RefPtr<nsCompleteUpgradeData> data =
+ new nsCompleteUpgradeData(aConn, aUpgradeListener);
+ 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 *ci)
+{
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
+ 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()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+ if (ent->mIdleConns.Length() == 0 &&
+ ent->mActiveConns.Length() == 0 &&
+ ent->mHalfOpens.Length() == 0 &&
+ ent->mPendingQ.Length() == 0) {
+ iter.Remove();
+ }
+ }
+
+ return NS_OK;
+}
+
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::LookupPreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ nsConnectionEntry *preferred = nullptr;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; !preferred && (i < len); ++i) {
+ preferred = mSpdyPreferredHash.Get(ent->mCoalescingKeys[i]);
+ }
+ return preferred;
+}
+
+void
+nsHttpConnectionMgr::StorePreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ if (ent->mCoalescingKeys.IsEmpty()) {
+ return;
+ }
+
+ ent->mInPreferredHash = true;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ mSpdyPreferredHash.Put(ent->mCoalescingKeys[i], ent);
+ }
+}
+
+void
+nsHttpConnectionMgr::RemovePreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ if (!ent->mInPreferredHash || ent->mCoalescingKeys.IsEmpty()) {
+ return;
+ }
+
+ ent->mInPreferredHash = false;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ mSpdyPreferredHash.Remove(ent->mCoalescingKeys[i]);
+ }
+}
+
+// Given a nsHttpConnectionInfo find the connection entry object that
+// contains either the nshttpconnection or nshttptransaction parameter.
+// Normally this is done by the hashkey lookup of connectioninfo,
+// but if spdy coalescing is in play it might be found in a redirected
+// entry
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci,
+ nsHttpConnection *conn,
+ nsHttpTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (!ci)
+ return nullptr;
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+
+ // If there is no sign of coalescing (or it is disabled) then just
+ // return the primary hash lookup
+ if (!ent || !ent->mUsingSpdy || ent->mCoalescingKeys.IsEmpty())
+ return ent;
+
+ // If there is no preferred coalescing entry for this host (or the
+ // preferred entry is the one that matched the mCT hash lookup) then
+ // there is only option
+ nsConnectionEntry *preferred = LookupPreferredHash(ent);
+ if (!preferred || (preferred == ent))
+ return ent;
+
+ if (conn) {
+ // The connection could be either in preferred or ent. It is most
+ // likely the only active connection in preferred - so start with that.
+ if (preferred->mActiveConns.Contains(conn))
+ return preferred;
+ if (preferred->mIdleConns.Contains(conn))
+ return preferred;
+ }
+
+ if (trans && preferred->mPendingQ.Contains(trans))
+ return preferred;
+
+ // Neither conn nor trans found in preferred, use the default entry
+ return ent;
+}
+
+nsresult
+nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p",
+ this, conn));
+
+ if (!conn->ConnectionInfo())
+ return NS_ERROR_UNEXPECTED;
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, nullptr);
+
+ RefPtr<nsHttpConnection> deleteProtector(conn);
+ if (!ent || !ent->mIdleConns.RemoveElement(conn))
+ return NS_ERROR_UNEXPECTED;
+
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ ConditionallyStopPruneDeadConnectionsTimer();
+ return NS_OK;
+}
+
+// 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 preliminary preferred host
+// entries used for de-sharding hostsnames.
+void
+nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
+ bool usingSpdy)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, nullptr);
+
+ if (!ent)
+ return;
+
+ if (!usingSpdy)
+ return;
+
+ ent->mUsingSpdy = true;
+ mNumSpdyActiveConns++;
+
+ uint32_t ttl = conn->TimeToLive();
+ uint64_t timeOfExpire = NowInSeconds() + ttl;
+ if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
+ PruneDeadConnectionsAfter(ttl);
+
+ // Lookup preferred directly from the hash instead of using
+ // GetSpdyPreferredEnt() because we want to avoid the cert compatibility
+ // check at this point because the cert is never part of the hash
+ // lookup. Filtering on that has to be done at the time of use
+ // rather than the time of registration (i.e. now).
+ nsConnectionEntry *joinedConnection;
+ nsConnectionEntry *preferred = LookupPreferredHash(ent);
+
+ LOG(("ReportSpdyConnection %p,%s conn %p prefers %p,%s\n",
+ ent, ent->mConnInfo->Origin(), conn, preferred,
+ preferred ? preferred->mConnInfo->Origin() : ""));
+
+ if (!preferred) {
+ // this becomes the preferred entry
+ StorePreferredHash(ent);
+ preferred = ent;
+ } else if ((preferred != ent) &&
+ (joinedConnection = GetSpdyPreferredEnt(ent)) &&
+ (joinedConnection != ent)) {
+ //
+ // A connection entry (e.g. made with a different hostname) with
+ // the same IP address is preferred for future transactions over this
+ // connection entry. Gracefully close down the connection to help
+ // new transactions migrate over.
+
+ LOG(("ReportSpdyConnection graceful close of conn=%p ent=%p to "
+ "migrate to preferred (desharding)\n", conn, ent));
+ conn->DontReuse();
+ } else if (preferred != ent) {
+ LOG (("ReportSpdyConnection preferred host may be in false start or "
+ "may have insufficient cert. Leave mapping in place but do not "
+ "abandon this connection yet."));
+ }
+
+ if ((preferred == ent) && conn->CanDirectlyActivate()) {
+ // this is a new spdy connection to the preferred entry
+
+ // Cancel any other pending connections - their associated transactions
+ // are in the pending queue and will be dispatched onto this connection
+ for (int32_t index = ent->mHalfOpens.Length() - 1;
+ index >= 0; --index) {
+ LOG(("ReportSpdyConnection forcing halfopen abandon %p\n",
+ ent->mHalfOpens[index]));
+ ent->mHalfOpens[index]->Abandon();
+ }
+
+ if (ent->mActiveConns.Length() > 1) {
+ // this is a new connection to an established preferred spdy host.
+ // if there is more than 1 live and established spdy connection (e.g.
+ // some could still be handshaking, shutting down, etc..) then close
+ // this one down after any transactions that are on it are complete.
+ // This probably happened due to the parallel connection algorithm
+ // that is used only before the host is known to speak spdy.
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ nsHttpConnection *otherConn = ent->mActiveConns[index];
+ if (otherConn != conn) {
+ LOG(("ReportSpdyConnection shutting down connection (%p) because new "
+ "spdy connection (%p) takes precedence\n", otherConn, conn));
+ otherConn->DontReuse();
+ }
+ }
+ }
+ }
+
+ ProcessPendingQ(ent->mConnInfo);
+ PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
+}
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry)
+{
+ if (!gHttpHandler->IsSpdyEnabled() ||
+ !gHttpHandler->CoalesceSpdy() ||
+ aOriginalEntry->mConnInfo->GetNoSpdy() ||
+ aOriginalEntry->mCoalescingKeys.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsConnectionEntry *preferred = LookupPreferredHash(aOriginalEntry);
+
+ // if there is no redirection no cert validation is required
+ if (preferred == aOriginalEntry)
+ return aOriginalEntry;
+
+ // if there is no preferred host or it is no longer using spdy
+ // then skip pooling
+ if (!preferred || !preferred->mUsingSpdy)
+ return nullptr;
+
+ // if there is not an active spdy session in this entry then
+ // we cannot pool because the cert upon activation may not
+ // be the same as the old one. Active sessions are prohibited
+ // from changing certs.
+
+ nsHttpConnection *activeSpdy = nullptr;
+
+ for (uint32_t index = 0; index < preferred->mActiveConns.Length(); ++index) {
+ if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
+ activeSpdy = preferred->mActiveConns[index];
+ break;
+ }
+ }
+
+ if (!activeSpdy) {
+ // remove the preferred status of this entry if it cannot be
+ // used for pooling.
+ RemovePreferredHash(preferred);
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "preferred host mapping %s to %s removed due to inactivity.\n",
+ aOriginalEntry->mConnInfo->Origin(),
+ preferred->mConnInfo->Origin()));
+
+ return nullptr;
+ }
+
+ // Check that the server cert supports redirection
+ nsresult rv;
+ bool isJoined = false;
+
+ nsCOMPtr<nsISupports> securityInfo;
+ nsCOMPtr<nsISSLSocketControl> sslSocketControl;
+ nsAutoCString negotiatedNPN;
+
+ activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (!securityInfo) {
+ NS_WARNING("cannot obtain spdy security info");
+ return nullptr;
+ }
+
+ sslSocketControl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("sslSocketControl QI Failed");
+ return nullptr;
+ }
+
+ // try all the spdy versions we support.
+ const SpdyInformation *info = gHttpHandler->SpdyInfo();
+ for (uint32_t index = SpdyInformation::kCount;
+ NS_SUCCEEDED(rv) && index > 0; --index) {
+ if (info->ProtocolEnabled(index - 1)) {
+ rv = sslSocketControl->JoinConnection(info->VersionString[index - 1],
+ aOriginalEntry->mConnInfo->GetOrigin(),
+ aOriginalEntry->mConnInfo->OriginPort(),
+ &isJoined);
+ if (NS_SUCCEEDED(rv) && isJoined) {
+ break;
+ }
+ }
+ }
+
+ if (NS_FAILED(rv) || !isJoined) {
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "Host %s cannot be confirmed to be joined "
+ "with %s connections. rv=%x isJoined=%d",
+ preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
+ rv, isJoined));
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, false);
+ return nullptr;
+ }
+
+ // IP pooling confirmed
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "Host %s has cert valid for %s connections, "
+ "so %s will be coalesced with %s",
+ preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
+ aOriginalEntry->mConnInfo->Origin(), preferred->mConnInfo->Origin()));
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, true);
+ return preferred;
+}
+
+//-----------------------------------------------------------------------------
+
+bool
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry "
+ "[ci=%s ent=%p active=%d idle=%d queued=%d]\n",
+ ent->mConnInfo->HashKey().get(), ent, ent->mActiveConns.Length(),
+ ent->mIdleConns.Length(), ent->mPendingQ.Length()));
+
+ ProcessSpdyPendingQ(ent);
+
+ nsHttpTransaction *trans;
+ 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 < ent->mPendingQ.Length(); ) {
+ trans = ent->mPendingQ[i];
+
+ // 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 alreadyHalfOpen = false;
+ for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) {
+ if (ent->mHalfOpens[j]->Transaction() == trans) {
+ alreadyHalfOpen = true;
+ break;
+ }
+ }
+
+ rv = TryDispatchTransaction(ent,
+ alreadyHalfOpen || !!trans->TunnelProvider(),
+ trans);
+ 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 %x\n", rv));
+
+ if (ent->mPendingQ.RemoveElement(trans)) {
+ // trans 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;
+}
+
+bool
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ return ProcessPendingQForEntry(ent, false);
+ return false;
+}
+
+bool
+nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ return ent->SupportsPipelining();
+ return false;
+}
+
+// nsHttpPipelineFeedback used to hold references across events
+
+class nsHttpPipelineFeedback : public ARefBase
+{
+public:
+ nsHttpPipelineFeedback(nsHttpConnectionInfo *ci,
+ nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+ nsHttpConnection *conn, uint32_t data)
+ : mConnInfo(ci)
+ , mConn(conn)
+ , mInfo(info)
+ , mData(data)
+ {
+ }
+
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ RefPtr<nsHttpConnection> mConn;
+ nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo;
+ uint32_t mData;
+private:
+ ~nsHttpPipelineFeedback() {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpPipelineFeedback)
+};
+
+void
+nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci,
+ PipelineFeedbackInfoType info,
+ nsHttpConnection *conn,
+ uint32_t data)
+{
+ if (!ci)
+ return;
+
+ // Post this to the socket thread if we are not running there already
+ if (PR_GetCurrentThread() != gSocketThread) {
+ RefPtr<nsHttpPipelineFeedback> fb =
+ new nsHttpPipelineFeedback(ci, info, conn, data);
+ PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, 0, fb);
+ return;
+ }
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ ent->OnPipelineFeedbackInfo(info, conn, data);
+}
+
+void
+nsHttpConnectionMgr::ReportFailedToProcess(nsIURI *uri)
+{
+ MOZ_ASSERT(uri);
+
+ nsAutoCString host;
+ int32_t port = -1;
+ nsAutoCString username;
+ bool usingSSL = false;
+ bool isHttp = false;
+
+ nsresult rv = uri->SchemeIs("https", &usingSSL);
+ if (NS_SUCCEEDED(rv) && usingSSL)
+ isHttp = true;
+ if (NS_SUCCEEDED(rv) && !isHttp)
+ rv = uri->SchemeIs("http", &isHttp);
+ if (NS_SUCCEEDED(rv))
+ rv = uri->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv))
+ rv = uri->GetPort(&port);
+ if (NS_SUCCEEDED(rv))
+ uri->GetUsername(username);
+ if (NS_FAILED(rv) || !isHttp || host.IsEmpty())
+ return;
+
+ // report the event for all the permutations of anonymous and
+ // private versions of this host
+ RefPtr<nsHttpConnectionInfo> ci =
+ new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr,
+ NeckoOriginAttributes(), usingSSL);
+ ci->SetAnonymous(false);
+ ci->SetPrivate(false);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(false);
+ ci->SetPrivate(true);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(true);
+ ci->SetPrivate(false);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(true);
+ ci->SetPrivate(true);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+}
+
+// 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(nsConnectionEntry *ent, uint32_t caps)
+{
+ nsHttpConnectionInfo *ci = ent->mConnInfo;
+
+ LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n",
+ ci->HashKey().get(), caps));
+
+ // 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;
+ }
+
+ // 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.
+ uint32_t totalCount =
+ ent->mActiveConns.Length() + ent->UnconnectedHalfOpens();
+
+ uint16_t maxPersistConns;
+
+ if (ci->UsingHttpProxy() && !ci->UsingConnect())
+ maxPersistConns = mMaxPersistConnsPerProxy;
+ else
+ maxPersistConns = mMaxPersistConnsPerHost;
+
+ LOG((" connection count = %d, limit %d\n", totalCount, maxPersistConns));
+
+ // use >= just to be safe
+ bool result = (totalCount >= maxPersistConns);
+ LOG((" result: %s", result ? "true" : "false"));
+ return result;
+}
+
+void
+nsHttpConnectionMgr::ClosePersistentConnections(nsConnectionEntry *ent)
+{
+ LOG(("nsHttpConnectionMgr::ClosePersistentConnections [ci=%s]\n",
+ ent->mConnInfo->HashKey().get()));
+ while (ent->mIdleConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ int32_t activeCount = ent->mActiveConns.Length();
+ for (int32_t i=0; i < activeCount; i++)
+ ent->mActiveConns[i]->DontReuse();
+}
+
+bool
+nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // 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 =
+ ent->mConnInfo->FirstHopSSL() && gHttpHandler->IsSpdyEnabled() &&
+ ent->mUsingSpdy && (ent->mHalfOpens.Length() || ent->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 (ent->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 (ent->mUsingSpdy && ent->mActiveConns.Length()) {
+ bool confirmedRestrict = false;
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ nsHttpConnection *conn = ent->mActiveConns[index];
+ if (!conn->ReportedNPN() || conn->CanDirectlyActivate()) {
+ confirmedRestrict = true;
+ break;
+ }
+ }
+ doRestrict = confirmedRestrict;
+ if (!confirmedRestrict) {
+ LOG(("nsHttpConnectionMgr spdy connection restriction to "
+ "%s bypassed.\n", ent->mConnInfo->Origin()));
+ }
+ }
+ return doRestrict;
+}
+
+// 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(nsConnectionEntry *ent,
+ nsHttpTransaction *trans)
+{
+ LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
+ this, ent, trans));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ uint32_t halfOpenLength = ent->mHalfOpens.Length();
+ for (uint32_t i = 0; i < halfOpenLength; i++) {
+ if (ent->mHalfOpens[i]->IsSpeculative()) {
+ // We've found a speculative connection in the half
+ // open list. Remove the speculative bit from it and that
+ // connection can later be used for this transaction
+ // (or another one in the pending queue) - we don't
+ // need to open a new connection here.
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
+ "Found a speculative half open connection\n",
+ ent->mConnInfo->HashKey().get()));
+
+ uint32_t flags;
+ ent->mHalfOpens[i]->SetSpeculative(false);
+ nsISocketTransport *transport = ent->mHalfOpens[i]->SocketTransport();
+ if (transport && NS_SUCCEEDED(transport->GetConnectionFlags(&flags))) {
+ flags &= ~nsISocketTransport::DISABLE_RFC1918;
+ transport->SetConnectionFlags(flags);
+ }
+
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN> usedSpeculativeConn;
+ ++usedSpeculativeConn;
+
+ if (ent->mHalfOpens[i]->IsFromPredictor()) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED> totalPreconnectsUsed;
+ ++totalPreconnectsUsed;
+ }
+
+ // return OK because we have essentially opened a new connection
+ // by converting a speculative half-open to general use
+ return NS_OK;
+ }
+ }
+
+ // 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 = ent->mActiveConns.Length();
+ for (uint32_t i = 0; i < activeLength; i++) {
+ nsAHttpTransaction *activeTrans = ent->mActiveConns[i]->Transaction();
+ NullHttpTransaction *nullTrans = activeTrans ? activeTrans->QueryNullTransaction() : nullptr;
+ if (nullTrans && nullTrans->Claim()) {
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
+ "Claiming a null transaction for later use\n",
+ ent->mConnInfo->HashKey().get()));
+ return NS_OK;
+ }
+ }
+ }
+
+ // 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) &&
+ RestrictConnections(ent)) {
+ 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()) {
+ nsAutoPtr<nsConnectionEntry> &entry = iter.Data();
+ if (!entry->mIdleConns.Length()) {
+ iter.Next();
+ continue;
+ }
+ RefPtr<nsHttpConnection> conn(entry->mIdleConns[0]);
+ entry->mIdleConns.RemoveElementAt(0);
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+ }
+
+ 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()) {
+ nsAutoPtr<nsConnectionEntry> &entry = iter.Data();
+ if (!entry->mUsingSpdy) {
+ continue;
+ }
+
+ for (uint32_t index = 0;
+ index < entry->mActiveConns.Length();
+ ++index) {
+ nsHttpConnection *conn = entry->mActiveConns[index];
+ if (conn->UsingSpdy() && conn->CanReuse()) {
+ conn->DontReuse();
+ // 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, true);
+ 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;
+}
+
+bool
+nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent,
+ nsHttpTransaction *trans,
+ nsHttpTransaction::Classifier classification,
+ uint16_t depthLimit)
+{
+ if (classification == nsAHttpTransaction::CLASS_SOLO)
+ return false;
+
+ uint32_t maxdepth = ent->MaxPipelineDepth(classification);
+ if (maxdepth == 0) {
+ ent->CreditPenalty();
+ maxdepth = ent->MaxPipelineDepth(classification);
+ }
+
+ if (ent->PipelineState() == PS_RED)
+ return false;
+
+ if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection)
+ return false;
+
+ // The maximum depth of a pipeline in yellow is 1 pipeline of
+ // depth 2 for entire CI. When that transaction completes successfully
+ // we transition to green and that expands the allowed depth
+ // to any number of pipelines of up to depth 4. When a transaction
+ // queued at position 3 or deeper succeeds we open it all the way
+ // up to depths limited only by configuration. The staggered start
+ // in green is simply because a successful yellow test of depth=2
+ // might really just be a race condition (i.e. depth=1 from the
+ // server's point of view), while depth=3 is a stronger indicator -
+ // keeping the pipelines to a modest depth during that period limits
+ // the damage if something is going to go wrong.
+
+ maxdepth = std::min<uint32_t>(maxdepth, depthLimit);
+
+ if (maxdepth < 2)
+ return false;
+
+ nsAHttpTransaction *activeTrans;
+
+ nsHttpConnection *bestConn = nullptr;
+ uint32_t activeCount = ent->mActiveConns.Length();
+ uint32_t bestConnLength = 0;
+ uint32_t connLength;
+
+ for (uint32_t i = 0; i < activeCount; ++i) {
+ nsHttpConnection *conn = ent->mActiveConns[i];
+ if (!conn->SupportsPipelining())
+ continue;
+
+ if (conn->Classification() != classification)
+ continue;
+
+ activeTrans = conn->Transaction();
+ if (!activeTrans ||
+ activeTrans->IsDone() ||
+ NS_FAILED(activeTrans->Status()))
+ continue;
+
+ connLength = activeTrans->PipelineDepth();
+
+ if (maxdepth <= connLength)
+ continue;
+
+ if (!bestConn || (connLength < bestConnLength)) {
+ bestConn = conn;
+ bestConnLength = connLength;
+ }
+ }
+
+ if (!bestConn)
+ return false;
+
+ activeTrans = bestConn->Transaction();
+ nsresult rv = activeTrans->AddTransaction(trans);
+ if (NS_FAILED(rv))
+ return false;
+
+ LOG((" scheduling trans %p on pipeline at position %d\n",
+ trans, trans->PipelinePosition()));
+
+ if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1))
+ ent->SetYellowConnection(bestConn);
+
+ if (!trans->GetPendingTime().IsNull()) {
+ if (trans->UsesPipelining())
+ AccumulateTimeDelta(
+ Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
+ trans->GetPendingTime(), TimeStamp::Now());
+ else
+ AccumulateTimeDelta(
+ Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return true;
+}
+
+bool
+nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent,
+ nsHttpTransaction::Classifier classification)
+{
+ // A connection entry is declared to be "under pressure" if most of the
+ // allowed parallel connections are already used up. In that case we want to
+ // favor existing pipelines over more parallelism so as to reserve any
+ // unused parallel connections for types that don't have existing pipelines.
+ //
+ // The definition of connection pressure is a pretty liberal one here - that
+ // is why we are using the more restrictive maxPersist* counters.
+ //
+ // Pipelines are also favored when the requested classification is already
+ // using 3 or more of the connections. Failure to do this could result in
+ // one class (e.g. images) establishing self replenishing queues on all the
+ // connections that would starve the other transaction types.
+
+ int32_t currentConns = ent->mActiveConns.Length();
+ int32_t maxConns =
+ (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) ?
+ mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost;
+
+ // Leave room for at least 3 distinct types to operate concurrently,
+ // this satisfies the typical {html, js/css, img} page.
+ if (currentConns >= (maxConns - 2))
+ return true; /* prefer pipeline */
+
+ int32_t sameClass = 0;
+ for (int32_t i = 0; i < currentConns; ++i)
+ if (classification == ent->mActiveConns[i]->Classification())
+ if (++sameClass == 3)
+ return true; /* prefer pipeline */
+
+ return false; /* normal behavior */
+}
+
+// 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(nsConnectionEntry *ent,
+ bool onlyReusedConnection,
+ nsHttpTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
+ "[trans=%p ci=%p ci=%s caps=%x tunnelprovider=%p onlyreused=%d "
+ "active=%d idle=%d]\n", trans,
+ ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
+ uint32_t(trans->Caps()), trans->TunnelProvider(),
+ onlyReusedConnection, ent->mActiveConns.Length(),
+ ent->mIdleConns.Length()));
+
+ nsHttpTransaction::Classifier classification = trans->Classification();
+ uint32_t caps = trans->Caps();
+
+ // no keep-alive means no pipelines either
+ if (!(caps & NS_HTTP_ALLOW_KEEPALIVE))
+ caps = caps & ~NS_HTTP_ALLOW_PIPELINING;
+
+ // 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
+
+ bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING);
+ RefPtr<nsHttpConnection> unusedSpdyPersistentConnection;
+
+ // step 0
+ // look for existing spdy connection - that's always best because it is
+ // essentially pipelining without head of line blocking
+
+ if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) {
+ RefPtr<nsHttpConnection> conn = GetSpdyPreferredConn(ent);
+ if (conn) {
+ if ((caps & NS_HTTP_ALLOW_KEEPALIVE) || !conn->IsExperienced()) {
+ LOG((" dispatch to spdy: [conn=%p]\n", conn.get()));
+ trans->RemoveDispatchedAsBlocking(); /* just in case */
+ DispatchTransaction(ent, trans, conn);
+ 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
+ if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) {
+ attemptedOptimisticPipeline = true;
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxOptimisticPipelinedRequests)) {
+ LOG((" dispatched step 1 trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // 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 - mNumSpdyActiveConns) <=
+ 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
+ if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+ RefPtr<nsHttpConnection> conn;
+ while (!conn && (ent->mIdleConns.Length() > 0)) {
+ conn = ent->mIdleConns[0];
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+
+ // we check if the connection can be reused before even checking if
+ // it is a "matching" connection.
+ if (!conn->CanReuse()) {
+ LOG((" dropping stale connection: [conn=%p]\n", conn.get()));
+ conn->Close(NS_ERROR_ABORT);
+ conn = nullptr;
+ }
+ else {
+ LOG((" reusing connection [conn=%p]\n", conn.get()));
+ conn->EndIdleMonitoring();
+ }
+
+ // If there are no idle connections left at all, we need to make
+ // sure that we are not pruning dead connections anymore.
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+ if (conn) {
+ // This will update the class of the connection to be the class of
+ // the transaction dispatched on it.
+ AddActiveConn(conn, ent);
+ DispatchTransaction(ent, trans, conn);
+ LOG((" dispatched step 2 (idle) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 3
+ // consider pipelining scripts and revalidations
+ if (!attemptedOptimisticPipeline &&
+ (classification == nsHttpTransaction::CLASS_REVALIDATION ||
+ classification == nsHttpTransaction::CLASS_SCRIPT)) {
+ // Assignation kept here for documentation purpose; Never read after
+ attemptedOptimisticPipeline = true;
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxOptimisticPipelinedRequests)) {
+ LOG((" dispatched step 3 (pipeline) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 4
+ if (!onlyReusedConnection) {
+ nsresult rv = MakeNewConnection(ent, trans);
+ 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 (%x) trans=%p\n", rv, trans));
+ return rv;
+ }
+ } 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
+ if (caps & NS_HTTP_ALLOW_PIPELINING) {
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxPipelinedRequests)) {
+ LOG((" dispatched step 5 trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // 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::DispatchTransaction(nsConnectionEntry *ent,
+ nsHttpTransaction *trans,
+ nsHttpConnection *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]\n",
+ ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority));
+
+ // 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. spdy or pipelines) becomes available.
+ trans->CancelPacing(NS_OK);
+
+ if (conn->UsingSpdy()) {
+ 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()) {
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(conn && !conn->Transaction(),
+ "DispatchTranaction() on non spdy active connection");
+
+ if (!(caps & NS_HTTP_ALLOW_PIPELINING))
+ conn->Classify(nsAHttpTransaction::CLASS_SOLO);
+ else
+ conn->Classify(trans->Classification());
+
+ rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
+
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ if (trans->UsesPipelining())
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
+ trans->GetPendingTime(), TimeStamp::Now());
+ else
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// 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 (or pipeline) 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(nsHttpConnection *conn) : mConn(conn) { }
+ void Reset() { mConn = nullptr; }
+private:
+ virtual ~ConnectionHandle();
+ RefPtr<nsHttpConnection> mConn;
+};
+
+nsAHttpConnection *
+nsHttpConnectionMgr::MakeConnectionHandle(nsHttpConnection *aWrapped)
+{
+ return new ConnectionHandle(aWrapped);
+}
+
+ConnectionHandle::~ConnectionHandle()
+{
+ if (mConn) {
+ gHttpHandler->ReclaimConnection(mConn);
+ }
+}
+
+NS_IMPL_ISUPPORTS0(ConnectionHandle)
+
+// 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(nsConnectionEntry *ent,
+ nsAHttpTransaction *aTrans,
+ uint32_t caps,
+ nsHttpConnection *conn,
+ int32_t priority)
+{
+ 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));
+
+ /* Use pipeline datastructure even if connection does not currently qualify
+ to pipeline this transaction because a different pipeline-eligible
+ transaction might be placed on the active connection. Make an exception
+ for CLASS_SOLO as that connection will never pipeline until it goes
+ quiescent */
+
+ RefPtr<nsAHttpTransaction> transaction;
+ nsresult rv;
+ if (conn->Classification() != nsAHttpTransaction::CLASS_SOLO) {
+ LOG((" using pipeline datastructure.\n"));
+ RefPtr<nsHttpPipeline> pipeline;
+ rv = BuildPipeline(ent, aTrans, getter_AddRefs(pipeline));
+ if (!NS_SUCCEEDED(rv))
+ return rv;
+ transaction = pipeline;
+ }
+ else {
+ LOG((" not using pipeline datastructure due to class solo.\n"));
+ 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=%x]\n", rv));
+ ent->mActiveConns.RemoveElement(conn);
+ if (conn == ent->mYellowConnection)
+ ent->OnYellowComplete();
+ DecrementActiveConnCount(conn);
+ ConditionallyStopTimeoutTick();
+
+ // sever back references to connection, and do so without triggering
+ // a call to ReclaimConnection ;-)
+ transaction->SetConnection(nullptr);
+ handle->Reset(); // destroy the connection
+ }
+
+ // As transaction goes out of scope it will drop the last refernece to the
+ // pipeline if activation failed, in which case this will destroy
+ // the pipeline, which will cause each the transactions owned by the
+ // pipeline to be restarted.
+
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
+ nsAHttpTransaction *firstTrans,
+ nsHttpPipeline **result)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ /* form a pipeline here even if nothing is pending so that we
+ can stream-feed it as new transactions arrive */
+
+ /* the first transaction can go in unconditionally - 1 transaction
+ on a nsHttpPipeline object is not a real HTTP pipeline */
+
+ RefPtr<nsHttpPipeline> pipeline = new nsHttpPipeline();
+ pipeline->AddTransaction(firstTrans);
+ pipeline.forget(result);
+ return NS_OK;
+}
+
+void
+nsHttpConnectionMgr::ReportProxyTelemetry(nsConnectionEntry *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(PR_GetCurrentThread() == gSocketThread);
+
+ // 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();
+
+ Http2PushedStream *pushedStream = trans->GetPushedStream();
+ if (pushedStream) {
+ LOG((" ProcessNewTransaction %p tied to h2 session push %p\n",
+ trans, pushedStream->Session()));
+ return pushedStream->Session()->
+ AddStream(trans, trans->Priority(), false, nullptr) ?
+ NS_OK : NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ nsHttpConnectionInfo *ci = trans->ConnectionInfo();
+ MOZ_ASSERT(ci);
+
+ nsConnectionEntry *ent =
+ GetOrCreateConnectionEntry(ci, !!trans->TunnelProvider());
+
+ // SPDY coalescing of hostnames means we might redirect from this
+ // connection entry onto the preferred one.
+ nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
+ if (preferredEntry && (preferredEntry != ent)) {
+ LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "redirected via coalescing from %s to %s\n", trans,
+ ent->mConnInfo->Origin(), preferredEntry->mConnInfo->Origin()));
+
+ ent = preferredEntry;
+ }
+
+ 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<nsHttpConnection> conn;
+ 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 (static_cast<int32_t>(ent->mActiveConns.IndexOf(conn)) == -1) {
+ 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(static_cast<int32_t>(ent->mIdleConns.IndexOf(conn)) == -1);
+ MOZ_ASSERT(!conn->IsExperienced());
+
+ AddActiveConn(conn, ent); // make it active
+ }
+
+ trans->SetConnection(nullptr);
+ rv = DispatchTransaction(ent, trans, conn);
+ } else {
+ rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(), trans);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
+ return rv;
+ }
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG((" adding transaction to pending queue "
+ "[trans=%p pending-count=%u]\n",
+ trans, ent->mPendingQ.Length()+1));
+ // put this transaction on the pending queue...
+ InsertTransactionSorted(ent->mPendingQ, trans);
+ return NS_OK;
+ }
+
+ LOG((" ProcessNewTransaction Hard Error trans=%p rv=%x\n", trans, rv));
+ return rv;
+}
+
+
+void
+nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
+ nsConnectionEntry *ent)
+{
+ ent->mActiveConns.AppendElement(conn);
+ mNumActiveConns++;
+ ActivateTimeoutTick();
+}
+
+void
+nsHttpConnectionMgr::DecrementActiveConnCount(nsHttpConnection *conn)
+{
+ mNumActiveConns--;
+ if (conn->EverUsedSpdy())
+ mNumSpdyActiveConns--;
+}
+
+void
+nsHttpConnectionMgr::StartedConnect()
+{
+ mNumActiveConns++;
+ ActivateTimeoutTick(); // likely disabled by RecvdConnect()
+}
+
+void
+nsHttpConnectionMgr::RecvdConnect()
+{
+ mNumActiveConns--;
+ ConditionallyStopTimeoutTick();
+}
+
+nsresult
+nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
+ nsAHttpTransaction *trans,
+ uint32_t caps,
+ bool speculative,
+ bool isFromPredictor,
+ bool allow1918)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ RefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps);
+ if (speculative) {
+ sock->SetSpeculative(true);
+ sock->SetAllow1918(allow1918);
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN> totalSpeculativeConn;
+ ++totalSpeculativeConn;
+
+ if (isFromPredictor) {
+ sock->SetIsFromPredictor(true);
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED> totalPreconnectsCreated;
+ ++totalPreconnectsCreated;
+ }
+ }
+
+ // 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);
+
+ ent->mHalfOpens.AppendElement(sock);
+ mNumHalfOpenConns++;
+ return NS_OK;
+}
+
+// This function tries to dispatch the pending spdy transactions on
+// the connection entry sent in as an argument. It will do so on the
+// active spdy connection either in that same entry or in the
+// redirected 'preferred' entry for the same coalescing hash key if
+// coalescing is enabled.
+
+void
+nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
+{
+ nsHttpConnection *conn = GetSpdyPreferredConn(ent);
+ if (!conn || !conn->CanDirectlyActivate())
+ return;
+
+ nsTArray<RefPtr<nsHttpTransaction> > leftovers;
+ uint32_t index;
+
+ // Dispatch all the transactions we can
+ for (index = 0;
+ index < ent->mPendingQ.Length() && conn->CanDirectlyActivate();
+ ++index) {
+ nsHttpTransaction *trans = ent->mPendingQ[index];
+
+ if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
+ trans->Caps() & NS_HTTP_DISALLOW_SPDY) {
+ leftovers.AppendElement(trans);
+ continue;
+ }
+
+ nsresult rv = DispatchTransaction(ent, trans, 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",
+ trans));
+ trans->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 < ent->mPendingQ.Length(); ++index) {
+ nsHttpTransaction *trans = ent->mPendingQ[index];
+ leftovers.AppendElement(trans);
+ }
+
+ // Put the leftovers back in the pending queue and get rid of the
+ // transactions we dispatched
+ leftovers.SwapElements(ent->mPendingQ);
+ leftovers.Clear();
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ProcessSpdyPendingQ(iter.Data());
+ }
+}
+
+nsHttpConnection *
+nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(ent);
+
+ nsConnectionEntry *preferred = GetSpdyPreferredEnt(ent);
+ // this entry is spdy-enabled if it is involved in a redirect
+ if (preferred) {
+ // all new connections for this entry will use spdy too
+ ent->mUsingSpdy = true;
+ } else {
+ preferred = ent;
+ }
+
+ if (!preferred->mUsingSpdy) {
+ return nullptr;
+ }
+
+ nsHttpConnection *rv = nullptr;
+ uint32_t activeLen = preferred->mActiveConns.Length();
+ uint32_t index;
+
+ // activeLen should generally be 1.. this is a setup race being resolved
+ // take a conn who can activate and is experienced
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ if (tmp->CanDirectlyActivate() && tmp->IsExperienced()) {
+ rv = tmp;
+ break;
+ }
+ }
+
+ // if that worked, cleanup anything else
+ if (rv) {
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ // in the case where there is a functional h2 session, drop the others
+ if (tmp != rv) {
+ tmp->DontReuse();
+ }
+ }
+ return rv;
+ }
+
+ // take a conn who can activate and leave the rest alone
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ if (tmp->CanDirectlyActivate()) {
+ rv = tmp;
+ break;
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+void
+nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
+
+ gHttpHandler->StopRequestTokenBucket();
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ // Close all active connections.
+ while (ent->mActiveConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mActiveConns[0]);
+ ent->mActiveConns.RemoveElementAt(0);
+ DecrementActiveConnCount(conn);
+ // Since nsHttpConnection::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);
+ }
+
+ // Close all idle connections.
+ while (ent->mIdleConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
+
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ // If all idle connections are removed we can stop pruning dead
+ // connections.
+ ConditionallyStopPruneDeadConnectionsTimer();
+
+ // Close all pending transactions.
+ while (ent->mPendingQ.Length()) {
+ nsHttpTransaction *trans = ent->mPendingQ[0];
+ trans->Close(NS_ERROR_ABORT);
+ ent->mPendingQ.RemoveElementAt(0);
+ }
+
+ // Close all half open tcp connections.
+ for (int32_t i = int32_t(ent->mHalfOpens.Length()) - 1; i >= 0; i--) {
+ ent->mHalfOpens[i]->Abandon();
+ }
+
+ iter.Remove();
+ }
+
+ if (mTimeoutTick) {
+ mTimeoutTick->Cancel();
+ mTimeoutTick = nullptr;
+ mTimeoutTickArmed = false;
+ }
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (mTrafficTimer) {
+ mTrafficTimer->Cancel();
+ mTrafficTimer = nullptr;
+ }
+
+ // 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)
+{
+ LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
+
+ nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
+ trans->SetPriority(priority);
+ nsresult rv = ProcessNewTransaction(trans);
+ if (NS_FAILED(rv))
+ trans->Close(rv); // for whatever its worth
+}
+
+void
+nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
+
+ RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction *>(param);
+ trans->SetPriority(priority);
+
+ nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(),
+ nullptr, trans);
+
+ if (ent) {
+ int32_t index = ent->mPendingQ.IndexOf(trans);
+ if (index >= 0) {
+ ent->mPendingQ.RemoveElementAt(index);
+ InsertTransactionSorted(ent->mPendingQ, trans);
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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 {
+ nsConnectionEntry *ent =
+ LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans);
+
+ if (ent) {
+ int32_t transIndex = ent->mPendingQ.IndexOf(trans);
+ if (transIndex >= 0) {
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
+ " found in pending queue\n", trans));
+ ent->mPendingQ.RemoveElementAt(transIndex);
+ }
+
+ // Abandon all half-open sockets belonging to the given transaction.
+ for (uint32_t index = 0;
+ index < ent->mHalfOpens.Length();
+ ++index) {
+ nsHalfOpenSocket *half = ent->mHalfOpens[index];
+ if (trans == half->Transaction()) {
+ half->Abandon();
+ // there is only one, and now mHalfOpens[] has been changed.
+ break;
+ }
+ }
+ }
+
+ 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.
+ for (uint32_t index = 0;
+ ent && (index < ent->mActiveConns.Length());
+ ++index) {
+ nsHttpConnection *activeConn = ent->mActiveConns[index];
+ nsAHttpTransaction *liveTransaction = activeConn->Transaction();
+ if (liveTransaction && liveTransaction->IsNullTransaction()) {
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p] "
+ "also canceling Null Transaction %p on conn %p\n",
+ trans, liveTransaction, activeConn));
+ activeConn->CloseTransaction(liveTransaction, closeCode);
+ }
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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()) {
+ ProcessPendingQForEntry(iter.Data(), true);
+ }
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
+ ci->HashKey().get()));
+
+ // start by processing the queue identified by the given connection info.
+ nsConnectionEntry *ent = mCT.Get(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(), 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);
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
+ ci->HashKey().get(), ent));
+ if (!ent) {
+ return;
+ }
+
+ for (int32_t i = ent->mPendingQ.Length() - 1; i >= 0; --i) {
+ nsHttpTransaction *trans = ent->mPendingQ[i];
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
+ ci->HashKey().get(), ent, trans));
+ trans->Close(reason);
+ ent->mPendingQ.RemoveElementAt(i);
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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()) {
+ nsAutoPtr<nsConnectionEntry>& 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 = UINT32_MAX;
+ int32_t count = ent->mIdleConns.Length();
+ if (count > 0) {
+ for (int32_t i = count - 1; i >= 0; --i) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[i]);
+ if (!conn->CanReuse()) {
+ ent->mIdleConns.RemoveElementAt(i);
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ } else {
+ timeToNextExpire =
+ std::min(timeToNextExpire, conn->TimeToLive());
+ }
+ }
+ }
+
+ if (ent->mUsingSpdy) {
+ for (uint32_t i = 0; i < ent->mActiveConns.Length(); ++i) {
+ nsHttpConnection* conn = ent->mActiveConns[i];
+ if (conn->UsingSpdy()) {
+ if (!conn->CanReuse()) {
+ // Marking it don't-reuse will create an active
+ // tear down if the spdy session is idle.
+ conn->DontReuse();
+ } else {
+ timeToNextExpire =
+ std::min(timeToNextExpire, conn->TimeToLive());
+ }
+ }
+ }
+ }
+
+ // 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();
+ }
+
+ // If this entry is empty, we have too many entries, and this
+ // doesn't represent some painfully determined red condition, then
+ // we can clean it up and restart from yellow.
+ if (ent->PipelineState() != PS_RED &&
+ mCT.Count() > 125 &&
+ ent->mIdleConns.Length() == 0 &&
+ ent->mActiveConns.Length() == 0 &&
+ ent->mHalfOpens.Length() == 0 &&
+ ent->mPendingQ.Length() == 0 &&
+ (!ent->mUsingSpdy || mCT.Count() > 300)) {
+ LOG((" removing empty connection entry\n"));
+ iter.Remove();
+ continue;
+ }
+
+ // Otherwise use this opportunity to compact our arrays...
+ ent->mIdleConns.Compact();
+ ent->mActiveConns.Compact();
+ ent->mPendingQ.Compact();
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
+
+ // Prune connections without traffic
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+
+ // Close the connections with no registered traffic.
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ LOG((" pruning no traffic [ci=%s]\n",
+ ent->mConnInfo->HashKey().get()));
+
+ uint32_t numConns = ent->mActiveConns.Length();
+ if (numConns) {
+ // Walk the list backwards to allow us to remove entries easily.
+ for (int index = numConns - 1; index >= 0; index--) {
+ if (ent->mActiveConns[index]->NoTraffic()) {
+ RefPtr<nsHttpConnection> conn = ent->mActiveConns[index];
+ ent->mActiveConns.RemoveElementAt(index);
+ DecrementActiveConnCount(conn);
+ conn->Close(NS_ERROR_ABORT);
+ LOG((" closed active connection due to no traffic "
+ "[conn=%p]\n", conn.get()));
+ }
+ }
+ }
+ }
+
+ mPruningNoTraffic = false; // not pruning anymore
+}
+
+void
+nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ // Iterate over all active connections and check them.
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ ent->mActiveConns[index]->CheckForTraffic(true);
+ }
+ // Iterate the idle connections and unmark them for traffic checks.
+ for (uint32_t index = 0; index < ent->mIdleConns.Length(); ++index) {
+ ent->mIdleConns[index]->CheckForTraffic(false);
+ }
+ }
+
+ // If the timer is already there. we just re-init it
+ if(!mTrafficTimer) {
+ mTrafficTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ // 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(PR_GetCurrentThread() == gSocketThread);
+
+ nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ClosePersistentConnections(iter.Data());
+ }
+
+ if (ci)
+ ResetIPFamilyPreference(ci);
+}
+
+void
+nsHttpConnectionMgr::OnMsgReclaimConnection(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param));
+
+ nsHttpConnection *conn = static_cast<nsHttpConnection *>(param);
+
+ //
+ // 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
+ //
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, 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);
+ LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
+ "forced new hash entry %s\n",
+ conn, conn->ConnectionInfo()->HashKey().get()));
+ }
+
+ MOZ_ASSERT(ent);
+ RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
+
+ // 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.
+
+ if (conn->EverUsedSpdy()) {
+ // Spdy 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 (ent->mActiveConns.RemoveElement(conn)) {
+ if (conn == ent->mYellowConnection) {
+ ent->OnYellowComplete();
+ }
+ DecrementActiveConnCount(conn);
+ ConditionallyStopTimeoutTick();
+ }
+
+ if (conn->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)
+
+ uint32_t idx;
+ for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
+ nsHttpConnection *idleConn = ent->mIdleConns[idx];
+ if (idleConn->MaxBytesRead() < conn->MaxBytesRead())
+ break;
+ }
+
+ ent->mIdleConns.InsertElementAt(idx, conn);
+ mNumIdleConns++;
+ conn->BeginIdleMonitoring();
+
+ // 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.
+ uint32_t timeToLive = conn->TimeToLive();
+ if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
+ PruneDeadConnectionsAfter(timeToLive);
+ } 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(PR_GetCurrentThread() == gSocketThread);
+ nsCompleteUpgradeData *data = static_cast<nsCompleteUpgradeData *>(param);
+ LOG(("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
+ "this=%p conn=%p listener=%p\n", this, data->mConn.get(),
+ data->mUpgradeListener.get()));
+
+ nsCOMPtr<nsISocketTransport> socketTransport;
+ nsCOMPtr<nsIAsyncInputStream> socketIn;
+ nsCOMPtr<nsIAsyncOutputStream> socketOut;
+
+ nsresult rv;
+ rv = data->mConn->TakeTransport(getter_AddRefs(socketTransport),
+ getter_AddRefs(socketIn),
+ getter_AddRefs(socketOut));
+
+ if (NS_SUCCEEDED(rv))
+ data->mUpgradeListener->OnTransportAvailable(socketTransport,
+ socketIn,
+ socketOut);
+}
+
+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_PERSISTENT_CONNECTIONS_PER_HOST:
+ mMaxPersistConnsPerHost = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
+ mMaxPersistConnsPerProxy = value;
+ break;
+ case MAX_REQUEST_DELAY:
+ mMaxRequestDelay = value;
+ break;
+ case MAX_PIPELINED_REQUESTS:
+ mMaxPipelinedRequests = value;
+ break;
+ case MAX_OPTIMISTIC_PIPELINED_REQUESTS:
+ mMaxOptimisticPipelinedRequests = value;
+ break;
+ default:
+ NS_NOTREACHED("unexpected parameter name");
+ }
+}
+
+// nsHttpConnectionMgr::nsConnectionEntry
+nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
+{
+ MOZ_COUNT_DTOR(nsConnectionEntry);
+ gHttpHandler->ConnMgr()->RemovePreferredHash(this);
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessFeedback(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpPipelineFeedback *fb = static_cast<nsHttpPipelineFeedback *>(param);
+ PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData);
+}
+
+// Read Timeout Tick handlers
+
+void
+nsHttpConnectionMgr::ActivateTimeoutTick()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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 = do_CreateInstance(NS_TIMER_CONTRACTID);
+ 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);
+}
+
+void
+nsHttpConnectionMgr::TimeoutTick()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ LOG(("nsHttpConnectionMgr::TimeoutTick() this=%p host=%s "
+ "idle=%d active=%d half-len=%d pending=%d\n",
+ this, ent->mConnInfo->Origin(), ent->mIdleConns.Length(),
+ ent->mActiveConns.Length(), ent->mHalfOpens.Length(),
+ ent->mPendingQ.Length()));
+
+ // First call the tick handler for each active connection.
+ PRIntervalTime tickTime = PR_IntervalNow();
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ uint32_t connNextTimeout =
+ ent->mActiveConns[index]->ReadTimeoutTick(tickTime);
+ mTimeoutTickNext = std::min(mTimeoutTickNext, connNextTimeout);
+ }
+
+ // Now check for any stalled half open sockets.
+ if (ent->mHalfOpens.Length()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
+
+ for (uint32_t index = ent->mHalfOpens.Length(); index > 0; ) {
+ index--;
+
+ nsHalfOpenSocket *half = ent->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",
+ ent->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",
+ ent->mConnInfo->HashKey().get(), delta));
+ half->Abandon();
+ }
+ }
+ }
+ if (ent->mHalfOpens.Length()) {
+ mTimeoutTickNext = 1;
+ }
+ }
+
+ 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
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *specificCI,
+ bool prohibitWildCard)
+{
+ // step 1
+ nsConnectionEntry *specificEnt = mCT.Get(specificCI->HashKey());
+ if (specificEnt && specificEnt->AvailableForDispatchNow()) {
+ return specificEnt;
+ }
+
+ if (!specificCI->UsingHttpsProxy()) {
+ prohibitWildCard = true;
+ }
+
+ // step 2
+ if (!prohibitWildCard) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
+ specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
+ nsConnectionEntry *wildCardEnt = mCT.Get(wildCardProxyCI->HashKey());
+ if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
+ return wildCardEnt;
+ }
+ }
+
+ // step 3
+ if (!specificEnt) {
+ RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
+ specificEnt = new nsConnectionEntry(clone);
+ mCT.Put(clone->HashKey(), specificEnt);
+ }
+ return specificEnt;
+}
+
+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);
+}
+
+void
+nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ SpeculativeConnectArgs *args = static_cast<SpeculativeConnectArgs *>(param);
+
+ LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
+ args->mTrans->ConnectionInfo()->HashKey().get()));
+
+ nsConnectionEntry *ent =
+ GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);
+
+ // If spdy has previously made a preferred entry for this host via
+ // the ip pooling rules. If so, connect to the preferred host instead of
+ // the one directly passed in here.
+ nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
+ if (preferredEntry)
+ ent = preferredEntry;
+
+ uint32_t parallelSpeculativeConnectLimit =
+ gHttpHandler->ParallelSpeculativeConnectLimit();
+ bool ignoreIdle = false;
+ bool isFromPredictor = false;
+ bool allow1918 = false;
+
+ if (args->mOverridesOK) {
+ parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
+ ignoreIdle = args->mIgnoreIdle;
+ isFromPredictor = args->mIsFromPredictor;
+ allow1918 = args->mAllow1918;
+ }
+
+ bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
+ if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
+ ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
+ !ent->mIdleConns.Length()) &&
+ !(keepAlive && RestrictConnections(ent)) &&
+ !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
+ CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918);
+ } else {
+ LOG(("OnMsgSpeculativeConnect Transport "
+ "not created due to existing connection count\n"));
+ }
+}
+
+bool
+ConnectionHandle::IsPersistent()
+{
+ return mConn->IsPersistent();
+}
+
+bool
+ConnectionHandle::IsReused()
+{
+ return mConn->IsReused();
+}
+
+void
+ConnectionHandle::DontReuse()
+{
+ mConn->DontReuse();
+}
+
+nsresult
+ConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
+{
+ return mConn->PushBack(buf, bufLen);
+}
+
+
+//////////////////////// nsHalfOpenSocket
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr::nsHalfOpenSocket,
+ nsIOutputStreamCallback,
+ nsITransportEventSink,
+ nsIInterfaceRequestor,
+ nsITimerCallback)
+
+nsHttpConnectionMgr::
+nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent,
+ nsAHttpTransaction *trans,
+ uint32_t caps)
+ : mEnt(ent)
+ , mTransaction(trans)
+ , mDispatchedMTransaction(false)
+ , mCaps(caps)
+ , mSpeculative(false)
+ , mIsFromPredictor(false)
+ , mAllow1918(true)
+ , mHasConnected(false)
+ , mPrimaryConnectedOK(false)
+ , mBackupConnectedOK(false)
+{
+ MOZ_ASSERT(ent && trans, "constructor with null arguments");
+ LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s key=%s]\n",
+ this, trans, ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get()));
+}
+
+nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket()
+{
+ MOZ_ASSERT(!mStreamOut);
+ MOZ_ASSERT(!mBackupStreamOut);
+ MOZ_ASSERT(!mSynTimer);
+ LOG(("Destroying nsHalfOpenSocket [this=%p]\n", this));
+
+ if (mEnt)
+ mEnt->RemoveHalfOpen(this);
+}
+
+nsresult
+nsHttpConnectionMgr::
+nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
+ nsIAsyncInputStream **instream,
+ nsIAsyncOutputStream **outstream,
+ bool isBackup)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+ const char *socketTypes[1];
+ uint32_t typeCount = 0;
+ const nsHttpConnectionInfo *ci = mEnt->mConnInfo;
+ if (ci->FirstHopSSL()) {
+ socketTypes[typeCount++] = "ssl";
+ } else {
+ socketTypes[typeCount] = gHttpHandler->DefaultSocketType();
+ if (socketTypes[typeCount]) {
+ typeCount++;
+ }
+ }
+
+ nsCOMPtr<nsISocketTransport> socketTransport;
+ nsCOMPtr<nsISocketTransportService> sts;
+
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("nsHalfOpenSocket::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, typeCount,
+ 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(("nsHalfOpenSocket this=%p using legacy nsISocketTransportService "
+ "means explicit route %s:%d will be ignored.\n", this,
+ ci->RoutedHost(), ci->RoutedPort()));
+ }
+
+ rv = sts->CreateTransport(socketTypes, typeCount,
+ 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;
+
+ if (mCaps & NS_HTTP_LOAD_ANONYMOUS)
+ tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
+
+ if (ci->GetPrivate())
+ tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
+
+ if ((mCaps & NS_HTTP_BE_CONSERVATIVE) || ci->GetBeConservative()) {
+ LOG(("Setting Socket to BE_CONSERVATIVE"));
+ tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
+ }
+
+ // 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".
+ if (mEnt->mPreferIPv6) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV4;
+ }
+ else if (mEnt->mPreferIPv4 ||
+ (isBackup && gHttpHandler->FastFallbackToIPv4())) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV6;
+ }
+
+ if (!Allow1918()) {
+ tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
+ }
+
+ socketTransport->SetConnectionFlags(tmpFlags);
+
+ NeckoOriginAttributes originAttributes =
+ mEnt->mConnInfo->GetOriginAttributes();
+ if (originAttributes != NeckoOriginAttributes()) {
+ socketTransport->SetOriginAttributes(originAttributes);
+ }
+
+ socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
+
+ if (!ci->GetNetworkInterfaceId().IsEmpty()) {
+ socketTransport->SetNetworkInterfaceId(ci->GetNetworkInterfaceId());
+ }
+
+ rv = socketTransport->SetEventSink(this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = socketTransport->SetSecurityCallbacks(this);
+ 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
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+
+ mPrimarySynStarted = TimeStamp::Now();
+ rv = SetupStreams(getter_AddRefs(mSocketTransport),
+ getter_AddRefs(mStreamIn),
+ getter_AddRefs(mStreamOut),
+ false);
+ LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
+ this, mEnt->mConnInfo->Origin(), rv));
+ if (NS_FAILED(rv)) {
+ if (mStreamOut)
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mSocketTransport = nullptr;
+ }
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
+{
+ MOZ_ASSERT(mTransaction);
+ MOZ_ASSERT(!mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+
+ mBackupSynStarted = TimeStamp::Now();
+ nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
+ getter_AddRefs(mBackupStreamIn),
+ getter_AddRefs(mBackupStreamOut),
+ true);
+ LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
+ this, mEnt->mConnInfo->Origin(), rv));
+ if (NS_FAILED(rv)) {
+ if (mBackupStreamOut)
+ mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mBackupStreamOut = nullptr;
+ mBackupStreamIn = nullptr;
+ mBackupTransport = nullptr;
+ }
+ return rv;
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
+{
+ uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
+ MOZ_ASSERT(!mSynTimer, "timer already initd");
+ if (timeout && !mTransaction->IsDone() && !mTransaction->IsNullTransaction()) {
+ // 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.
+ nsresult rv;
+ mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
+ LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this));
+ }
+ } else if (timeout) {
+ LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p], did not arm\n", this));
+ }
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::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(("nsHalfOpenSocket::CancelBackupTimer()"));
+ mSynTimer->Cancel();
+ mSynTimer = nullptr;
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::Abandon()
+{
+ LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s] %p %p %p %p",
+ this, mEnt->mConnInfo->Origin(),
+ mSocketTransport.get(), mBackupTransport.get(),
+ mStreamOut.get(), mBackupStreamOut.get()));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ RefPtr<nsHalfOpenSocket> deleteProtector(this);
+
+ // Tell socket (and backup socket) to forget the half open socket.
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(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) {
+ 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).
+ mStreamIn = mBackupStreamIn = nullptr;
+
+ // Stop the timer - we don't want any new backups.
+ CancelBackupTimer();
+
+ // Remove the half open from the connection entry.
+ if (mEnt)
+ mEnt->RemoveHalfOpen(this);
+ mEnt = nullptr;
+}
+
+double
+nsHttpConnectionMgr::nsHalfOpenSocket::Duration(TimeStamp epoch)
+{
+ if (mPrimarySynStarted.IsNull())
+ return 0;
+
+ return (epoch - mPrimarySynStarted).ToMilliseconds();
+}
+
+
+NS_IMETHODIMP // method for nsITimerCallback
+nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(timer == mSynTimer, "wrong timer");
+ MOZ_ASSERT(mTransaction && !mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+
+ SetupBackupStreams();
+
+ mSynTimer = nullptr;
+ return NS_OK;
+}
+
+// method for nsIAsyncOutputStreamCallback
+NS_IMETHODIMP
+nsHttpConnectionMgr::
+nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut,
+ "stream mismatch");
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n",
+ this, mEnt->mConnInfo->Origin(),
+ out == mStreamOut ? "primary" : "backup"));
+ int32_t index;
+ nsresult rv;
+
+ gHttpHandler->ConnMgr()->RecvdConnect();
+
+ CancelBackupTimer();
+
+ // assign the new socket to the http connection
+ RefPtr<nsHttpConnection> conn = new nsHttpConnection();
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady "
+ "Created new nshttpconnection %p\n", conn.get()));
+
+ // 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));
+ if (out == mStreamOut) {
+ TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
+ rv = conn->Init(mEnt->mConnInfo,
+ gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mSocketTransport, mStreamIn, mStreamOut,
+ mPrimaryConnectedOK, callbacks,
+ PR_MillisecondsToInterval(
+ static_cast<uint32_t>(rtt.ToMilliseconds())));
+
+ if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr)))
+ mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
+
+ // The nsHttpConnection object now owns these streams and sockets
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mSocketTransport = nullptr;
+ } else if (out == mBackupStreamOut) {
+ MOZ_ASSERT(!mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+ 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())));
+
+ 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(("nsHalfOpenSocket::OnOutputStreamReady "
+ "conn->init (%p) failed %x\n", conn.get(), rv));
+ return rv;
+ }
+
+ // This half-open socket has created a connection. This flag excludes it
+ // from counter of actual connections used for checking limits.
+ mHasConnected = true;
+
+ // if this is still in the pending list, remove it and dispatch it
+ index = mEnt->mPendingQ.IndexOf(mTransaction);
+ if (index != -1) {
+ MOZ_ASSERT(!mSpeculative,
+ "Speculative Half Open found mTransaction");
+ RefPtr<nsHttpTransaction> temp = mEnt->mPendingQ[index];
+ mEnt->mPendingQ.RemoveElementAt(index);
+ gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
+ rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, 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.
+ conn->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
+ if (mEnt->mConnInfo->FirstHopSSL() && !mEnt->mPendingQ.Length() &&
+ !mEnt->mConnInfo->UsingConnect()) {
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady 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 & ~NS_HTTP_ALLOW_PIPELINING);
+ }
+
+ gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
+ conn->Classify(nsAHttpTransaction::CLASS_SOLO);
+ rv = gHttpHandler->ConnMgr()->
+ DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
+ } else {
+ // otherwise just put this in the persistent connection pool
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match "
+ "returning conn %p to pool\n", conn.get()));
+ gHttpHandler->ConnMgr()->OnMsgReclaimConnection(0, conn);
+ }
+ }
+
+ return rv;
+}
+
+// method for nsITransportEventSink
+NS_IMETHODIMP
+nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
+ nsresult status,
+ int64_t progress,
+ int64_t progressMax)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTransaction)
+ 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;
+ }
+
+ // 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->mConnInfo->UsingProxy() &&
+ mEnt->mCoalescingKeys.IsEmpty()) {
+
+ nsCOMPtr<nsIDNSRecord> 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) {
+ nsCString *newKey = mEnt->mCoalescingKeys.AppendElement(nsCString());
+ newKey->SetCapacity(kIPv6CStrBufSize + 26);
+ NetAddrToString(&addressSet[i], newKey->BeginWriting(), kIPv6CStrBufSize);
+ newKey->SetLength(strlen(newKey->BeginReading()));
+ if (mEnt->mConnInfo->GetAnonymous()) {
+ newKey->AppendLiteral("~A:");
+ } else {
+ newKey->AppendLiteral("~.:");
+ }
+ newKey->AppendInt(mEnt->mConnInfo->OriginPort());
+ LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::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.
+ if (mEnt && !mBackupTransport && !mSynTimer)
+ 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
+nsHttpConnectionMgr::nsHalfOpenSocket::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;
+}
+
+
+already_AddRefed<nsHttpConnection>
+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();
+}
+
+uint32_t
+ConnectionHandle::CancelPipeline(nsresult reason)
+{
+ // no pipeline to cancel
+ return 0;
+}
+
+nsAHttpTransaction::Classifier
+ConnectionHandle::Classification()
+{
+ if (mConn)
+ return mConn->Classification();
+
+ LOG(("ConnectionHandle::Classification this=%p "
+ "has null mConn using CLASS_SOLO default", this));
+ return nsAHttpTransaction::CLASS_SOLO;
+}
+
+// nsConnectionEntry
+
+nsHttpConnectionMgr::
+nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
+ : mConnInfo(ci)
+ , mPipelineState(PS_YELLOW)
+ , mYellowGoodEvents(0)
+ , mYellowBadEvents(0)
+ , mYellowConnection(nullptr)
+ , mGreenDepth(kPipelineOpen)
+ , mPipeliningPenalty(0)
+ , mUsingSpdy(false)
+ , mInPreferredHash(false)
+ , mPreferIPv4(false)
+ , mPreferIPv6(false)
+ , mUsedForConnection(false)
+{
+ MOZ_COUNT_CTOR(nsConnectionEntry);
+ if (gHttpHandler->GetPipelineAggressive()) {
+ mGreenDepth = kPipelineUnlimited;
+ mPipelineState = PS_GREEN;
+ }
+ mInitialGreenDepth = mGreenDepth;
+ memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX);
+}
+
+bool
+nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
+{
+ if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
+ return true;
+ }
+
+ return gHttpHandler->ConnMgr()->
+ GetSpdyPreferredConn(this) ? true : false;
+}
+
+bool
+nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining()
+{
+ return mPipelineState != nsHttpConnectionMgr::PS_RED;
+}
+
+nsHttpConnectionMgr::PipeliningState
+nsHttpConnectionMgr::nsConnectionEntry::PipelineState()
+{
+ return mPipelineState;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnPipelineFeedbackInfo(
+ nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+ nsHttpConnection *conn,
+ uint32_t data)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mPipelineState == PS_YELLOW) {
+ if (info & kPipelineInfoTypeBad)
+ mYellowBadEvents++;
+ else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood))
+ mYellowGoodEvents++;
+ }
+
+ if (mPipelineState == PS_GREEN && info == GoodCompletedOK) {
+ int32_t depth = data;
+ LOG(("Transaction completed at pipeline depth of %d. Host = %s\n",
+ depth, mConnInfo->Origin()));
+
+ if (depth >= 3)
+ mGreenDepth = kPipelineUnlimited;
+ }
+
+ nsAHttpTransaction::Classifier classification;
+ if (conn)
+ classification = conn->Classification();
+ else if (info == BadInsufficientFraming ||
+ info == BadUnexpectedLarge)
+ classification = (nsAHttpTransaction::Classifier) data;
+ else
+ classification = nsAHttpTransaction::CLASS_SOLO;
+
+ if (gHttpHandler->GetPipelineAggressive() &&
+ info & kPipelineInfoTypeBad &&
+ info != BadExplicitClose &&
+ info != RedVersionTooLow &&
+ info != RedBannedServer &&
+ info != RedCorruptedContent &&
+ info != BadInsufficientFraming) {
+ LOG(("minor negative feedback ignored "
+ "because of pipeline aggressive mode"));
+ }
+ else if (info & kPipelineInfoTypeBad) {
+ if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) {
+ LOG(("transition to red from %d. Host = %s.\n",
+ mPipelineState, mConnInfo->Origin()));
+ mPipelineState = PS_RED;
+ mPipeliningPenalty = 0;
+ }
+
+ if (mLastCreditTime.IsNull())
+ mLastCreditTime = TimeStamp::Now();
+
+ // Red* events impact the host globally via mPipeliningPenalty, while
+ // Bad* events impact the per class penalty.
+
+ // The individual penalties should be < 16bit-signed-maxint - 25000
+ // (approx 7500). Penalties are paid-off either when something promising
+ // happens (a successful transaction, or promising headers) or when
+ // time goes by at a rate of 1 penalty point every 16 seconds.
+
+ switch (info) {
+ case RedVersionTooLow:
+ mPipeliningPenalty += 1000;
+ break;
+ case RedBannedServer:
+ mPipeliningPenalty += 7000;
+ break;
+ case RedCorruptedContent:
+ mPipeliningPenalty += 7000;
+ break;
+ case RedCanceledPipeline:
+ mPipeliningPenalty += 60;
+ break;
+ case BadExplicitClose:
+ mPipeliningClassPenalty[classification] += 250;
+ break;
+ case BadSlowReadMinor:
+ mPipeliningClassPenalty[classification] += 5;
+ break;
+ case BadSlowReadMajor:
+ mPipeliningClassPenalty[classification] += 25;
+ break;
+ case BadInsufficientFraming:
+ mPipeliningClassPenalty[classification] += 7000;
+ break;
+ case BadUnexpectedLarge:
+ mPipeliningClassPenalty[classification] += 120;
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Unknown Bad/Red Pipeline Feedback Event");
+ }
+
+ const int16_t kPenalty = 25000;
+ mPipeliningPenalty = std::min(mPipeliningPenalty, kPenalty);
+ mPipeliningClassPenalty[classification] =
+ std::min(mPipeliningClassPenalty[classification], kPenalty);
+
+ LOG(("Assessing red penalty to %s class %d for event %d. "
+ "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Origin(),
+ classification, info, mPipeliningPenalty, classification,
+ mPipeliningClassPenalty[classification]));
+ }
+ else {
+ // hand out credits for neutral and good events such as
+ // "headers look ok" events
+
+ mPipeliningPenalty = std::max(mPipeliningPenalty - 1, 0);
+ mPipeliningClassPenalty[classification] = std::max(mPipeliningClassPenalty[classification] - 1, 0);
+ }
+
+ if (mPipelineState == PS_RED && !mPipeliningPenalty)
+ {
+ LOG(("transition %s to yellow\n", mConnInfo->Origin()));
+ mPipelineState = PS_YELLOW;
+ mYellowConnection = nullptr;
+ }
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn)
+{
+ MOZ_ASSERT(!mYellowConnection && mPipelineState == PS_YELLOW,
+ "yellow connection already set or state is not yellow");
+ mYellowConnection = conn;
+ mYellowGoodEvents = mYellowBadEvents = 0;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnYellowComplete()
+{
+ if (mPipelineState == PS_YELLOW) {
+ if (mYellowGoodEvents && !mYellowBadEvents) {
+ LOG(("transition %s to green\n", mConnInfo->Origin()));
+ mPipelineState = PS_GREEN;
+ mGreenDepth = mInitialGreenDepth;
+ }
+ else {
+ // The purpose of the yellow state is to witness at least
+ // one successful pipelined transaction without seeing any
+ // kind of negative feedback before opening the flood gates.
+ // If we haven't confirmed that, then transfer back to red.
+ LOG(("transition %s to red from yellow return\n",
+ mConnInfo->Origin()));
+ mPipelineState = PS_RED;
+ }
+ }
+
+ mYellowConnection = nullptr;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::CreditPenalty()
+{
+ if (mLastCreditTime.IsNull())
+ return;
+
+ // Decrease penalty values by 1 for every 16 seconds
+ // (i.e 3.7 per minute, or 1000 every 4h20m)
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration elapsedTime = now - mLastCreditTime;
+ uint32_t creditsEarned =
+ static_cast<uint32_t>(elapsedTime.ToSeconds()) >> 4;
+
+ bool failed = false;
+ if (creditsEarned > 0) {
+ mPipeliningPenalty =
+ std::max(int32_t(mPipeliningPenalty - creditsEarned), 0);
+ if (mPipeliningPenalty > 0)
+ failed = true;
+
+ for (int32_t i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
+ mPipeliningClassPenalty[i] =
+ std::max(int32_t(mPipeliningClassPenalty[i] - creditsEarned), 0);
+ failed = failed || (mPipeliningClassPenalty[i] > 0);
+ }
+
+ // update last credit mark to reflect elapsed time
+ mLastCreditTime += TimeDuration::FromSeconds(creditsEarned << 4);
+ }
+ else {
+ failed = true; /* just assume this */
+ }
+
+ // If we are no longer red then clear the credit counter - you only
+ // get credits for time spent in the red state
+ if (!failed)
+ mLastCreditTime = TimeStamp(); /* reset to null timestamp */
+
+ if (mPipelineState == PS_RED && !mPipeliningPenalty)
+ {
+ LOG(("transition %s to yellow based on time credit\n",
+ mConnInfo->Origin()));
+ mPipelineState = PS_YELLOW;
+ mYellowConnection = nullptr;
+ }
+}
+
+uint32_t
+nsHttpConnectionMgr::
+nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass)
+{
+ // Still subject to configuration limit no matter return value
+
+ if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0))
+ return 0;
+
+ if (mPipelineState == PS_YELLOW)
+ return kPipelineRestricted;
+
+ return mGreenDepth;
+}
+
+bool
+nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams> *aArg)
+{
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ if (ent->mConnInfo->GetPrivate()) {
+ continue;
+ }
+
+ HttpRetParams data;
+ data.host = ent->mConnInfo->Origin();
+ data.port = ent->mConnInfo->OriginPort();
+ for (uint32_t i = 0; i < ent->mActiveConns.Length(); i++) {
+ HttpConnInfo info;
+ info.ttl = ent->mActiveConns[i]->TimeToLive();
+ info.rtt = ent->mActiveConns[i]->Rtt();
+ if (ent->mActiveConns[i]->UsingSpdy()) {
+ info.SetHTTP2ProtocolVersion(
+ ent->mActiveConns[i]->GetSpdyVersion());
+ } else {
+ info.SetHTTP1ProtocolVersion(
+ ent->mActiveConns[i]->GetLastHttpResponseVersion());
+ }
+ data.active.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < ent->mIdleConns.Length(); i++) {
+ HttpConnInfo info;
+ info.ttl = ent->mIdleConns[i]->TimeToLive();
+ info.rtt = ent->mIdleConns[i]->Rtt();
+ info.SetHTTP1ProtocolVersion(
+ ent->mIdleConns[i]->GetLastHttpResponseVersion());
+ data.idle.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < ent->mHalfOpens.Length(); i++) {
+ HalfOpenSockets hSocket;
+ hSocket.speculative = ent->mHalfOpens[i]->IsSpeculative();
+ data.halfOpens.AppendElement(hSocket);
+ }
+ data.spdy = ent->mUsingSpdy;
+ data.ssl = ent->mConnInfo->EndToEndSSL();
+ aArg->AppendElement(data);
+ }
+
+ return true;
+}
+
+void
+nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsConnectionEntry *ent = LookupConnectionEntry(ci, nullptr, nullptr);
+ if (ent)
+ ent->ResetIPFamilyPreference();
+}
+
+uint32_t
+nsHttpConnectionMgr::
+nsConnectionEntry::UnconnectedHalfOpens()
+{
+ uint32_t unconnectedHalfOpens = 0;
+ for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
+ if (!mHalfOpens[i]->HasConnected())
+ ++unconnectedHalfOpens;
+ }
+ return unconnectedHalfOpens;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen)
+{
+ // 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)) {
+
+ if (halfOpen->IsSpeculative()) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN> unusedSpeculativeConn;
+ ++unusedSpeculativeConn;
+
+ if (halfOpen->IsFromPredictor()) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED> totalPreconnectsUnused;
+ ++totalPreconnectsUnused;
+ }
+ }
+
+ MOZ_ASSERT(gHttpHandler->ConnMgr()->mNumHalfOpenConns);
+ if (gHttpHandler->ConnMgr()->mNumHalfOpenConns) { // just in case
+ gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
+ }
+ }
+
+ if (!UnconnectedHalfOpens())
+ // perhaps this reverted RestrictConnections()
+ // use the PostEvent version of processpendingq to avoid
+ // altering the pending q vector from an arbitrary stack
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::RecordIPFamilyPreference(uint16_t family)
+{
+ if (family == PR_AF_INET && !mPreferIPv6)
+ mPreferIPv4 = true;
+
+ if (family == PR_AF_INET6 && !mPreferIPv4)
+ mPreferIPv6 = true;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::ResetIPFamilyPreference()
+{
+ mPreferIPv4 = false;
+ mPreferIPv6 = false;
+}
+
+void
+nsHttpConnectionMgr::MoveToWildCardConnEntry(nsHttpConnectionInfo *specificCI,
+ nsHttpConnectionInfo *wildCardCI,
+ nsHttpConnection *proxyConn)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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()));
+
+ nsConnectionEntry *ent = LookupConnectionEntry(specificCI, proxyConn, nullptr);
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy %d)\n",
+ proxyConn, ent, ent ? ent->mUsingSpdy : 0));
+
+ if (!ent || !ent->mUsingSpdy) {
+ return;
+ }
+
+ nsConnectionEntry *wcEnt = GetOrCreateConnectionEntry(wildCardCI, true);
+ if (wcEnt == ent) {
+ // nothing to do!
+ return;
+ }
+ wcEnt->mUsingSpdy = true;
+
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
+ "idle=%d active=%d half=%d pending=%d\n", ent,
+ ent->mIdleConns.Length(), ent->mActiveConns.Length(),
+ ent->mHalfOpens.Length(), ent->mPendingQ.Length()));
+
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
+ "idle=%d active=%d half=%d pending=%d\n", wcEnt,
+ wcEnt->mIdleConns.Length(), wcEnt->mActiveConns.Length(),
+ wcEnt->mHalfOpens.Length(), wcEnt->mPendingQ.Length()));
+
+ int32_t count = ent->mActiveConns.Length();
+ RefPtr<nsHttpConnection> deleteProtector(proxyConn);
+ for (int32_t i = 0; i < count; ++i) {
+ if (ent->mActiveConns[i] == proxyConn) {
+ ent->mActiveConns.RemoveElementAt(i);
+ wcEnt->mActiveConns.InsertElementAt(0, proxyConn);
+ return;
+ }
+ }
+
+ count = ent->mIdleConns.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ if (ent->mIdleConns[i] == proxyConn) {
+ ent->mIdleConns.RemoveElementAt(i);
+ wcEnt->mIdleConns.InsertElementAt(0, proxyConn);
+ return;
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h
new file mode 100644
index 0000000000..7ca2a2b28d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -0,0 +1,636 @@
+/* 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 nsHttpConnectionMgr_h__
+#define nsHttpConnectionMgr_h__
+
+#include "nsHttpConnection.h"
+#include "nsHttpTransaction.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsAutoPtr.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "AlternateServices.h"
+#include "ARefBase.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 nsIObserver
+ , public AltSvcCache
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ // parameter names
+ enum nsParamName {
+ MAX_CONNECTIONS,
+ MAX_PERSISTENT_CONNECTIONS_PER_HOST,
+ MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
+ MAX_REQUEST_DELAY,
+ MAX_PIPELINED_REQUESTS,
+ MAX_OPTIMISTIC_PIPELINED_REQUESTS
+ };
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may only be called on the main thread.
+ //-------------------------------------------------------------------------
+
+ nsHttpConnectionMgr();
+
+ nsresult Init(uint16_t maxConnections,
+ uint16_t maxPersistentConnectionsPerHost,
+ uint16_t maxPersistentConnectionsPerProxy,
+ uint16_t maxRequestDelay,
+ uint16_t maxPipelinedRequests,
+ uint16_t maxOptimisticPipelinedRequests);
+ nsresult Shutdown();
+
+ //-------------------------------------------------------------------------
+ // 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();
+
+ // adds a transaction to the list of managed transactions.
+ nsresult AddTransaction(nsHttpTransaction *, int32_t priority);
+
+ // called to reschedule the given transaction. it must already have been
+ // added to the connection manager via AddTransaction.
+ nsresult RescheduleTransaction(nsHttpTransaction *, int32_t priority);
+
+ // cancels a transaction w/ the given reason.
+ nsresult CancelTransaction(nsHttpTransaction *, nsresult reason);
+ nsresult CancelTransactions(nsHttpConnectionInfo *, nsresult reason);
+
+ // called to force the connection manager to prune its list of idle
+ // connections.
+ nsresult PruneDeadConnections();
+
+ // called to close active connections with no registered "traffic"
+ nsresult PruneNoTraffic();
+
+ // "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.
+ nsresult VerifyTraffic();
+
+ // Close all idle persistent connections and prevent any active connections
+ // from being reused. Optional connection info resets CI specific
+ // information such as Happy Eyeballs history.
+ nsresult DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *);
+
+ // called to get a reference to the socket transport service. the socket
+ // transport service is not available when the connection manager is down.
+ nsresult GetSocketThreadTarget(nsIEventTarget **);
+
+ // 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.
+ nsresult SpeculativeConnect(nsHttpConnectionInfo *,
+ nsIInterfaceRequestor *,
+ uint32_t caps = 0,
+ NullHttpTransaction * = nullptr);
+
+ // 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.
+ nsresult ReclaimConnection(nsHttpConnection *conn);
+
+ // 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.
+ nsresult CompleteUpgrade(nsAHttpConnection *aConn,
+ nsIHttpUpgradeListener *aUpgradeListener);
+
+ // called to update a parameter after the connection manager has already
+ // been initialized.
+ nsresult UpdateParam(nsParamName name, uint16_t value);
+
+ // called from main thread to post a new request token bucket
+ // to the socket thread
+ nsresult UpdateRequestTokenBucket(EventTokenBucket *aBucket);
+
+ // clears the connection history mCT
+ nsresult ClearConnectionHistory();
+
+ // Pipielining Interfaces and Datatypes
+
+ const static uint32_t kPipelineInfoTypeMask = 0xffff0000;
+ const static uint32_t kPipelineInfoIDMask = ~kPipelineInfoTypeMask;
+
+ const static uint32_t kPipelineInfoTypeRed = 0x00010000;
+ const static uint32_t kPipelineInfoTypeBad = 0x00020000;
+ const static uint32_t kPipelineInfoTypeNeutral = 0x00040000;
+ const static uint32_t kPipelineInfoTypeGood = 0x00080000;
+
+ enum PipelineFeedbackInfoType
+ {
+ // Used when an HTTP response less than 1.1 is received
+ RedVersionTooLow = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0001,
+
+ // Used when a HTTP Server response header that is on the banned from
+ // pipelining list is received
+ RedBannedServer = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0002,
+
+ // Used when a response is terminated early, when it fails an
+ // integrity check such as assoc-req or when a 304 contained a Last-Modified
+ // differnet than the entry being validated.
+ RedCorruptedContent = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0004,
+
+ // Used when a pipeline is only partly satisfied - for instance if the
+ // server closed the connection after responding to the first
+ // request but left some requests unprocessed.
+ RedCanceledPipeline = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0005,
+
+ // Used when a connection that we expected to stay persistently open
+ // was closed by the server. Not used when simply timed out.
+ BadExplicitClose = kPipelineInfoTypeBad | 0x0003,
+
+ // Used when there is a gap of around 400 - 1200ms in between data being
+ // read from the server
+ BadSlowReadMinor = kPipelineInfoTypeBad | 0x0006,
+
+ // Used when there is a gap of > 1200ms in between data being
+ // read from the server
+ BadSlowReadMajor = kPipelineInfoTypeBad | 0x0007,
+
+ // Used when a response is received that is not framed with either chunked
+ // encoding or a complete content length.
+ BadInsufficientFraming = kPipelineInfoTypeBad | 0x0008,
+
+ // Used when a very large response is recevied in a potential pipelining
+ // context. Large responses cause head of line blocking.
+ BadUnexpectedLarge = kPipelineInfoTypeBad | 0x000B,
+
+ // Used when a response is received that has headers that appear to support
+ // pipelining.
+ NeutralExpectedOK = kPipelineInfoTypeNeutral | 0x0009,
+
+ // Used when a response is received successfully to a pipelined request.
+ GoodCompletedOK = kPipelineInfoTypeGood | 0x000A
+ };
+
+ // called to provide information relevant to the pipelining manager
+ // may be called from any thread
+ void PipelineFeedbackInfo(nsHttpConnectionInfo *,
+ PipelineFeedbackInfoType info,
+ nsHttpConnection *,
+ uint32_t);
+
+ void ReportFailedToProcess(nsIURI *uri);
+
+ // Causes a large amount of connection diagnostic information to be
+ // printed to the javascript console
+ void PrintDiagnostics();
+
+ //-------------------------------------------------------------------------
+ // 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,
+ nsHttpConnection *conn);
+
+ // called to force the transaction queue to be processed once more, giving
+ // preference to the specified connection.
+ nsresult ProcessPendingQ(nsHttpConnectionInfo *);
+ bool ProcessPendingQForEntry(nsHttpConnectionInfo *);
+
+ // Try and process all pending transactions
+ nsresult ProcessPendingQ();
+
+ // 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.
+ nsresult CloseIdleConnection(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);
+
+ bool SupportsPipelining(nsHttpConnectionInfo *);
+
+ bool GetConnectionData(nsTArray<HttpRetParams> *);
+
+ void ResetIPFamilyPreference(nsHttpConnectionInfo *);
+
+ uint16_t MaxRequestDelay() { return mMaxRequestDelay; }
+
+ // public, so that the SPDY/http2 seesions can activate
+ void ActivateTimeoutTick();
+
+private:
+ virtual ~nsHttpConnectionMgr();
+
+ enum PipeliningState {
+ // Host has proven itself pipeline capable through past experience and
+ // large pipeline depths are allowed on multiple connections.
+ PS_GREEN,
+
+ // Not enough information is available yet with this host to be certain
+ // of pipeline capability. Small pipelines on a single connection are
+ // allowed in order to decide whether or not to proceed to green.
+ PS_YELLOW,
+
+ // One or more bad events has happened that indicate that pipelining
+ // to this host (or a particular type of transaction with this host)
+ // is a bad idea. Pipelining is not currently allowed, but time and
+ // other positive experiences will eventually allow it to try again.
+ PS_RED
+ };
+
+ class nsHalfOpenSocket;
+
+ // nsConnectionEntry
+ //
+ // mCT maps connection info hash key to nsConnectionEntry object, which
+ // contains list of active and idle connections as well as the list of
+ // pending transactions.
+ //
+ class nsConnectionEntry
+ {
+ public:
+ explicit nsConnectionEntry(nsHttpConnectionInfo *ci);
+ ~nsConnectionEntry();
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ nsTArray<RefPtr<nsHttpTransaction> > mPendingQ; // pending transaction queue
+ nsTArray<RefPtr<nsHttpConnection> > mActiveConns; // active connections
+ nsTArray<RefPtr<nsHttpConnection> > mIdleConns; // idle persistent connections
+ nsTArray<nsHalfOpenSocket*> mHalfOpens; // half open connections
+
+ bool AvailableForDispatchNow();
+
+ // calculate the number of half open sockets that have not had at least 1
+ // connection complete
+ uint32_t UnconnectedHalfOpens();
+
+ // Remove a particular half open socket from the mHalfOpens array
+ void RemoveHalfOpen(nsHalfOpenSocket *);
+
+ // Pipeline depths for various states
+ const static uint32_t kPipelineUnlimited = 1024; // fully open - extended green
+ const static uint32_t kPipelineOpen = 6; // 6 on each conn - normal green
+ const static uint32_t kPipelineRestricted = 2; // 2 on just 1 conn in yellow
+
+ nsHttpConnectionMgr::PipeliningState PipelineState();
+ void OnPipelineFeedbackInfo(
+ nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+ nsHttpConnection *, uint32_t);
+ bool SupportsPipelining();
+ uint32_t MaxPipelineDepth(nsAHttpTransaction::Classifier classification);
+ void CreditPenalty();
+
+ nsHttpConnectionMgr::PipeliningState mPipelineState;
+
+ void SetYellowConnection(nsHttpConnection *);
+ void OnYellowComplete();
+ uint32_t mYellowGoodEvents;
+ uint32_t mYellowBadEvents;
+ nsHttpConnection *mYellowConnection;
+
+ // initialGreenDepth is the max depth of a pipeline when you first
+ // transition to green. Normally this is kPipelineOpen, but it can
+ // be kPipelineUnlimited in aggressive mode.
+ uint32_t mInitialGreenDepth;
+
+ // greenDepth is the current max allowed depth of a pipeline when
+ // in the green state. Normally this starts as kPipelineOpen and
+ // grows to kPipelineUnlimited after a pipeline of depth 3 has been
+ // successfully transacted.
+ uint32_t mGreenDepth;
+
+ // pipeliningPenalty is the current amount of penalty points this host
+ // entry has earned for participating in events that are not conducive
+ // to good pipelines - such as head of line blocking, canceled pipelines,
+ // etc.. penalties are paid back either through elapsed time or simply
+ // healthy transactions. Having penalty points means that this host is
+ // not currently eligible for pipelines.
+ int16_t mPipeliningPenalty;
+
+ // some penalty points only apply to particular classifications of
+ // transactions - this allows a server that perhaps has head of line
+ // blocking problems on CGI queries to still serve JS pipelined.
+ int16_t mPipeliningClassPenalty[nsAHttpTransaction::CLASS_MAX];
+
+ // for calculating penalty repair credits
+ TimeStamp mLastCreditTime;
+
+ // 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 from the connection information
+ // to build the hash key for hosts in the same ip pool.
+ //
+ // When a set of hosts are coalesced together one of them is marked
+ // mSpdyPreferred. The mapping is maintained in the connection mananger
+ // mSpdyPreferred hash.
+ //
+ 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;
+
+ bool mInPreferredHash : 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;
+
+ // Set the IP family preference flags according the connected family
+ void RecordIPFamilyPreference(uint16_t family);
+ // Resets all flags to their default values
+ void ResetIPFamilyPreference();
+ };
+
+public:
+ static nsAHttpConnection *MakeConnectionHandle(nsHttpConnection *aWrapped);
+
+private:
+
+ // nsHalfOpenSocket is used to hold the state of an opening TCP socket
+ // while we wait for it to establish and bind it to a connection
+
+ class nsHalfOpenSocket final : public nsIOutputStreamCallback,
+ public nsITransportEventSink,
+ public nsIInterfaceRequestor,
+ public nsITimerCallback
+ {
+ ~nsHalfOpenSocket();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITIMERCALLBACK
+
+ nsHalfOpenSocket(nsConnectionEntry *ent,
+ nsAHttpTransaction *trans,
+ uint32_t caps);
+
+ nsresult SetupStreams(nsISocketTransport **,
+ nsIAsyncInputStream **,
+ nsIAsyncOutputStream **,
+ bool isBackup);
+ nsresult SetupPrimaryStreams();
+ 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; }
+ void SetSpeculative(bool val) { mSpeculative = val; }
+
+ bool IsFromPredictor() { return mIsFromPredictor; }
+ void SetIsFromPredictor(bool val) { mIsFromPredictor = val; }
+
+ bool Allow1918() { return mAllow1918; }
+ void SetAllow1918(bool val) { mAllow1918 = val; }
+
+ bool HasConnected() { return mHasConnected; }
+
+ void PrintDiagnostics(nsCString &log);
+ private:
+ nsConnectionEntry *mEnt;
+ 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;
+
+ // 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;
+
+ // for syn retry
+ nsCOMPtr<nsITimer> mSynTimer;
+ nsCOMPtr<nsISocketTransport> mBackupTransport;
+ nsCOMPtr<nsIAsyncOutputStream> mBackupStreamOut;
+ nsCOMPtr<nsIAsyncInputStream> mBackupStreamIn;
+
+ // mHasConnected tracks whether one of the sockets has completed the
+ // connection process. It may have completed unsuccessfully.
+ bool mHasConnected;
+
+ bool mPrimaryConnectedOK;
+ bool mBackupConnectedOK;
+ };
+ friend class nsHalfOpenSocket;
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members may be accessed from any thread (use mReentrantMonitor)
+ //-------------------------------------------------------------------------
+
+ ReentrantMonitor mReentrantMonitor;
+ nsCOMPtr<nsIEventTarget> mSocketThreadTarget;
+
+ // connection limits
+ uint16_t mMaxConns;
+ uint16_t mMaxPersistConnsPerHost;
+ uint16_t mMaxPersistConnsPerProxy;
+ uint16_t mMaxRequestDelay; // in seconds
+ uint16_t mMaxPipelinedRequests;
+ uint16_t mMaxOptimisticPipelinedRequests;
+ Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members are only accessed on the socket transport thread
+ //-------------------------------------------------------------------------
+
+ bool ProcessPendingQForEntry(nsConnectionEntry *, bool considerAll);
+ bool IsUnderPressure(nsConnectionEntry *ent,
+ nsHttpTransaction::Classifier classification);
+ bool AtActiveConnectionLimit(nsConnectionEntry *, uint32_t caps);
+ nsresult TryDispatchTransaction(nsConnectionEntry *ent,
+ bool onlyReusedConnection,
+ nsHttpTransaction *trans);
+ nsresult DispatchTransaction(nsConnectionEntry *,
+ nsHttpTransaction *,
+ nsHttpConnection *);
+ nsresult DispatchAbstractTransaction(nsConnectionEntry *,
+ nsAHttpTransaction *,
+ uint32_t,
+ nsHttpConnection *,
+ int32_t);
+ nsresult BuildPipeline(nsConnectionEntry *,
+ nsAHttpTransaction *,
+ nsHttpPipeline **);
+ bool RestrictConnections(nsConnectionEntry *);
+ nsresult ProcessNewTransaction(nsHttpTransaction *);
+ nsresult EnsureSocketThreadTarget();
+ void ClosePersistentConnections(nsConnectionEntry *ent);
+ void ReportProxyTelemetry(nsConnectionEntry *ent);
+ nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *,
+ uint32_t, bool, bool, bool);
+ void AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
+ void DecrementActiveConnCount(nsHttpConnection *);
+ void StartedConnect();
+ void RecvdConnect();
+
+ nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *,
+ bool allowWildCard);
+
+ nsresult MakeNewConnection(nsConnectionEntry *ent,
+ nsHttpTransaction *trans);
+ bool AddToShortestPipeline(nsConnectionEntry *ent,
+ nsHttpTransaction *trans,
+ nsHttpTransaction::Classifier classification,
+ uint16_t depthLimit);
+
+ // Manage the preferred spdy connection entry for this address
+ nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry);
+ nsConnectionEntry *LookupPreferredHash(nsConnectionEntry *ent);
+ void StorePreferredHash(nsConnectionEntry *ent);
+ void RemovePreferredHash(nsConnectionEntry *ent);
+ nsHttpConnection *GetSpdyPreferredConn(nsConnectionEntry *ent);
+ nsDataHashtable<nsCStringHashKey, nsConnectionEntry *> mSpdyPreferredHash;
+ nsConnectionEntry *LookupConnectionEntry(nsHttpConnectionInfo *ci,
+ nsHttpConnection *conn,
+ nsHttpTransaction *trans);
+
+ void ProcessSpdyPendingQ(nsConnectionEntry *ent);
+
+ // used to marshall events to the socket transport thread.
+ nsresult PostEvent(nsConnEventHandler handler,
+ int32_t iparam = 0,
+ ARefBase *vparam = nullptr);
+
+ // message handlers
+ void OnMsgShutdown (int32_t, ARefBase *);
+ void OnMsgShutdownConfirm (int32_t, ARefBase *);
+ void OnMsgNewTransaction (int32_t, ARefBase *);
+ void OnMsgReschedTransaction (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 OnMsgReclaimConnection (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 *);
+
+ // 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 connections which are a subset of the active conns
+ uint16_t mNumSpdyActiveConns;
+ // 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
+ // nsConnectionEntry object. It is unlocked and therefore must only
+ // be accessed from the socket thread.
+ //
+ nsClassHashtable<nsCStringHashKey, nsConnectionEntry> mCT;
+
+ // Read Timeout Tick handlers
+ void TimeoutTick();
+
+ // For diagnostics
+ void OnMsgPrintDiagnostics(int32_t, ARefBase *);
+
+ nsCString mLogData;
+};
+
+} // 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..235f391bc7
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.cpp
@@ -0,0 +1,722 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Sprintf.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 {
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth <public>
+//-----------------------------------------------------------------------------
+
+nsHttpDigestAuth::nsHttpDigestAuth()
+{}
+
+nsHttpDigestAuth::~nsHttpDigestAuth()
+{}
+
+//-----------------------------------------------------------------------------
+// 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->GetPath(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, 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;
+ 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=%x]\n", 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 nsAFlatCString & nonce,
+ uint16_t qop,
+ const char * nonce_count,
+ const nsAFlatCString & 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.Assign(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 nsAFlatCString & username,
+ const nsAFlatCString & password,
+ const nsAFlatCString & realm,
+ uint16_t algorithm,
+ const nsAFlatCString & nonce,
+ const nsAFlatCString & 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 + 1);
+
+ contents.Assign(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];
+ ExpandToHex(mHashBuf, part1);
+
+ 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 nsAFlatCString & method,
+ const nsAFlatCString & 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..fa15166768
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.h
@@ -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/. */
+
+#ifndef nsDigestAuth_h__
+#define nsDigestAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+#include "nsStringFwd.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsICryptoHash;
+
+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();
+
+ protected:
+ ~nsHttpDigestAuth();
+
+ nsresult ExpandToHex(const char * digest, char * result);
+
+ nsresult CalculateResponse(const char * ha1_digest,
+ const char * ha2_digest,
+ const nsAFlatCString & nonce,
+ uint16_t qop,
+ const char * nonce_count,
+ const nsAFlatCString & cnonce,
+ char * result);
+
+ nsresult CalculateHA1(const nsAFlatCString & username,
+ const nsAFlatCString & password,
+ const nsAFlatCString & realm,
+ uint16_t algorithm,
+ const nsAFlatCString & nonce,
+ const nsAFlatCString & cnonce,
+ char * result);
+
+ nsresult CalculateHA2(const nsAFlatCString & http_method,
+ const nsAFlatCString & http_uri_path,
+ uint16_t qop,
+ const char * body_digest,
+ char * result);
+
+ 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
+ nsresult MD5Hash(const char *buf, uint32_t len);
+
+ nsresult GetMethodAndPath(nsIHttpAuthenticableChannel *,
+ bool, nsCString &, nsCString &);
+
+ // append the quoted version of value to aHeaderLine
+ nsresult AppendQuotedString(const nsACString & value,
+ nsACString & aHeaderLine);
+
+ protected:
+ nsCOMPtr<nsICryptoHash> mVerifier;
+ char mHashBuf[DIGEST_LENGTH];
+};
+
+} // 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..1ddffabffc
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -0,0 +1,2493 @@
+/* -*- 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 "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpChannel.h"
+#include "nsHttpAuthCache.h"
+#include "nsStandardURL.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMNavigator.h"
+#include "nsIMozNavigatorNetwork.h"
+#include "nsINetworkProperties.h"
+#include "nsIHttpChannel.h"
+#include "nsIStandardURL.h"
+#include "LoadContextInfo.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsISocketProviderService.h"
+#include "nsISocketProvider.h"
+#include "nsPrintfCString.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "prprf.h"
+#include "mozilla/Sprintf.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsSocketTransportService2.h"
+#include "nsAlgorithm.h"
+#include "ASpdySession.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "EventTokenBucket.h"
+#include "Tickler.h"
+#include "nsIXULAppInfo.h"
+#include "nsICookieService.h"
+#include "nsIObserverService.h"
+#include "nsISiteSecurityService.h"
+#include "nsIStreamConverterService.h"
+#include "nsITimer.h"
+#include "nsCRT.h"
+#include "nsIMemoryReporter.h"
+#include "nsIParentalControlsService.h"
+#include "nsPIDOMWindow.h"
+#include "nsINetworkLinkService.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsIOService.h"
+#include "nsIUUIDGenerator.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/BasePrincipal.h"
+
+#include "mozilla/dom/ContentParent.h"
+
+#if defined(XP_UNIX)
+#include <sys/utsname.h>
+#endif
+
+#if defined(XP_WIN)
+#include <windows.h>
+#endif
+
+#if defined(XP_MACOSX)
+#include <CoreServices/CoreServices.h>
+#include "nsCocoaFeatures.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 DONOTTRACK_HEADER_ENABLED "privacy.donottrackheader.enabled"
+#define H2MANDATORY_SUITE "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256"
+#define TELEMETRY_ENABLED "toolkit.telemetry.enabled"
+#define ALLOW_EXPERIMENTS "network.allow-experiments"
+#define SAFE_HINT_HEADER_VALUE "safeHint.enabled"
+#define SECURITY_PREFIX "security."
+#define NEW_TAB_REMOTE_MODE "browser.newtabpage.remote.mode"
+
+#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)
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gHttpLog("nsHttp");
+
+static nsresult
+NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ int32_t aDefaultPort,
+ nsIURI **aURI)
+{
+ RefPtr<nsStandardURL> url = new nsStandardURL();
+
+ nsresult rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY,
+ aDefaultPort, aSpec, aCharset, aBaseURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ url.forget(aURI);
+ return NS_OK;
+}
+
+#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(NS_LITERAL_STRING("device"), 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(NS_LITERAL_CSTRING("%DEVICEID%"), deviceModelId);
+ return deviceString;
+ }
+ return deviceModelId;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler <public>
+//-----------------------------------------------------------------------------
+
+nsHttpHandler *gHttpHandler = nullptr;
+
+nsHttpHandler::nsHttpHandler()
+ : mHttpVersion(NS_HTTP_VERSION_1_1)
+ , mProxyHttpVersion(NS_HTTP_VERSION_1_1)
+ , mCapabilities(NS_HTTP_ALLOW_KEEPALIVE)
+ , mReferrerLevel(0xff) // by default we always send a referrer
+ , mSpoofReferrerSource(false)
+ , mReferrerTrimmingPolicy(0)
+ , mReferrerXOriginTrimmingPolicy(0)
+ , mReferrerXOriginPolicy(0)
+ , mFastFallbackToIPv4(false)
+ , mProxyPipelining(true)
+ , mIdleTimeout(PR_SecondsToInterval(10))
+ , mSpdyTimeout(PR_SecondsToInterval(180))
+ , mResponseTimeout(PR_SecondsToInterval(300))
+ , mResponseTimeoutEnabled(false)
+ , mNetworkChangedTimeout(5000)
+ , mMaxRequestAttempts(6)
+ , mMaxRequestDelay(10)
+ , mIdleSynTimeout(250)
+ , mH2MandatorySuiteEnabled(false)
+ , mPipeliningEnabled(false)
+ , mMaxConnections(24)
+ , mMaxPersistentConnectionsPerServer(2)
+ , mMaxPersistentConnectionsPerProxy(4)
+ , mMaxPipelinedRequests(32)
+ , mMaxOptimisticPipelinedRequests(4)
+ , mPipelineAggressive(false)
+ , mMaxPipelineObjectSize(300000)
+ , mPipelineRescheduleOnTimeout(true)
+ , mPipelineRescheduleTimeout(PR_MillisecondsToInterval(1500))
+ , mPipelineReadTimeout(PR_MillisecondsToInterval(30000))
+ , mRedirectionLimit(10)
+ , mPhishyUserPassLength(1)
+ , mQoSBits(0x00)
+ , mPipeliningOverSSL(false)
+ , mEnforceAssocReq(false)
+ , mLastUniqueID(NowInSeconds())
+ , mSessionStartTime(0)
+ , mLegacyAppName("Mozilla")
+ , mLegacyAppVersion("5.0")
+ , mProduct("Gecko")
+ , mCompatFirefoxEnabled(false)
+ , mUserAgentIsDirty(true)
+ , mPromptTempRedirect(true)
+ , mEnablePersistentHttpsCaching(false)
+ , mDoNotTrackEnabled(false)
+ , mSafeHintEnabled(false)
+ , mParentalControlEnabled(false)
+ , mHandlerActive(false)
+ , mTelemetryEnabled(false)
+ , mAllowExperiments(true)
+ , mDebugObservations(false)
+ , mEnableSpdy(false)
+ , mHttp2Enabled(true)
+ , mUseH2Deps(true)
+ , mEnforceHttp2TlsProfile(true)
+ , mCoalesceSpdy(true)
+ , mSpdyPersistentSettings(false)
+ , mAllowPush(true)
+ , mEnableAltSvc(false)
+ , mEnableAltSvcOE(false)
+ , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
+ , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize)
+ , mSpdyPushAllowance(32768)
+ , mSpdyPullAllowance(ASpdySession::kInitialRwin)
+ , mDefaultSpdyConcurrent(ASpdySession::kDefaultMaxConcurrent)
+ , mSpdyPingThreshold(PR_SecondsToInterval(58))
+ , mSpdyPingTimeout(PR_SecondsToInterval(8))
+ , mConnectTimeout(90000)
+ , 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)
+ , mKeepEmptyResponseHeadersAsEmtpyString(false)
+ , mDefaultHpackBuffer(4096)
+ , mMaxHttpResponseHeaderSize(393216)
+{
+ LOG(("Creating nsHttpHandler [this=%p].\n", this));
+
+ MOZ_ASSERT(!gHttpHandler, "HTTP handler already created!");
+ gHttpHandler = this;
+}
+
+nsHttpHandler::~nsHttpHandler()
+{
+ LOG(("Deleting nsHttpHandler [this=%p]\n", this));
+
+ // make sure the connection manager is shutdown
+ if (mConnMgr) {
+ mConnMgr->Shutdown();
+ 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();
+ if (mPipelineTestTimer) {
+ mPipelineTestTimer->Cancel();
+ mPipelineTestTimer = nullptr;
+ }
+
+ gHttpHandler = 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>(service);
+
+ if (IsNeckoChild())
+ NeckoChild::InitNeckoChild();
+
+ InitUserAgentComponents();
+
+ // monitor some preference changes
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ prefBranch->AddObserver(HTTP_PREF_PREFIX, this, true);
+ prefBranch->AddObserver(UA_PREF_PREFIX, this, true);
+ prefBranch->AddObserver(INTL_ACCEPT_LANGUAGES, this, true);
+ prefBranch->AddObserver(BROWSER_PREF("disk_cache_ssl"), this, true);
+ prefBranch->AddObserver(DONOTTRACK_HEADER_ENABLED, this, true);
+ prefBranch->AddObserver(TELEMETRY_ENABLED, this, true);
+ prefBranch->AddObserver(H2MANDATORY_SUITE, this, true);
+ prefBranch->AddObserver(HTTP_PREF("tcp_keepalive.short_lived_connections"), this, true);
+ prefBranch->AddObserver(HTTP_PREF("tcp_keepalive.long_lived_connections"), this, true);
+ prefBranch->AddObserver(SAFE_HINT_HEADER_VALUE, this, true);
+ prefBranch->AddObserver(SECURITY_PREFIX, this, true);
+ prefBranch->AddObserver(NEW_TAB_REMOTE_MODE, this, true);
+ PrefsChanged(prefBranch, nullptr);
+ }
+
+ nsHttpChannelAuthProvider::InitializePrefs();
+
+ 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);
+ }
+
+ mSessionStartTime = NowInSeconds();
+ mHandlerActive = true;
+
+ rv = mAuthCache.Init();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mPrivateAuthCache.Init();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = InitConnectionMgr();
+ if (NS_FAILED(rv)) return rv;
+
+ mRequestContextService =
+ do_GetService("@mozilla.org/network/request-context-service;1");
+
+#if defined(ANDROID) || defined(MOZ_MULET)
+ mProductSub.AssignLiteral(MOZILLA_UAVERSION);
+#else
+ mProductSub.AssignLiteral("20100101");
+#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 = services::GetObserverService();
+ 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:failed-to-process-uri-content", true);
+ obsService->AddObserver(this, "last-pb-context-exited", true);
+ obsService->AddObserver(this, "webapps-clear-data", true);
+ obsService->AddObserver(this, "browser:purge-session-history", true);
+ obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
+ obsService->AddObserver(this, "application-background", 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;
+}
+
+void
+nsHttpHandler::MakeNewRequestTokenBucket()
+{
+ LOG(("nsHttpHandler::MakeNewRequestTokenBucket this=%p child=%d\n",
+ this, IsNeckoChild()));
+ if (!mConnMgr || IsNeckoChild()) {
+ return;
+ }
+ RefPtr<EventTokenBucket> tokenBucket =
+ new EventTokenBucket(RequestTokenBucketHz(), RequestTokenBucketBurst());
+ mConnMgr->UpdateRequestTokenBucket(tokenBucket);
+}
+
+nsresult
+nsHttpHandler::InitConnectionMgr()
+{
+ // Init ConnectionManager only on parent!
+ if (IsNeckoChild()) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ if (!mConnMgr) {
+ mConnMgr = new nsHttpConnectionMgr();
+ }
+
+ rv = mConnMgr->Init(mMaxConnections,
+ mMaxPersistentConnectionsPerServer,
+ mMaxPersistentConnectionsPerProxy,
+ mMaxRequestDelay,
+ mMaxPipelinedRequests,
+ mMaxOptimisticPipelinedRequests);
+ return rv;
+}
+
+nsresult
+nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure)
+{
+ 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.
+ rv = request->SetHeader(nsHttp::Accept, mAccept,
+ false, nsHttpHeaderArray::eVarietyRequestOverride);
+ if (NS_FAILED(rv)) return rv;
+
+ // Add the "Accept-Language" header. This header is also exposed to the
+ // service worker.
+ if (!mAcceptLanguages.IsEmpty()) {
+ // Add the "Accept-Language" header
+ 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, NS_LITERAL_CSTRING("safe"),
+ 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
+
+ NS_NAMED_LITERAL_CSTRING(close, "close");
+ NS_NAMED_LITERAL_CSTRING(keepAlive, "keep-alive");
+
+ const nsACString *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;
+}
+
+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>(service);
+ }
+ *result = mStreamConvSvc;
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+nsISiteSecurityService*
+nsHttpHandler::GetSSService()
+{
+ if (!mSSService) {
+ nsCOMPtr<nsISiteSecurityService> service = do_GetService(NS_SSSERVICE_CONTRACTID);
+ mSSService = new nsMainThreadPtrHolder<nsISiteSecurityService>(service);
+ }
+ return mSSService;
+}
+
+nsICookieService *
+nsHttpHandler::GetCookieService()
+{
+ if (!mCookieService) {
+ nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ mCookieService = new nsMainThreadPtrHolder<nsICookieService>(service);
+ }
+ return mCookieService;
+}
+
+nsresult
+nsHttpHandler::GetIOService(nsIIOService** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ NS_ADDREF(*result = mIOService);
+ return NS_OK;
+}
+
+uint32_t
+nsHttpHandler::Get32BitsOfPseudoRandom()
+{
+ // only confirm rand seeding on socket thread
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // rand() provides different amounts of PRNG on different platforms.
+ // 15 or 31 bits are common amounts.
+
+ static_assert(RAND_MAX >= 0xfff, "RAND_MAX should be >= 12 bits");
+
+#if RAND_MAX < 0xffffU
+ return ((uint16_t) rand() << 20) |
+ (((uint16_t) rand() & 0xfff) << 8) |
+ ((uint16_t) rand() & 0xff);
+#elif RAND_MAX < 0xffffffffU
+ return ((uint16_t) rand() << 16) | ((uint16_t) rand() & 0xffff);
+#else
+ return (uint32_t) rand();
+#endif
+}
+
+void
+nsHttpHandler::NotifyObservers(nsIHttpChannel *chan, const char *event)
+{
+ LOG(("nsHttpHandler::NotifyObservers [chan=%x 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)
+{
+ // TODO E10S This helper has to be initialized on the other process
+ RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
+ new nsAsyncRedirectVerifyHelper();
+
+ return redirectCallbackHelper->Init(oldChan, newChan, flags);
+}
+
+/* static */ nsresult
+nsHttpHandler::GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine)
+{
+ return NS_GenerateHostPort(host, port, hostLine);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler <private>
+//-----------------------------------------------------------------------------
+
+const nsAFlatCString &
+nsHttpHandler::UserAgent()
+{
+ if (mUserAgentOverride) {
+ 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 WNT_BASE "Windows NT %ld.%ld"
+#define W64_PREFIX "; Win64"
+#endif
+
+void
+nsHttpHandler::InitUserAgentComponents()
+{
+#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(
+ NS_LITERAL_STRING("release_version"), 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(NS_LITERAL_STRING("tablet"), &isTablet);
+ if (NS_SUCCEEDED(rv) && isTablet) {
+ mCompatDevice.AssignLiteral("Tablet");
+ } else {
+ bool isTV;
+ rv = infoService->GetPropertyAsBool(NS_LITERAL_STRING("tv"), &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
+
+#ifdef MOZ_MULET
+ {
+ // Add the `Mobile` or `Tablet` or `TV` token when running in the b2g
+ // desktop simulator via preference.
+ nsCString deviceType;
+ nsresult rv = Preferences::GetCString("devtools.useragent.device_type", &deviceType);
+ if (NS_SUCCEEDED(rv)) {
+ mCompatDevice.Assign(deviceType);
+ } else {
+ mCompatDevice.AssignLiteral("Mobile");
+ }
+ }
+#endif // MOZ_MULET
+
+#if defined(MOZ_WIDGET_GONK)
+ // Device model identifier should be a simple token, which can be composed
+ // of letters, numbers, hyphen ("-") and dot (".").
+ // Any other characters means the identifier is invalid and ignored.
+ nsCString deviceId;
+ rv = Preferences::GetCString("general.useragent.device_id", &deviceId);
+ if (NS_SUCCEEDED(rv)) {
+ bool valid = true;
+ deviceId.Trim(" ", true, true);
+ for (size_t i = 0; i < deviceId.Length(); i++) {
+ char c = deviceId.CharAt(i);
+ if (!(isalnum(c) || c == '-' || c == '.')) {
+ valid = false;
+ break;
+ }
+ }
+ if (valid) {
+ mDeviceModelId = deviceId;
+ } else {
+ LOG(("nsHttpHandler: Ignore invalid device ID: [%s]\n",
+ deviceId.get()));
+ }
+ }
+#endif
+
+#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_IA64
+ format = WNT_BASE W64_PREFIX "; IA64";
+#elif defined _M_X64 || defined _M_AMD64
+ format = WNT_BASE W64_PREFIX "; x64";
+#else
+ BOOL isWow64 = FALSE;
+ if (!IsWow64Process(GetCurrentProcess(), &isWow64)) {
+ isWow64 = FALSE;
+ }
+ format = isWow64
+ ? WNT_BASE "; WOW64"
+ : WNT_BASE;
+#endif
+ char *buf = PR_smprintf(format,
+ info.dwMajorVersion,
+ info.dwMinorVersion);
+ if (buf) {
+ mOscpu = buf;
+ PR_smprintf_free(buf);
+ }
+ }
+#elif defined (XP_MACOSX)
+#if defined(__ppc__)
+ mOscpu.AssignLiteral("PPC Mac OS X");
+#elif defined(__i386__) || defined(__x86_64__)
+ mOscpu.AssignLiteral("Intel Mac OS X");
+#endif
+ SInt32 majorVersion = nsCocoaFeatures::OSXVersionMajor();
+ SInt32 minorVersion = nsCocoaFeatures::OSXVersionMinor();
+ mOscpu += nsPrintfCString(" %d.%d", majorVersion, minorVersion);
+#elif defined (XP_UNIX)
+ struct utsname name;
+
+ int ret = uname(&name);
+ if (ret >= 0) {
+ nsAutoCString buf;
+ buf = (char*)name.sysname;
+
+ if (strcmp(name.machine, "x86_64") == 0 &&
+ sizeof(void *) == sizeof(int32_t)) {
+ // We're running 32-bit code on x86_64. Make this browser
+ // look like it's running on i686 hardware, but append "
+ // (x86_64)" to the end of the oscpu identifier to be able
+ // to differentiate this from someone running 64-bit code
+ // on x86_64..
+
+ buf += " i686 on x86_64";
+ } else {
+ 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;
+}
+
+void
+nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
+{
+ nsresult rv = NS_OK;
+ int32_t val;
+
+ LOG(("nsHttpHandler::PrefsChanged [pref=%s]\n", 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) {
+ mConnMgr->DoShiftReloadConnectionCleanup(nullptr);
+ mConnMgr->PruneDeadConnections();
+ }
+ }
+
+ //
+ // UA components
+ //
+
+ bool cVar = false;
+
+ if (PREF_CHANGED(UA_PREF("compatMode.firefox"))) {
+ rv = prefs->GetBoolPref(UA_PREF("compatMode.firefox"), &cVar);
+ mCompatFirefoxEnabled = (NS_SUCCEEDED(rv) && cVar);
+ mUserAgentIsDirty = true;
+ }
+
+ // general.useragent.override
+ if (PREF_CHANGED(UA_PREF("override"))) {
+ prefs->GetCharPref(UA_PREF("override"),
+ getter_Copies(mUserAgentOverride));
+ mUserAgentIsDirty = true;
+ }
+
+#ifdef ANDROID
+ // general.useragent.use_device
+ if (PREF_CHANGED(UA_PREF("use_device"))) {
+ if (Preferences::GetBool(UA_PREF("use_device"), false)) {
+ mDeviceModelId = mozilla::net::GetDeviceModelId();
+ } else {
+ mDeviceModelId = EmptyCString();
+ }
+ mUserAgentIsDirty = true;
+ }
+#endif
+
+ //
+ // HTTP options
+ //
+
+ if (PREF_CHANGED(HTTP_PREF("keep-alive.timeout"))) {
+ rv = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetIntPref(HTTP_PREF("request.max-start-delay"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxRequestDelay = (uint16_t) clamped(val, 0, 0xffff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_REQUEST_DELAY,
+ mMaxRequestDelay);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("response.timeout"))) {
+ rv = prefs->GetIntPref(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 = prefs->GetIntPref(HTTP_PREF("network-changed.timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mNetworkChangedTimeout = clamped(val, 1, 600) * 1000;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-connections"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("max-connections"), &val);
+ if (NS_SUCCEEDED(rv)) {
+
+ mMaxConnections = (uint16_t) clamped((uint32_t)val,
+ (uint32_t)1, MaxSocketCount());
+
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_CONNECTIONS,
+ mMaxConnections);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-server"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("max-persistent-connections-per-server"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPersistentConnectionsPerServer = (uint8_t) clamped(val, 1, 0xff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_HOST,
+ mMaxPersistentConnectionsPerServer);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-proxy"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("max-persistent-connections-per-proxy"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPersistentConnectionsPerProxy = (uint8_t) clamped(val, 1, 0xff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
+ mMaxPersistentConnectionsPerProxy);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("sendRefererHeader"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("sendRefererHeader"), &val);
+ if (NS_SUCCEEDED(rv))
+ mReferrerLevel = (uint8_t) clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("referer.spoofSource"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("referer.spoofSource"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mSpoofReferrerSource = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("referer.trimmingPolicy"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("referer.trimmingPolicy"), &val);
+ if (NS_SUCCEEDED(rv))
+ mReferrerTrimmingPolicy = (uint8_t) clamped(val, 0, 2);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("referer.XOriginTrimmingPolicy"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("referer.XOriginTrimmingPolicy"), &val);
+ if (NS_SUCCEEDED(rv))
+ mReferrerXOriginTrimmingPolicy = (uint8_t) clamped(val, 0, 2);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("referer.XOriginPolicy"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("referer.XOriginPolicy"), &val);
+ if (NS_SUCCEEDED(rv))
+ mReferrerXOriginPolicy = (uint8_t) clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("redirection-limit"))) {
+ rv = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetBoolPref(HTTP_PREF("fast-fallback-to-IPv4"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mFastFallbackToIPv4 = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("version"))) {
+ nsXPIDLCString httpVersion;
+ prefs->GetCharPref(HTTP_PREF("version"), getter_Copies(httpVersion));
+ if (httpVersion) {
+ if (!PL_strcmp(httpVersion, "1.1"))
+ mHttpVersion = NS_HTTP_VERSION_1_1;
+ else if (!PL_strcmp(httpVersion, "0.9"))
+ mHttpVersion = NS_HTTP_VERSION_0_9;
+ else
+ mHttpVersion = NS_HTTP_VERSION_1_0;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("proxy.version"))) {
+ nsXPIDLCString httpVersion;
+ prefs->GetCharPref(HTTP_PREF("proxy.version"), getter_Copies(httpVersion));
+ if (httpVersion) {
+ if (!PL_strcmp(httpVersion, "1.1"))
+ mProxyHttpVersion = NS_HTTP_VERSION_1_1;
+ else
+ mProxyHttpVersion = NS_HTTP_VERSION_1_0;
+ // it does not make sense to issue a HTTP/0.9 request to a proxy server
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ if (cVar)
+ mCapabilities |= NS_HTTP_ALLOW_PIPELINING;
+ else
+ mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+ mPipeliningEnabled = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.maxrequests"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxrequests"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPipelinedRequests = clamped(val, 1, 0xffff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PIPELINED_REQUESTS,
+ mMaxPipelinedRequests);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.max-optimistic-requests"))) {
+ rv = prefs->
+ GetIntPref(HTTP_PREF("pipelining.max-optimistic-requests"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxOptimisticPipelinedRequests = clamped(val, 1, 0xffff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam
+ (nsHttpConnectionMgr::MAX_OPTIMISTIC_PIPELINED_REQUESTS,
+ mMaxOptimisticPipelinedRequests);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.aggressive"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining.aggressive"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mPipelineAggressive = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.maxsize"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxsize"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPipelineObjectSize =
+ static_cast<int64_t>(clamped(val, 1000, 100000000));
+ }
+ }
+
+ // Determines whether or not to actually reschedule after the
+ // reschedule-timeout has expired
+ if (PREF_CHANGED(HTTP_PREF("pipelining.reschedule-on-timeout"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining.reschedule-on-timeout"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mPipelineRescheduleOnTimeout = cVar;
+ }
+
+ // The amount of time head of line blocking is allowed (in ms)
+ // before the blocked transactions are moved to another pipeline
+ if (PREF_CHANGED(HTTP_PREF("pipelining.reschedule-timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pipelining.reschedule-timeout"),
+ &val);
+ if (NS_SUCCEEDED(rv)) {
+ mPipelineRescheduleTimeout =
+ PR_MillisecondsToInterval((uint16_t) clamped(val, 500, 0xffff));
+ }
+ }
+
+ // The amount of time a pipelined transaction is allowed to wait before
+ // being canceled and retried in a non-pipeline connection
+ if (PREF_CHANGED(HTTP_PREF("pipelining.read-timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pipelining.read-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mPipelineReadTimeout =
+ PR_MillisecondsToInterval((uint16_t) clamped(val, 5000,
+ 0xffff));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.ssl"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining.ssl"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mPipeliningOverSSL = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("proxy.pipelining"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("proxy.pipelining"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mProxyPipelining = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("qos"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("qos"), &val);
+ if (NS_SUCCEEDED(rv))
+ mQoSBits = (uint8_t) clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept.default"))) {
+ nsXPIDLCString accept;
+ rv = prefs->GetCharPref(HTTP_PREF("accept.default"),
+ getter_Copies(accept));
+ if (NS_SUCCEEDED(rv))
+ SetAccept(accept);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept-encoding"))) {
+ nsXPIDLCString acceptEncodings;
+ rv = prefs->GetCharPref(HTTP_PREF("accept-encoding"),
+ getter_Copies(acceptEncodings));
+ if (NS_SUCCEEDED(rv)) {
+ SetAcceptEncodings(acceptEncodings, false);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept-encoding.secure"))) {
+ nsXPIDLCString acceptEncodings;
+ rv = prefs->GetCharPref(HTTP_PREF("accept-encoding.secure"),
+ getter_Copies(acceptEncodings));
+ if (NS_SUCCEEDED(rv)) {
+ SetAcceptEncodings(acceptEncodings, true);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("default-socket-type"))) {
+ nsXPIDLCString sval;
+ rv = prefs->GetCharPref(HTTP_PREF("default-socket-type"),
+ getter_Copies(sval));
+ if (NS_SUCCEEDED(rv)) {
+ if (sval.IsEmpty())
+ mDefaultSocketType.Adopt(0);
+ else {
+ // verify that this socket type is actually valid
+ nsCOMPtr<nsISocketProviderService> sps(
+ do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID));
+ if (sps) {
+ nsCOMPtr<nsISocketProvider> sp;
+ rv = sps->GetSocketProvider(sval, 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 = prefs->GetBoolPref(HTTP_PREF("prompt-temp-redirect"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mPromptTempRedirect = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("assoc-req.enforce"))) {
+ cVar = false;
+ rv = prefs->GetBoolPref(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 = prefs->GetBoolPref(BROWSER_PREF("disk_cache_ssl"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnablePersistentHttpsCaching = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("phishy-userpass-length"))) {
+ rv = prefs->GetIntPref(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 = prefs->GetBoolPref(HTTP_PREF("spdy.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnableSpdy = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enabled.http2"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.http2"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mHttp2Enabled = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enabled.deps"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.deps"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mUseH2Deps = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enforce-tls-profile"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.enforce-tls-profile"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnforceHttp2TlsProfile = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.coalesce-hostnames"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mCoalesceSpdy = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.persistent-settings"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.persistent-settings"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mSpdyPersistentSettings = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.timeout"))) {
+ rv = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetBoolPref(HTTP_PREF("spdy.allow-push"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mAllowPush = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("altsvc.enabled"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("altsvc.enabled"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnableAltSvc = cVar;
+ }
+
+
+ if (PREF_CHANGED(HTTP_PREF("altsvc.oe"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("altsvc.oe"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnableAltSvcOE = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.push-allowance"))) {
+ rv = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetIntPref(HTTP_PREF("spdy.send-buffer-size"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdySendBufferSize = (uint32_t) clamped(val, 1500, 0x7fffffff);
+ }
+
+ // The maximum amount of time to wait for socket transport to be
+ // established
+ if (PREF_CHANGED(HTTP_PREF("connection-timeout"))) {
+ rv = prefs->GetIntPref(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 number of current global half open sockets allowable
+ // for starting a new speculative connection.
+ if (PREF_CHANGED(HTTP_PREF("speculative-parallel-limit"))) {
+ rv = prefs->GetIntPref(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 = prefs->GetBoolPref(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 = prefs->GetBoolPref(HTTP_PREF("diagnostics"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ if (mConnMgr)
+ mConnMgr->PrintDiagnostics();
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max_response_header_size"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("max_response_header_size"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxHttpResponseHeaderSize = val;
+ }
+ }
+ //
+ // INTL options
+ //
+
+ if (PREF_CHANGED(INTL_ACCEPT_LANGUAGES)) {
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ prefs->GetComplexValue(INTL_ACCEPT_LANGUAGES,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(pls));
+ if (pls) {
+ nsXPIDLString uval;
+ pls->ToString(getter_Copies(uval));
+ if (uval)
+ SetAcceptLanguages(NS_ConvertUTF16toUTF8(uval).get());
+ }
+ }
+
+ //
+ // Tracking options
+ //
+
+ if (PREF_CHANGED(DONOTTRACK_HEADER_ENABLED)) {
+ cVar = false;
+ rv = prefs->GetBoolPref(DONOTTRACK_HEADER_ENABLED, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mDoNotTrackEnabled = cVar;
+ }
+ }
+ // Hint option
+ if (PREF_CHANGED(SAFE_HINT_HEADER_VALUE)) {
+ cVar = false;
+ rv = prefs->GetBoolPref(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;
+
+ //
+ // Telemetry
+ //
+
+ if (PREF_CHANGED(TELEMETRY_ENABLED)) {
+ cVar = false;
+ requestTokenBucketUpdated = true;
+ rv = prefs->GetBoolPref(TELEMETRY_ENABLED, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mTelemetryEnabled = cVar;
+ }
+ }
+
+ // "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256" is the required h2 interop
+ // suite.
+
+ if (PREF_CHANGED(H2MANDATORY_SUITE)) {
+ cVar = false;
+ rv = prefs->GetBoolPref(H2MANDATORY_SUITE, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mH2MandatorySuiteEnabled = cVar;
+ }
+ }
+
+ //
+ // network.allow-experiments
+ //
+ if (PREF_CHANGED(ALLOW_EXPERIMENTS)) {
+ cVar = true;
+ requestTokenBucketUpdated = true;
+ rv = prefs->GetBoolPref(ALLOW_EXPERIMENTS, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mAllowExperiments = cVar;
+ }
+ }
+
+ // network.http.debug-observations
+ if (PREF_CHANGED("network.http.debug-observations")) {
+ cVar = false;
+ rv = prefs->GetBoolPref("network.http.debug-observations", &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mDebugObservations = cVar;
+ }
+ }
+
+ //
+ // Test HTTP Pipelining (bug796192)
+ // If experiments are allowed and pipelining is Off,
+ // turn it On for just 10 minutes
+ //
+ if (mAllowExperiments && !mPipeliningEnabled &&
+ PREF_CHANGED(HTTP_PREF("pipelining.abtest"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining.abtest"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ // If option is enabled, only test for ~1% of sessions
+ if (cVar && !(rand() % 128)) {
+ mCapabilities |= NS_HTTP_ALLOW_PIPELINING;
+ if (mPipelineTestTimer)
+ mPipelineTestTimer->Cancel();
+ mPipelineTestTimer =
+ do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mPipelineTestTimer->InitWithFuncCallback(
+ TimerCallback, this, 10*60*1000, // 10 minutes
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ } else {
+ mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+ if (mPipelineTestTimer) {
+ mPipelineTestTimer->Cancel();
+ mPipelineTestTimer = nullptr;
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.enabled"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pacing.requests.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketEnabled = cVar;
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.min-parallelism"))) {
+ rv = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetIntPref(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 = prefs->GetBoolPref(
+ 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 = prefs->GetIntPref(
+ 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 = prefs->GetIntPref(
+ 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 = prefs->GetBoolPref(
+ 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 = prefs->GetIntPref(
+ 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")) ) {
+ rv = prefs->GetBoolPref(HTTP_PREF("enforce-framing.http1"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_STRICT;
+ } else {
+ rv = prefs->GetBoolPref(HTTP_PREF("enforce-framing.soft"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_BARELY;
+ } else {
+ mEnforceH1Framing = FRAMECHECK_LAX;
+ }
+ }
+ }
+
+ // remote content-signature testing option
+ if (PREF_CHANGED(NEW_TAB_REMOTE_MODE)) {
+ nsAutoCString channel;
+ prefs->GetCharPref(NEW_TAB_REMOTE_MODE, getter_Copies(channel));
+ if (channel.EqualsLiteral("test") ||
+ channel.EqualsLiteral("test2") ||
+ channel.EqualsLiteral("dev")) {
+ mNewTabContentSignaturesDisabled = true;
+ } else {
+ mNewTabContentSignaturesDisabled = false;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("keep_empty_response_headers_as_empty_string"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("keep_empty_response_headers_as_empty_string"),
+ &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mKeepEmptyResponseHeadersAsEmtpyString = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.hpack-default-buffer"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.default-hpack-buffer"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mDefaultHpackBuffer = val;
+ }
+ }
+
+ // Enable HTTP response timeout if TCP Keepalives are disabled.
+ mResponseTimeoutEnabled = !mTCPKeepaliveShortLivedEnabled &&
+ !mTCPKeepaliveLongLivedEnabled;
+
+#undef PREF_CHANGED
+#undef MULTI_PREF_CHANGED
+}
+
+
+/**
+ * Static method called by mPipelineTestTimer when it expires.
+ */
+void
+nsHttpHandler::TimerCallback(nsITimer * aTimer, void * aClosure)
+{
+ RefPtr<nsHttpHandler> thisObject = static_cast<nsHttpHandler*>(aClosure);
+ if (!thisObject->mPipeliningEnabled)
+ thisObject->mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+}
+
+/**
+ * Currently, only regularizes the case of subtags.
+ */
+static void
+CanonicalizeLanguageTag(char *languageTag)
+{
+ char *s = languageTag;
+ while (*s != '\0') {
+ *s = nsCRT::ToLower(*s);
+ s++;
+ }
+
+ s = languageTag;
+ bool isFirst = true;
+ bool seenSingleton = false;
+ while (*s != '\0') {
+ char *subTagEnd = strchr(s, '-');
+ if (subTagEnd == nullptr) {
+ subTagEnd = strchr(s, '\0');
+ }
+
+ if (isFirst) {
+ isFirst = false;
+ } else if (seenSingleton) {
+ // Do nothing
+ } else {
+ size_t subTagLength = subTagEnd - s;
+ if (subTagLength == 1) {
+ seenSingleton = true;
+ } else if (subTagLength == 2) {
+ *s = nsCRT::ToUpper(*s);
+ *(s + 1) = nsCRT::ToUpper(*(s + 1));
+ } else if (subTagLength == 4) {
+ *s = nsCRT::ToUpper(*s);
+ }
+ }
+
+ s = subTagEnd;
+ if (*s != '\0') {
+ s++;
+ }
+ }
+}
+
+/**
+ * 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;
+
+ uint32_t n, count_n, size, wrote;
+ double q, dec;
+ char *p, *p2, *token, *q_Accept, *o_Accept;
+ const char *comma;
+ int32_t available;
+
+ o_Accept = strdup(i_AcceptLanguages);
+ if (!o_Accept)
+ return NS_ERROR_OUT_OF_MEMORY;
+ for (p = o_Accept, n = size = 0; '\0' != *p; p++) {
+ if (*p == ',') n++;
+ size++;
+ }
+
+ available = size + ++n * 11 + 1;
+ q_Accept = new char[available];
+ if (!q_Accept) {
+ free(o_Accept);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *q_Accept = '\0';
+ q = 1.0;
+ dec = q / (double) n;
+ count_n = 0;
+ p2 = q_Accept;
+ for (token = nsCRT::strtok(o_Accept, ",", &p);
+ token != (char *) 0;
+ token = nsCRT::strtok(p, ",", &p))
+ {
+ token = net_FindCharNotInSet(token, HTTP_LWS);
+ char* trim;
+ trim = net_FindCharInSet(token, ";" HTTP_LWS);
+ if (trim != (char*)0) // remove "; q=..." if present
+ *trim = '\0';
+
+ if (*token != '\0') {
+ CanonicalizeLanguageTag(token);
+
+ comma = count_n++ != 0 ? "," : ""; // delimiter if not first item
+ uint32_t u = QVAL_TO_UINT(q);
+
+ // Only display q-value if less than 1.00.
+ if (u < 100) {
+ const char *qval_str;
+
+ // 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)) {
+ u = (u + 5) / 10;
+ qval_str = "%s%s;q=0.%u";
+ } else {
+ // Values below 10 require zero padding.
+ qval_str = "%s%s;q=0.%02u";
+ }
+
+ wrote = snprintf(p2, available, qval_str, comma, token, u);
+ } else {
+ wrote = snprintf(p2, available, "%s%s", comma, token);
+ }
+
+ q -= dec;
+ p2 += wrote;
+ available -= wrote;
+ MOZ_ASSERT(available > 0, "allocated string not long enough");
+ }
+ }
+ free(o_Accept);
+
+ o_AcceptLanguages.Assign((const char *) q_Accept);
+ delete [] q_Accept;
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpHandler::SetAcceptLanguages(const char *aAcceptLanguages)
+{
+ nsAutoCString buf;
+ nsresult rv = PrepareAcceptLanguages(aAcceptLanguages, buf);
+ if (NS_SUCCEEDED(rv))
+ mAcceptLanguages.Assign(buf);
+ return rv;
+}
+
+nsresult
+nsHttpHandler::SetAccept(const char *aAccept)
+{
+ mAccept = aAccept;
+ return NS_OK;
+}
+
+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::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **aURI)
+{
+ return mozilla::net::NewURI(aSpec, aCharset, aBaseURI, NS_HTTP_DEFAULT_PORT, aURI);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ LOG(("nsHttpHandler::NewChannel\n"));
+
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(result);
+
+ bool isHttp = false, isHttps = false;
+
+ // Verify that we have been given a valid scheme
+ nsresult rv = uri->SchemeIs("http", &isHttp);
+ if (NS_FAILED(rv)) return rv;
+ if (!isHttp) {
+ rv = uri->SchemeIs("https", &isHttps);
+ if (NS_FAILED(rv)) return rv;
+ if (!isHttps) {
+ NS_WARNING("Invalid URI scheme");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NewProxiedChannel2(uri, nullptr, 0, nullptr, aLoadInfo, result);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewChannel(nsIURI *uri, nsIChannel **result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIProxiedProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::NewProxiedChannel2(nsIURI *uri,
+ nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ RefPtr<HttpBaseChannel> httpChannel;
+
+ LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n",
+ givenProxyInfo));
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ bool https;
+ nsresult rv = uri->SchemeIs("https", &https);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (IsNeckoChild()) {
+ httpChannel = new HttpChannelChild();
+ } else {
+ httpChannel = new nsHttpChannel();
+ }
+
+ uint32_t caps = mCapabilities;
+
+ if (https) {
+ // enable pipelining over SSL if requested
+ if (mPipeliningOverSSL)
+ caps |= NS_HTTP_ALLOW_PIPELINING;
+ }
+
+ if (!IsNeckoChild()) {
+ // HACK: make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+
+ nsID channelId;
+ rv = NewChannelId(&channelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpChannel->Init(uri, caps, proxyInfo, proxyResolveFlags, proxyURI, channelId);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // 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,
+ nsIChannel **result)
+{
+ return NewProxiedChannel2(uri, givenProxyInfo,
+ proxyResolveFlags, proxyURI,
+ nullptr, 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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));
+
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject);
+ if (prefBranch)
+ PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get());
+ } else if (!strcmp(topic, "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();
+
+ if (!mDoNotTrackEnabled) {
+ Telemetry::Accumulate(Telemetry::DNT_USAGE, 2);
+ } else {
+ Telemetry::Accumulate(Telemetry::DNT_USAGE, 1);
+ }
+ } else if (!strcmp(topic, "profile-change-net-restore")) {
+ // initialize connection manager
+ InitConnectionMgr();
+ } else if (!strcmp(topic, "net:clear-active-logins")) {
+ mAuthCache.ClearAll();
+ mPrivateAuthCache.ClearAll();
+ } else if (!strcmp(topic, "net:prune-dead-connections")) {
+ if (mConnMgr) {
+ mConnMgr->PruneDeadConnections();
+ }
+ } else if (!strcmp(topic, "net:prune-all-connections")) {
+ if (mConnMgr) {
+ mConnMgr->DoShiftReloadConnectionCleanup(nullptr);
+ mConnMgr->PruneDeadConnections();
+ }
+ } else if (!strcmp(topic, "net:failed-to-process-uri-content")) {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
+ if (uri && mConnMgr) {
+ mConnMgr->ReportFailedToProcess(uri);
+ }
+ } else if (!strcmp(topic, "last-pb-context-exited")) {
+ mPrivateAuthCache.ClearAll();
+ if (mConnMgr) {
+ mConnMgr->ClearAltServiceMappings();
+ }
+ } else if (!strcmp(topic, "webapps-clear-data")) {
+ if (mConnMgr) {
+ mConnMgr->ClearAltServiceMappings();
+ }
+ } else if (!strcmp(topic, "browser:purge-session-history")) {
+ if (mConnMgr) {
+ if (gSocketTransportService) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(mConnMgr,
+ &nsHttpConnectionMgr::ClearConnectionHistory);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ mConnMgr->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) {
+ mConnMgr->PruneDeadConnections();
+ mConnMgr->VerifyTraffic();
+ }
+ }
+ } else if (!strcmp(topic, "application-background")) {
+ // going to the background on android means we should close
+ // down idle connections for power conservation
+ if (mConnMgr) {
+ mConnMgr->DoShiftReloadConnectionCleanup(nullptr);
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+
+nsresult
+nsHttpHandler::SpeculativeConnectInternal(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks,
+ bool anonymous)
+{
+ if (IsNeckoChild()) {
+ ipc::URIParams params;
+ SerializeURI(aURI, params);
+ gNeckoChild->SendSpeculativeConnect(params,
+ IPC::Principal(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);
+ if (!IsNeckoChild()) {
+ 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;
+ nsCOMPtr<nsIURI> clone;
+ if (NS_SUCCEEDED(sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS,
+ aURI, flags, 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
+ bool usingSSL = false;
+ rv = aURI->SchemeIs("https", &usingSSL);
+ if (NS_FAILED(rv))
+ return rv;
+
+ 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);
+
+ NeckoOriginAttributes neckoOriginAttributes;
+ // If the principal is given, we use the originAttributes from this
+ // principal. Otherwise, we use the originAttributes from the
+ // loadContext.
+ if (aPrincipal) {
+ neckoOriginAttributes.InheritFromDocToNecko(
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef());
+ } else if (loadContext) {
+ DocShellOriginAttributes docshellOriginAttributes;
+ loadContext->GetOriginAttributes(docshellOriginAttributes);
+ neckoOriginAttributes.InheritFromDocShellToNecko(docshellOriginAttributes);
+ }
+
+ auto *ci =
+ new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr,
+ neckoOriginAttributes, usingSSL);
+ ci->SetAnonymous(anonymous);
+
+ return SpeculativeConnect(ci, aCallbacks);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeConnect(nsIURI *aURI,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeConnect2(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeAnonymousConnect(nsIURI *aURI,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, true);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeAnonymousConnect2(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;
+
+ nsCOMPtr<nsIDOMNavigator> domNavigator = piWindow->GetNavigator();
+ nsCOMPtr<nsIMozNavigatorNetwork> networkNavigator =
+ do_QueryInterface(domNavigator);
+ if (!networkNavigator)
+ return;
+
+ nsCOMPtr<nsINetworkProperties> networkProperties;
+ networkNavigator->GetProperties(getter_AddRefs(networkProperties));
+ if (!networkProperties)
+ return;
+
+ uint32_t gwAddress;
+ bool isWifi;
+ nsresult rv;
+
+ rv = networkProperties->GetDhcpGateway(&gwAddress);
+ if (NS_SUCCEEDED(rv))
+ rv = networkProperties->GetIsWifi(&isWifi);
+ if (NS_FAILED(rv))
+ return;
+
+ 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_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset,
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ return mozilla::net::NewURI(aSpec, aOriginCharset, aBaseURI, NS_HTTPS_DEFAULT_PORT, _retval);
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** _retval)
+{
+ MOZ_ASSERT(gHttpHandler);
+ if (!gHttpHandler)
+ return NS_ERROR_UNEXPECTED;
+ return gHttpHandler->NewChannel2(aURI, aLoadInfo, _retval);
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::AllowPort(int32_t aPort, const char *aScheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+void
+nsHttpHandler::ShutdownConnectionManager()
+{
+ // ensure connection manager is shutdown
+ if (mConnMgr) {
+ mConnMgr->Shutdown();
+ }
+}
+
+nsresult
+nsHttpHandler::NewChannelId(nsID *channelId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mUUIDGen) {
+ nsresult rv;
+ mUUIDGen = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mUUIDGen->GenerateUUIDInPlace(channelId);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h
new file mode 100644
index 0000000000..13cc72e8ef
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -0,0 +1,683 @@
+/* -*- 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 nsHttpHandler_h__
+#define nsHttpHandler_h__
+
+#include "nsHttp.h"
+#include "nsHttpAuthCache.h"
+#include "nsHttpConnectionMgr.h"
+#include "ASpdySession.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+
+#include "nsIHttpProtocolHandler.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+
+class nsIHttpChannel;
+class nsIPrefBranch;
+class nsICancelable;
+class nsICookieService;
+class nsIIOService;
+class nsIRequestContextService;
+class nsISiteSecurityService;
+class nsIStreamConverterService;
+class nsITimer;
+class nsIUUIDGenerator;
+
+
+namespace mozilla {
+namespace net {
+
+extern Atomic<PRThread*, Relaxed> gSocketThread;
+
+class ATokenBucketEvent;
+class EventTokenBucket;
+class Tickler;
+class nsHttpConnection;
+class nsHttpConnectionInfo;
+class nsHttpTransaction;
+class AltSvcMapping;
+
+enum FrameCheckLevel {
+ FRAMECHECK_LAX,
+ FRAMECHECK_BARELY,
+ 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
+
+ nsHttpHandler();
+
+ nsresult Init();
+ nsresult AddStandardRequestHeaders(nsHttpRequestHead *, bool isSecure);
+ nsresult AddConnectionHeader(nsHttpRequestHead *,
+ uint32_t capabilities);
+ bool IsAcceptableEncoding(const char *encoding, bool isSecure);
+
+ const nsAFlatCString &UserAgent();
+
+ nsHttpVersion HttpVersion() { return mHttpVersion; }
+ nsHttpVersion ProxyHttpVersion() { return mProxyHttpVersion; }
+ uint8_t ReferrerLevel() { return mReferrerLevel; }
+ bool SpoofReferrerSource() { return mSpoofReferrerSource; }
+ uint8_t ReferrerTrimmingPolicy() { return mReferrerTrimmingPolicy; }
+ uint8_t ReferrerXOriginTrimmingPolicy() {
+ return mReferrerXOriginTrimmingPolicy;
+ }
+ uint8_t ReferrerXOriginPolicy() { return mReferrerXOriginPolicy; }
+ 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 char *DefaultSocketType() { return mDefaultSocketType.get(); /* ok to return null */ }
+ uint32_t PhishyUserPassLength() { return mPhishyUserPassLength; }
+ uint8_t GetQoSBits() { return mQoSBits; }
+ uint16_t GetIdleSynTimeout() { return mIdleSynTimeout; }
+ bool FastFallbackToIPv4() { return mFastFallbackToIPv4; }
+ bool ProxyPipelining() { return mProxyPipelining; }
+ uint32_t MaxSocketCount();
+ bool EnforceAssocReq() { return mEnforceAssocReq; }
+
+ bool IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; }
+ bool IsTelemetryEnabled() { return mTelemetryEnabled; }
+ bool AllowExperiments() { return mTelemetryEnabled && mAllowExperiments; }
+
+ 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; }
+ uint32_t ConnectTimeout() { return mConnectTimeout; }
+ uint32_t ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
+ bool CriticalRequestPrioritization() { return mCriticalRequestPrioritization; }
+ bool UseH2Deps() { return mUseH2Deps; }
+
+ 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; }
+
+ // TCP Keepalive configuration values.
+
+ // Returns true if TCP keepalive should be enabled for short-lived conns.
+ bool TCPKeepaliveEnabledForShortLivedConns() {
+ return mTCPKeepaliveShortLivedEnabled;
+ }
+ // Return time (secs) that a connection is consider short lived (for TCP
+ // keepalive purposes). After this time, the connection is long-lived.
+ int32_t GetTCPKeepaliveShortLivedTime() {
+ return mTCPKeepaliveShortLivedTimeS;
+ }
+ // Returns time (secs) before first TCP keepalive probes should be sent;
+ // same time used between successful keepalive probes.
+ int32_t GetTCPKeepaliveShortLivedIdleTime() {
+ return mTCPKeepaliveShortLivedIdleTimeS;
+ }
+
+ // Returns true if TCP keepalive should be enabled for long-lived conns.
+ bool TCPKeepaliveEnabledForLongLivedConns() {
+ return mTCPKeepaliveLongLivedEnabled;
+ }
+ // Returns time (secs) before first TCP keepalive probes should be sent;
+ // same time used between successful keepalive probes.
+ int32_t GetTCPKeepaliveLongLivedIdleTime() {
+ return mTCPKeepaliveLongLivedIdleTimeS;
+ }
+
+ // returns the HTTP framing check level preference, as controlled with
+ // network.http.enforce-framing.http1 and network.http.enforce-framing.soft
+ FrameCheckLevel GetEnforceH1Framing() { return mEnforceH1Framing; }
+
+ nsHttpAuthCache *AuthCache(bool aPrivate) {
+ return aPrivate ? &mPrivateAuthCache : &mAuthCache;
+ }
+ nsHttpConnectionMgr *ConnMgr() { return mConnMgr; }
+
+ // 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.
+ nsresult InitiateTransaction(nsHttpTransaction *trans, int32_t priority)
+ {
+ return mConnMgr->AddTransaction(trans, priority);
+ }
+
+ // Called to change the priority of an existing transaction that has
+ // already been initiated.
+ nsresult RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
+ {
+ return mConnMgr->RescheduleTransaction(trans, priority);
+ }
+
+ // Called to cancel a transaction, which may or may not be assigned to
+ // a connection. Callable from any thread.
+ nsresult CancelTransaction(nsHttpTransaction *trans, nsresult reason)
+ {
+ return mConnMgr->CancelTransaction(trans, reason);
+ }
+
+ // Called when a connection is done processing a transaction. Callable
+ // from any thread.
+ nsresult ReclaimConnection(nsHttpConnection *conn)
+ {
+ return mConnMgr->ReclaimConnection(conn);
+ }
+
+ nsresult ProcessPendingQ(nsHttpConnectionInfo *cinfo)
+ {
+ return mConnMgr->ProcessPendingQ(cinfo);
+ }
+
+ nsresult ProcessPendingQ()
+ {
+ return mConnMgr->ProcessPendingQ();
+ }
+
+ nsresult GetSocketThreadTarget(nsIEventTarget **target)
+ {
+ return mConnMgr->GetSocketThreadTarget(target);
+ }
+
+ nsresult SpeculativeConnect(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps = 0)
+ {
+ TickleWifi(callbacks);
+ return mConnMgr->SpeculativeConnect(ci, callbacks, caps);
+ }
+
+ // Alternate Services Maps are main thread only
+ void UpdateAltServiceMapping(AltSvcMapping *map,
+ nsProxyInfo *proxyInfo,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps,
+ const NeckoOriginAttributes &originAttributes)
+ {
+ mConnMgr->UpdateAltServiceMapping(map, proxyInfo, callbacks, caps,
+ originAttributes);
+ }
+
+ already_AddRefed<AltSvcMapping> GetAltServiceMapping(const nsACString &scheme,
+ const nsACString &host,
+ int32_t port, bool pb)
+ {
+ return mConnMgr->GetAltServiceMapping(scheme, host, port, pb);
+ }
+
+ //
+ // The HTTP handler caches pointers to specific XPCOM services, and
+ // provides the following helper routines for accessing those services:
+ //
+ nsresult GetStreamConverterService(nsIStreamConverterService **);
+ nsresult GetIOService(nsIIOService** service);
+ nsICookieService * GetCookieService(); // not addrefed
+ nsISiteSecurityService * GetSSService();
+
+ // callable from socket thread only
+ uint32_t Get32BitsOfPseudoRandom();
+
+ // Called by the channel synchronously during asyncOpen
+ void OnOpeningRequest(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_OPENING_REQUEST_TOPIC);
+ }
+
+ // Called by the channel before writing a request
+ void OnModifyRequest(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_MODIFY_REQUEST_TOPIC);
+ }
+
+ // Called by the channel and cached in the loadGroup
+ void OnUserAgentRequest(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_USERAGENT_REQUEST_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 channels before a redirect happens. This notifies both the
+ // channel's and the global redirect observers.
+ nsresult AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags);
+
+ // 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);
+ }
+
+ // Generates the host:port string for use in the Host: header as well as the
+ // CONNECT line for proxies. This handles IPv6 literals correctly.
+ static nsresult GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine);
+
+ bool GetPipelineAggressive() { return mPipelineAggressive; }
+ void GetMaxPipelineObjectSize(int64_t *outVal)
+ {
+ *outVal = mMaxPipelineObjectSize;
+ }
+
+ bool GetPipelineEnabled()
+ {
+ return mCapabilities & NS_HTTP_ALLOW_PIPELINING;
+ }
+
+ bool GetPipelineRescheduleOnTimeout()
+ {
+ return mPipelineRescheduleOnTimeout;
+ }
+
+ PRIntervalTime GetPipelineRescheduleTimeout()
+ {
+ return mPipelineRescheduleTimeout;
+ }
+
+ PRIntervalTime GetPipelineTimeout() { return mPipelineReadTimeout; }
+
+ SpdyInformation *SpdyInfo() { return &mSpdyInfo; }
+ bool IsH2MandatorySuiteEnabled() { return mH2MandatorySuiteEnabled; }
+
+ // Returns true if content-signature test pref is set such that they are
+ // NOT enforced on remote newtabs.
+ bool NewTabContentSignaturesDisabled()
+ {
+ return mNewTabContentSignaturesDisabled;
+ }
+
+ // returns true in between Init and Shutdown states
+ bool Active() { return mHandlerActive; }
+
+ // When the disk cache is responding slowly its use is suppressed
+ // for 1 minute for most requests. Callable from main thread only.
+ TimeStamp GetCacheSkippedUntil() { return mCacheSkippedUntil; }
+ void SetCacheSkippedUntil(TimeStamp arg) { mCacheSkippedUntil = arg; }
+ void ClearCacheSkippedUntil() { mCacheSkippedUntil = TimeStamp(); }
+
+ nsIRequestContextService *GetRequestContextService()
+ {
+ return mRequestContextService.get();
+ }
+
+ void ShutdownConnectionManager();
+
+ bool KeepEmptyResponseHeadersAsEmtpyString() const
+ {
+ return mKeepEmptyResponseHeadersAsEmtpyString;
+ }
+
+ uint32_t DefaultHpackBuffer() const
+ {
+ return mDefaultHpackBuffer;
+ }
+
+ uint32_t MaxHttpResponseHeaderSize() const
+ {
+ return mMaxHttpResponseHeaderSize;
+ }
+
+private:
+ virtual ~nsHttpHandler();
+
+ //
+ // Useragent/prefs helper methods
+ //
+ void BuildUserAgent();
+ void InitUserAgentComponents();
+ void PrefsChanged(nsIPrefBranch *prefs, const char *pref);
+
+ nsresult SetAccept(const char *);
+ nsresult SetAcceptLanguages(const char *);
+ nsresult SetAcceptEncodings(const char *, bool mIsSecure);
+
+ nsresult InitConnectionMgr();
+
+ void NotifyObservers(nsIHttpChannel *chan, const char *event);
+
+ static void TimerCallback(nsITimer * aTimer, void * aClosure);
+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<nsHttpConnectionMgr> mConnMgr;
+
+ //
+ // prefs
+ //
+
+ uint8_t mHttpVersion;
+ uint8_t mProxyHttpVersion;
+ uint32_t mCapabilities;
+ uint8_t mReferrerLevel;
+ uint8_t mSpoofReferrerSource;
+ uint8_t mReferrerTrimmingPolicy;
+ uint8_t mReferrerXOriginTrimmingPolicy;
+ uint8_t mReferrerXOriginPolicy;
+
+ bool mFastFallbackToIPv4;
+ bool mProxyPipelining;
+ PRIntervalTime mIdleTimeout;
+ PRIntervalTime mSpdyTimeout;
+ PRIntervalTime mResponseTimeout;
+ bool mResponseTimeoutEnabled;
+ uint32_t mNetworkChangedTimeout; // milliseconds
+ uint16_t mMaxRequestAttempts;
+ uint16_t mMaxRequestDelay;
+ uint16_t mIdleSynTimeout;
+
+ bool mH2MandatorySuiteEnabled;
+ bool mPipeliningEnabled;
+ uint16_t mMaxConnections;
+ uint8_t mMaxPersistentConnectionsPerServer;
+ uint8_t mMaxPersistentConnectionsPerProxy;
+ uint16_t mMaxPipelinedRequests;
+ uint16_t mMaxOptimisticPipelinedRequests;
+ bool mPipelineAggressive;
+ int64_t mMaxPipelineObjectSize;
+ bool mPipelineRescheduleOnTimeout;
+ PRIntervalTime mPipelineRescheduleTimeout;
+ PRIntervalTime mPipelineReadTimeout;
+ nsCOMPtr<nsITimer> mPipelineTestTimer;
+
+ uint8_t mRedirectionLimit;
+
+ // 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 mPipeliningOverSSL;
+ bool mEnforceAssocReq;
+
+ nsCString mAccept;
+ nsCString mAcceptLanguages;
+ nsCString mHttpAcceptEncodings;
+ nsCString mHttpsAcceptEncodings;
+
+ nsXPIDLCString mDefaultSocketType;
+
+ // cache support
+ uint32_t mLastUniqueID;
+ uint32_t mSessionStartTime;
+
+ // useragent components
+ nsCString mLegacyAppName;
+ nsCString mLegacyAppVersion;
+ nsCString mPlatform;
+ nsCString mOscpu;
+ nsCString mMisc;
+ nsCString mProduct;
+ nsXPIDLCString mProductSub;
+ nsXPIDLCString mAppName;
+ nsXPIDLCString mAppVersion;
+ nsCString mCompatFirefox;
+ bool mCompatFirefoxEnabled;
+ nsXPIDLCString mCompatDevice;
+ nsCString mDeviceModelId;
+
+ nsCString mUserAgent;
+ nsXPIDLCString mUserAgentOverride;
+ bool mUserAgentIsDirty; // true if mUserAgent should be rebuilt
+
+
+ bool mPromptTempRedirect;
+
+ // Persistent HTTPS caching flag
+ bool mEnablePersistentHttpsCaching;
+
+ // For broadcasting tracking preference
+ bool mDoNotTrackEnabled;
+
+ // for broadcasting safe hint;
+ bool mSafeHintEnabled;
+ bool mParentalControlEnabled;
+
+ // true in between init and shutdown states
+ Atomic<bool, Relaxed> mHandlerActive;
+
+ // Whether telemetry is reported or not
+ uint32_t mTelemetryEnabled : 1;
+
+ // The value of network.allow-experiments
+ uint32_t mAllowExperiments : 1;
+
+ // 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;
+
+ // 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 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;
+
+ // When the disk cache is responding slowly its use is suppressed
+ // for 1 minute for most requests.
+ TimeStamp mCacheSkippedUntil;
+
+ // 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;
+
+ // True if remote newtab content-signature disabled because of the channel.
+ bool mNewTabContentSignaturesDisabled;
+
+ // If it is set to false, headers with empty value will not appear in the
+ // header array - behavior as it used to be. If it is true: empty headers
+ // coming from the network will exits in header array as empty string.
+ // Call SetHeader with an empty value will still delete the header.
+ // (Bug 6699259)
+ bool mKeepEmptyResponseHeadersAsEmtpyString;
+
+ // The default size (in bytes) of the HPACK decompressor table.
+ uint32_t mDefaultHpackBuffer;
+
+ // The max size (in bytes) for received Http response header.
+ uint32_t mMaxHttpResponseHeaderSize;
+
+private:
+ // For Rate Pacing Certain Network Events. Only assign this pointer on
+ // socket thread.
+ void MakeNewRequestTokenBucket();
+ RefPtr<EventTokenBucket> mRequestTokenBucket;
+
+public:
+ // Socket thread only
+ nsresult SubmitPacedRequest(ATokenBucketEvent *event,
+ nsICancelable **cancel)
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (!mRequestTokenBucket) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mRequestTokenBucket->SubmitEvent(event, cancel);
+ }
+
+ // Socket thread only
+ void SetRequestTokenBucket(EventTokenBucket *aTokenBucket)
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mRequestTokenBucket = aTokenBucket;
+ }
+
+ void StopRequestTokenBucket()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (mRequestTokenBucket) {
+ mRequestTokenBucket->Stop();
+ mRequestTokenBucket = nullptr;
+ }
+ }
+
+private:
+ RefPtr<Tickler> mWifiTickler;
+ void TickleWifi(nsIInterfaceRequestor *cb);
+
+private:
+ nsresult SpeculativeConnectInternal(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks,
+ bool anonymous);
+
+ // UUID generator for channelIds
+ nsCOMPtr<nsIUUIDGenerator> mUUIDGen;
+
+public:
+ nsresult NewChannelId(nsID *channelId);
+};
+
+extern 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() { }
+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() { }
+
+ nsresult Init();
+};
+
+} // 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..670300dea5
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -0,0 +1,441 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// 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(nsHttpAtom header,
+ 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, value, variety);
+ } else if (merge && !IsSingletonHeader(header)) {
+ return MergeHeader(header, entry, value, variety);
+ } else {
+ // Replace the existing string with the new value
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ return SetHeader_internal(header, value, variety);
+ } else {
+ entry->value = value;
+ entry->variety = variety;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::SetHeader_internal(nsHttpAtom header,
+ const nsACString &value,
+ nsHttpHeaderArray::HeaderVariety variety)
+{
+ nsEntry *entry = mHeaders.AppendElement();
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ entry->header = header;
+ entry->value = value;
+ entry->variety = variety;
+ return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::SetEmptyHeader(nsHttpAtom header, HeaderVariety variety)
+{
+ 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, EmptyCString(), variety);
+}
+
+nsresult
+nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header,
+ 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) {
+ if (value.IsEmpty()) {
+ if (!gHttpHandler->KeepEmptyResponseHeadersAsEmtpyString() &&
+ !TrackEmptyHeader(header)) {
+ LOG(("Ignoring Empty Header: %s\n", header.get()));
+ if (response) {
+ // Set header as original but not as response header.
+ return SetHeader_internal(header, value,
+ eVarietyResponseNetOriginal);
+ }
+ return NS_OK; // ignore empty headers by default
+ }
+ }
+ HeaderVariety variety = eVarietyRequestOverride;
+ if (response) {
+ variety = eVarietyResponseNetOriginalAndResponse;
+ }
+ return SetHeader_internal(header, 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, value,
+ eVarietyResponseNetOriginal);
+ }
+ return rv;
+ } else {
+ // 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, value,
+ eVarietyResponseNetOriginal);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::SetResponseHeaderFromCache(nsHttpAtom header,
+ 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, value,
+ eVarietyResponseNetOriginal);
+ } else {
+ 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, 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;
+ }
+ rv = NS_OK;
+ if (NS_FAILED(aVisitor->VisitHeader(nsDependentCString(entry.header),
+ 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;
+ } else if (filter == eFilterResponse && entry.variety == eVarietyResponseNetOriginal) {
+ continue;
+ } else if (filter == eFilterResponseOriginal && entry.variety == eVarietyResponse) {
+ continue;
+ }
+ rv = visitor->VisitHeader(
+ nsDependentCString(entry.header), entry.value);
+ if NS_FAILED(rv) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+/*static*/ nsresult
+nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
+ nsHttpAtom *hdr,
+ 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);
+
+ 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;
+ }
+
+ buf.Append(entry.header);
+ 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;
+ }
+
+ buf.Append(entry.header);
+ buf.AppendLiteral(": ");
+ buf.Append(entry.value);
+ buf.AppendLiteral("\r\n");
+ }
+}
+
+const char *
+nsHttpHeaderArray::PeekHeaderAt(uint32_t index, nsHttpAtom &header) const
+{
+ const nsEntry &entry = mHeaders[index];
+
+ header = entry.header;
+ 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..91b91f04aa
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.h
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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
+ nsresult SetHeader(nsHttpAtom header, const nsACString &value,
+ bool merge, HeaderVariety variety);
+
+ // Used by internal setters to set an empty header
+ nsresult SetEmptyHeader(nsHttpAtom header, 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.
+ nsresult SetHeaderFromNet(nsHttpAtom header, const nsACString &value,
+ bool response);
+
+ nsresult SetResponseHeaderFromCache(nsHttpAtom header, const nsACString &value,
+ HeaderVariety variety);
+
+ nsresult GetHeader(nsHttpAtom header, nsACString &value) const;
+ 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
+ };
+
+ 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).
+ static nsresult ParseHeaderLine(const nsACString& line,
+ nsHttpAtom *header=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) const;
+
+ void Clear();
+
+ // Must be copy-constructable and assignable
+ struct nsEntry
+ {
+ nsHttpAtom header;
+ 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 **);
+ nsresult MergeHeader(nsHttpAtom header, nsEntry *entry,
+ const nsACString &value, HeaderVariety variety);
+ nsresult SetHeader_internal(nsHttpAtom header, const nsACString &value,
+ HeaderVariety variety);
+
+ // Header cannot be merged: only one value possible
+ bool IsSingletonHeader(nsHttpAtom header);
+ // For some headers we want to track empty values to prevent them being
+ // combined with non-empty ones as a CRLF attack vector
+ bool TrackEmptyHeader(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
+ nsTArray<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;
+}
+
+inline bool
+nsHttpHeaderArray::TrackEmptyHeader(nsHttpAtom header)
+{
+ return header == nsHttp::Content_Length ||
+ header == nsHttp::Location ||
+ header == nsHttp::Access_Control_Allow_Origin;
+}
+
+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;
+ nsresult rv = SetHeader_internal(header, 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..aa5b1f8f7e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp
@@ -0,0 +1,533 @@
+/* vim:set ts=4 sw=4 sts=4 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 "nsIPrefService.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#ifdef XP_WIN
+#include "nsIChannel.h"
+#include "nsIX509Cert.h"
+#include "nsISSLStatus.h"
+#include "nsISSLStatusProvider.h"
+#endif
+#include "mozilla/Attributes.h"
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.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";
+
+// XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp,
+// but since that file lives in a separate library we cannot directly share it.
+// bug 236865 addresses this problem.
+
+static bool
+MatchesBaseURI(const nsCSubstring &matchScheme,
+ const nsCSubstring &matchHost,
+ int32_t matchPort,
+ const char *baseStart,
+ const char *baseEnd)
+{
+ // check if scheme://host:port matches baseURI
+
+ // parse the base URI
+ const char *hostStart, *schemeEnd = strstr(baseStart, "://");
+ if (schemeEnd) {
+ // the given scheme must match the parsed scheme exactly
+ if (!matchScheme.Equals(Substring(baseStart, schemeEnd)))
+ return false;
+ hostStart = schemeEnd + 3;
+ }
+ else
+ hostStart = baseStart;
+
+ // XXX this does not work for IPv6-literals
+ const char *hostEnd = strchr(hostStart, ':');
+ if (hostEnd && hostEnd < baseEnd) {
+ // the given port must match the parsed port exactly
+ int port = atoi(hostEnd + 1);
+ if (matchPort != (int32_t) port)
+ return false;
+ }
+ else
+ hostEnd = baseEnd;
+
+
+ // if we didn't parse out a host, then assume we got a match.
+ if (hostStart == hostEnd)
+ return true;
+
+ uint32_t hostLen = hostEnd - hostStart;
+
+ // matchHost must either equal host or be a subdomain of host
+ if (matchHost.Length() < hostLen)
+ return false;
+
+ const char *end = matchHost.EndReading();
+ if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) {
+ // if matchHost ends with host from the base URI, then make sure it is
+ // either an exact match, or prefixed with a dot. we don't want
+ // "foobar.com" to match "bar.com"
+ if (matchHost.Length() == hostLen ||
+ *(end - hostLen) == '.' ||
+ *(end - hostLen - 1) == '.')
+ return true;
+ }
+
+ return false;
+}
+
+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;
+}
+
+static bool
+TestPref(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;
+ if (NS_FAILED(uri->GetPort(&port)))
+ return false;
+
+ char *hostList;
+ if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !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"
+ //
+
+ char *start = hostList, *end;
+ for (;;) {
+ // skip past any whitespace
+ while (*start == ' ' || *start == '\t')
+ ++start;
+ end = strchr(start, ',');
+ if (!end)
+ end = start + strlen(start);
+ if (start == end)
+ break;
+ if (MatchesBaseURI(scheme, host, port, start, end))
+ return true;
+ if (*end == '\0')
+ break;
+ start = end + 1;
+ }
+
+ free(hostList);
+ return false;
+}
+
+// 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;
+ 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 && TestPref(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() {}
+public:
+ NS_DECL_ISUPPORTS
+};
+NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
+
+//-----------------------------------------------------------------------------
+
+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<nsISupports> 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 = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "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 = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "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.
+ *sessionState = new nsNTLMSessionState();
+ if (!*sessionState)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*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 = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "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.swap(*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;
+
+ // 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<nsISSLStatusProvider> statusProvider =
+ do_QueryInterface(security);
+
+ if (mUseNative && statusProvider) {
+ nsCOMPtr<nsISSLStatus> status;
+ rv = statusProvider->GetSSLStatus(getter_AddRefs(status));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = status->GetServerCert(getter_AddRefs(cert));
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t length;
+ uint8_t* certArray;
+ cert->GetRawDER(&length, &certArray);
+
+ // If there is a server certificate, we pass it along the
+ // first time we call GetNextToken().
+ inBufLen = length;
+ inBuf = certArray;
+ } 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 (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);
+ }
+
+ if (inBuf)
+ 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..9bc1ef6f2e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.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 nsHttpNTLMAuth_h__
+#define nsHttpNTLMAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+
+namespace mozilla { namespace net {
+
+class nsHttpNTLMAuth : public nsIHttpAuthenticator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpNTLMAuth() {}
+
+private:
+ virtual ~nsHttpNTLMAuth() {}
+
+ // This flag indicates whether we are using the native NTLM implementation
+ // or the internal one.
+ bool mUseNative;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpNTLMAuth_h__
diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp
new file mode 100644
index 0000000000..293de8e39c
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -0,0 +1,911 @@
+/* -*- 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 "nsHttpPipeline.h"
+#include "nsHttpHandler.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+#include "nsIPipe.h"
+#include "nsCOMPtr.h"
+#include "nsSocketTransportService2.h"
+#include <algorithm>
+
+#ifdef DEBUG
+#include "prthread.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpPushBackWriter
+//-----------------------------------------------------------------------------
+
+class nsHttpPushBackWriter : public nsAHttpSegmentWriter
+{
+public:
+ nsHttpPushBackWriter(const char *buf, uint32_t bufLen)
+ : mBuf(buf)
+ , mBufLen(bufLen)
+ { }
+ virtual ~nsHttpPushBackWriter() {}
+
+ nsresult OnWriteSegment(char *buf, uint32_t count, uint32_t *countWritten)
+ {
+ if (mBufLen == 0)
+ return NS_BASE_STREAM_CLOSED;
+
+ if (count > mBufLen)
+ count = mBufLen;
+
+ memcpy(buf, mBuf, count);
+
+ mBuf += count;
+ mBufLen -= count;
+ *countWritten = count;
+ return NS_OK;
+ }
+
+private:
+ const char *mBuf;
+ uint32_t mBufLen;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline <public>
+//-----------------------------------------------------------------------------
+
+nsHttpPipeline::nsHttpPipeline()
+ : mStatus(NS_OK)
+ , mRequestIsPartial(false)
+ , mResponseIsPartial(false)
+ , mClosed(false)
+ , mUtilizedPipeline(false)
+ , mPushBackBuf(nullptr)
+ , mPushBackLen(0)
+ , mPushBackMax(0)
+ , mHttp1xTransactionCount(0)
+ , mReceivingFromProgress(0)
+ , mSendingToProgress(0)
+ , mSuppressSendEvents(true)
+{
+}
+
+nsHttpPipeline::~nsHttpPipeline()
+{
+ // make sure we aren't still holding onto any transactions!
+ Close(NS_ERROR_ABORT);
+
+ if (mPushBackBuf)
+ free(mPushBackBuf);
+}
+
+nsresult
+nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans)
+{
+ LOG(("nsHttpPipeline::AddTransaction [this=%p trans=%p]\n", this, trans));
+
+ if (mRequestQ.Length() || mResponseQ.Length())
+ mUtilizedPipeline = true;
+
+ // A reference to the actual transaction is held by the pipeline transaction
+ // in either the request or response queue
+ mRequestQ.AppendElement(trans);
+ uint32_t qlen = PipelineDepth();
+
+ if (qlen != 1) {
+ trans->SetPipelinePosition(qlen);
+ }
+ else {
+ // do it for this case in case an idempotent cancellation
+ // is being repeated and an old value needs to be cleared
+ trans->SetPipelinePosition(0);
+ }
+
+ // trans->SetConnection() needs to be updated to point back at
+ // the pipeline object.
+ trans->SetConnection(this);
+
+ if (mConnection && !mClosed && mRequestQ.Length() == 1)
+ mConnection->ResumeSend();
+
+ return NS_OK;
+}
+
+uint32_t
+nsHttpPipeline::PipelineDepth()
+{
+ return mRequestQ.Length() + mResponseQ.Length();
+}
+
+nsresult
+nsHttpPipeline::SetPipelinePosition(int32_t position)
+{
+ nsAHttpTransaction *trans = Response(0);
+ if (trans)
+ return trans->SetPipelinePosition(position);
+ return NS_OK;
+}
+
+int32_t
+nsHttpPipeline::PipelinePosition()
+{
+ nsAHttpTransaction *trans = Response(0);
+ if (trans)
+ return trans->PipelinePosition();
+
+ // The response queue is empty, so return oldest request
+ if (mRequestQ.Length())
+ return Request(mRequestQ.Length() - 1)->PipelinePosition();
+
+ // No transactions in the pipeline
+ return 0;
+}
+
+nsHttpPipeline *
+nsHttpPipeline::QueryPipeline()
+{
+ return this;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsHttpPipeline)
+NS_IMPL_RELEASE(nsHttpPipeline)
+
+// multiple inheritance fun :-)
+NS_INTERFACE_MAP_BEGIN(nsHttpPipeline)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans,
+ nsHttpRequestHead *requestHead,
+ nsHttpResponseHead *responseHead,
+ bool *reset)
+{
+ LOG(("nsHttpPipeline::OnHeadersAvailable [this=%p]\n", this));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mConnection, "no connection");
+
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ MOZ_ASSERT(ci);
+
+ if (!ci) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool pipeliningBefore = gHttpHandler->ConnMgr()->SupportsPipelining(ci);
+
+ // trans has now received its response headers; forward to the real connection
+ nsresult rv = mConnection->OnHeadersAvailable(trans,
+ requestHead,
+ responseHead,
+ reset);
+
+ if (!pipeliningBefore && gHttpHandler->ConnMgr()->SupportsPipelining(ci)) {
+ // The received headers have expanded the eligible
+ // pipeline depth for this connection
+ gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci);
+ }
+
+ return rv;
+}
+
+void
+nsHttpPipeline::CloseTransaction(nsAHttpTransaction *aTrans, nsresult reason)
+{
+ LOG(("nsHttpPipeline::CloseTransaction [this=%p trans=%p reason=%x]\n",
+ this, aTrans, reason));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(NS_FAILED(reason), "expecting failure code");
+
+ // the specified transaction is to be closed with the given "reason"
+ RefPtr<nsAHttpTransaction> trans(aTrans);
+ int32_t index;
+ bool killPipeline = false;
+
+ if ((index = mRequestQ.IndexOf(trans)) >= 0) {
+ if (index == 0 && mRequestIsPartial) {
+ // the transaction is in the request queue. check to see if any of
+ // its data has been written out yet.
+ killPipeline = true;
+ }
+ mRequestQ.RemoveElementAt(index);
+ } else if ((index = mResponseQ.IndexOf(trans)) >= 0) {
+ mResponseQ.RemoveElementAt(index);
+ // while we could avoid killing the pipeline if this transaction is the
+ // last transaction in the pipeline, there doesn't seem to be that much
+ // value in doing so. most likely if this transaction is going away,
+ // the others will be shortly as well.
+ killPipeline = true;
+ }
+
+ // Marking this connection as non-reusable prevents other items from being
+ // added to it and causes it to be torn down soon.
+ DontReuse();
+
+ trans->Close(reason);
+ trans = nullptr;
+
+ if (killPipeline) {
+ // reschedule anything from this pipeline onto a different connection
+ CancelPipeline(reason);
+ }
+
+ // If all the transactions have been removed then we can close the connection
+ // right away.
+ if (!mRequestQ.Length() && !mResponseQ.Length() && mConnection)
+ mConnection->CloseTransaction(this, reason);
+}
+
+nsresult
+nsHttpPipeline::TakeTransport(nsISocketTransport **aTransport,
+ nsIAsyncInputStream **aInputStream,
+ nsIAsyncOutputStream **aOutputStream)
+{
+ return mConnection->TakeTransport(aTransport, aInputStream, aOutputStream);
+}
+
+bool
+nsHttpPipeline::IsPersistent()
+{
+ return true; // pipelining requires this
+}
+
+bool
+nsHttpPipeline::IsReused()
+{
+ if (!mUtilizedPipeline && mConnection)
+ return mConnection->IsReused();
+ return true;
+}
+
+void
+nsHttpPipeline::DontReuse()
+{
+ if (mConnection)
+ mConnection->DontReuse();
+}
+
+nsresult
+nsHttpPipeline::PushBack(const char *data, uint32_t length)
+{
+ LOG(("nsHttpPipeline::PushBack [this=%p len=%u]\n", this, length));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mPushBackLen == 0, "push back buffer already has data!");
+
+ // If we have no chance for a pipeline (e.g. due to an Upgrade)
+ // then push this data down to original connection
+ if (!mConnection->IsPersistent())
+ return mConnection->PushBack(data, length);
+
+ // PushBack is called recursively from WriteSegments
+
+ // XXX we have a design decision to make here. either we buffer the data
+ // and process it when we return to WriteSegments, or we attempt to move
+ // onto the next transaction from here. doing so adds complexity with the
+ // benefit of eliminating the extra buffer copy. the buffer is at most
+ // 4096 bytes, so it is really unclear if there is any value in the added
+ // complexity. besides simplicity, buffering this data has the advantage
+ // that we'll call close on the transaction sooner, which will wake up
+ // the HTTP channel sooner to continue with its work.
+
+ if (!mPushBackBuf) {
+ mPushBackMax = length;
+ mPushBackBuf = (char *) malloc(mPushBackMax);
+ if (!mPushBackBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ else if (length > mPushBackMax) {
+ // grow push back buffer as necessary.
+ MOZ_ASSERT(length <= nsIOService::gDefaultSegmentSize, "too big");
+ mPushBackMax = length;
+ mPushBackBuf = (char *) realloc(mPushBackBuf, mPushBackMax);
+ if (!mPushBackBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy(mPushBackBuf, data, length);
+ mPushBackLen = length;
+
+ return NS_OK;
+}
+
+already_AddRefed<nsHttpConnection>
+nsHttpPipeline::TakeHttpConnection()
+{
+ if (mConnection)
+ return mConnection->TakeHttpConnection();
+ return nullptr;
+}
+
+nsAHttpTransaction::Classifier
+nsHttpPipeline::Classification()
+{
+ if (mConnection)
+ return mConnection->Classification();
+
+ LOG(("nsHttpPipeline::Classification this=%p "
+ "has null mConnection using CLASS_SOLO default", this));
+ return nsAHttpTransaction::CLASS_SOLO;
+}
+
+void
+nsHttpPipeline::SetProxyConnectFailed()
+{
+ nsAHttpTransaction *trans = Request(0);
+
+ if (trans)
+ trans->SetProxyConnectFailed();
+}
+
+nsHttpRequestHead *
+nsHttpPipeline::RequestHead()
+{
+ nsAHttpTransaction *trans = Request(0);
+
+ if (trans)
+ return trans->RequestHead();
+ return nullptr;
+}
+
+uint32_t
+nsHttpPipeline::Http1xTransactionCount()
+{
+ return mHttp1xTransactionCount;
+}
+
+nsresult
+nsHttpPipeline::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
+{
+ LOG(("nsHttpPipeline::TakeSubTransactions [this=%p]\n", this));
+
+ if (mResponseQ.Length() || mRequestIsPartial)
+ return NS_ERROR_ALREADY_OPENED;
+
+ int32_t i, count = mRequestQ.Length();
+ for (i = 0; i < count; ++i) {
+ nsAHttpTransaction *trans = Request(i);
+ // set the transaction connection object back to the underlying
+ // nsHttpConnectionHandle
+ trans->SetConnection(mConnection);
+ outTransactions.AppendElement(trans);
+ }
+ mRequestQ.Clear();
+
+ LOG((" took %d\n", count));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsAHttpTransaction
+//-----------------------------------------------------------------------------
+
+void
+nsHttpPipeline::SetConnection(nsAHttpConnection *conn)
+{
+ LOG(("nsHttpPipeline::SetConnection [this=%p conn=%p]\n", this, conn));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!conn || !mConnection, "already have a connection");
+
+ mConnection = conn;
+}
+
+nsAHttpConnection *
+nsHttpPipeline::Connection()
+{
+ LOG(("nsHttpPipeline::Connection [this=%p conn=%p]\n", this, mConnection.get()));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mConnection;
+}
+
+void
+nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // depending on timing this could be either the request or the response
+ // that is needed - but they both go to the same host. A request for these
+ // callbacks directly in nsHttpTransaction would not make a distinction
+ // over whether the the request had been transmitted yet.
+ nsAHttpTransaction *trans = Request(0);
+ if (!trans)
+ trans = Response(0);
+ if (trans)
+ trans->GetSecurityCallbacks(result);
+ else {
+ *result = nullptr;
+ }
+}
+
+void
+nsHttpPipeline::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress)
+{
+ LOG(("nsHttpPipeline::OnStatus [this=%p status=%x progress=%lld]\n",
+ this, status, progress));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsAHttpTransaction *trans;
+ int32_t i, count;
+
+ 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:
+ // These should only appear at most once per pipeline.
+ // Deliver to the first transaction.
+
+ trans = Request(0);
+ if (!trans)
+ trans = Response(0);
+ if (trans)
+ trans->OnTransportStatus(transport, status, progress);
+
+ break;
+
+ case NS_NET_STATUS_SENDING_TO:
+ // This is generated by the socket transport when (part) of
+ // a transaction is written out
+ //
+ // In pipelining this is generated out of FillSendBuf(), but it cannot do
+ // so until the connection is confirmed by CONNECTED_TO.
+ // See patch for bug 196827.
+ //
+
+ if (mSuppressSendEvents) {
+ mSuppressSendEvents = false;
+
+ // catch up by sending the event to all the transactions that have
+ // moved from request to response and any that have been partially
+ // sent. Also send WAITING_FOR to those that were completely sent
+ count = mResponseQ.Length();
+ for (i = 0; i < count; ++i) {
+ Response(i)->OnTransportStatus(transport,
+ NS_NET_STATUS_SENDING_TO,
+ progress);
+ Response(i)->OnTransportStatus(transport,
+ NS_NET_STATUS_WAITING_FOR,
+ progress);
+ }
+ if (mRequestIsPartial && Request(0))
+ Request(0)->OnTransportStatus(transport,
+ NS_NET_STATUS_SENDING_TO,
+ progress);
+ mSendingToProgress = progress;
+ }
+ // otherwise ignore it
+ break;
+
+ case NS_NET_STATUS_WAITING_FOR:
+ // Created by nsHttpConnection when request pipeline has been totally
+ // sent. Ignore it here because it is simulated in FillSendBuf() when
+ // a request is moved from request to response.
+
+ // ignore it
+ break;
+
+ case NS_NET_STATUS_RECEIVING_FROM:
+ // Forward this only to the transaction currently recieving data. It is
+ // normally generated by the socket transport, but can also
+ // be repeated by the pushbackwriter if necessary.
+ mReceivingFromProgress = progress;
+ if (Response(0))
+ Response(0)->OnTransportStatus(transport, status, progress);
+ break;
+
+ default:
+ // forward other notifications to all request transactions
+ count = mRequestQ.Length();
+ for (i = 0; i < count; ++i)
+ Request(i)->OnTransportStatus(transport, status, progress);
+ break;
+ }
+}
+
+nsHttpConnectionInfo *
+nsHttpPipeline::ConnectionInfo()
+{
+ nsAHttpTransaction *trans = Request(0) ? Request(0) : Response(0);
+ if (!trans) {
+ return nullptr;
+ }
+ return trans->ConnectionInfo();
+}
+
+bool
+nsHttpPipeline::IsDone()
+{
+ bool done = true;
+
+ uint32_t i, count = mRequestQ.Length();
+ for (i = 0; done && (i < count); i++)
+ done = Request(i)->IsDone();
+
+ count = mResponseQ.Length();
+ for (i = 0; done && (i < count); i++)
+ done = Response(i)->IsDone();
+
+ return done;
+}
+
+nsresult
+nsHttpPipeline::Status()
+{
+ return mStatus;
+}
+
+uint32_t
+nsHttpPipeline::Caps()
+{
+ nsAHttpTransaction *trans = Request(0);
+ if (!trans)
+ trans = Response(0);
+
+ return trans ? trans->Caps() : 0;
+}
+
+void
+nsHttpPipeline::SetDNSWasRefreshed()
+{
+ nsAHttpTransaction *trans = Request(0);
+ if (!trans)
+ trans = Response(0);
+
+ if (trans)
+ trans->SetDNSWasRefreshed();
+}
+
+uint64_t
+nsHttpPipeline::Available()
+{
+ uint64_t result = 0;
+
+ int32_t i, count = mRequestQ.Length();
+ for (i=0; i<count; ++i)
+ result += Request(i)->Available();
+ return result;
+}
+
+nsresult
+nsHttpPipeline::ReadFromPipe(nsIInputStream *stream,
+ void *closure,
+ const char *buf,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ nsHttpPipeline *self = (nsHttpPipeline *) closure;
+ return self->mReader->OnReadSegment(buf, count, countRead);
+}
+
+nsresult
+nsHttpPipeline::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ LOG(("nsHttpPipeline::ReadSegments [this=%p count=%u]\n", this, count));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mClosed) {
+ *countRead = 0;
+ return mStatus;
+ }
+
+ nsresult rv;
+ uint64_t avail = 0;
+ if (mSendBufIn) {
+ rv = mSendBufIn->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (avail == 0) {
+ rv = FillSendBuf();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mSendBufIn->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+
+ // return EOF if send buffer is empty
+ if (avail == 0) {
+ *countRead = 0;
+ return NS_OK;
+ }
+ }
+
+ // read no more than what was requested
+ if (avail > count)
+ avail = count;
+
+ mReader = reader;
+
+ // avail is under 4GB, so casting to uint32_t is safe
+ rv = mSendBufIn->ReadSegments(ReadFromPipe, this, (uint32_t)avail, countRead);
+
+ mReader = nullptr;
+ return rv;
+}
+
+nsresult
+nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ LOG(("nsHttpPipeline::WriteSegments [this=%p count=%u]\n", this, count));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mClosed)
+ return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
+
+ nsAHttpTransaction *trans;
+ nsresult rv;
+
+ trans = Response(0);
+ // This code deals with the establishment of a CONNECT tunnel through
+ // an HTTP proxy. It allows the connection to do the CONNECT/200
+ // HTTP transaction to establish a tunnel as a precursor to the
+ // actual pipeline of regular HTTP transactions.
+ if (!trans && mRequestQ.Length() &&
+ mConnection->IsProxyConnectInProgress()) {
+ LOG(("nsHttpPipeline::WriteSegments [this=%p] Forced Delegation\n",
+ this));
+ trans = Request(0);
+ }
+
+ if (!trans) {
+ if (mRequestQ.Length() > 0)
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ else
+ rv = NS_BASE_STREAM_CLOSED;
+ } else {
+ //
+ // ask the transaction to consume data from the connection.
+ // PushBack may be called recursively.
+ //
+ rv = trans->WriteSegments(writer, count, countWritten);
+
+ if (rv == NS_BASE_STREAM_CLOSED || trans->IsDone()) {
+ trans->Close(NS_OK);
+
+ // Release the transaction if it is not IsProxyConnectInProgress()
+ if (trans == Response(0)) {
+ mResponseQ.RemoveElementAt(0);
+ mResponseIsPartial = false;
+ ++mHttp1xTransactionCount;
+ }
+
+ // ask the connection manager to add additional transactions
+ // to our pipeline.
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ if (ci)
+ gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci);
+ }
+ else
+ mResponseIsPartial = true;
+ }
+
+ if (mPushBackLen) {
+ nsHttpPushBackWriter pushBackWriter(mPushBackBuf, mPushBackLen);
+ uint32_t len = mPushBackLen, n;
+ mPushBackLen = 0;
+
+ // This progress notification has previously been sent from
+ // the socket transport code, but it was delivered to the
+ // previous transaction on the pipeline.
+ nsITransport *transport = Transport();
+ if (transport)
+ OnTransportStatus(transport, NS_NET_STATUS_RECEIVING_FROM,
+ mReceivingFromProgress);
+
+ // the push back buffer is never larger than NS_HTTP_SEGMENT_SIZE,
+ // so we are guaranteed that the next response will eat the entire
+ // push back buffer (even though it might again call PushBack).
+ rv = WriteSegments(&pushBackWriter, len, &n);
+ }
+
+ return rv;
+}
+
+uint32_t
+nsHttpPipeline::CancelPipeline(nsresult originalReason)
+{
+ uint32_t i, reqLen, respLen, total;
+ nsAHttpTransaction *trans;
+
+ reqLen = mRequestQ.Length();
+ respLen = mResponseQ.Length();
+ total = reqLen + respLen;
+
+ // don't count the first response, if presnet
+ if (respLen)
+ total--;
+
+ if (!total)
+ return 0;
+
+ // any pending requests can ignore this error and be restarted
+ // unless it is during a CONNECT tunnel request
+ for (i = 0; i < reqLen; ++i) {
+ trans = Request(i);
+ if (mConnection && mConnection->IsProxyConnectInProgress())
+ trans->Close(originalReason);
+ else
+ trans->Close(NS_ERROR_NET_RESET);
+ }
+ mRequestQ.Clear();
+
+ // any pending responses can be restarted except for the first one,
+ // that we might want to finish on this pipeline or cancel individually.
+ // Higher levels of callers ensure that we don't process non-idempotent
+ // tranasction with the NS_HTTP_ALLOW_PIPELINING bit set
+ for (i = 1; i < respLen; ++i) {
+ trans = Response(i);
+ trans->Close(NS_ERROR_NET_RESET);
+ }
+
+ if (respLen > 1)
+ mResponseQ.TruncateLength(1);
+
+ DontReuse();
+ Classify(nsAHttpTransaction::CLASS_SOLO);
+
+ return total;
+}
+
+void
+nsHttpPipeline::Close(nsresult reason)
+{
+ LOG(("nsHttpPipeline::Close [this=%p reason=%x]\n", this, reason));
+
+ if (mClosed) {
+ LOG((" already closed\n"));
+ return;
+ }
+
+ // the connection is going away!
+ mStatus = reason;
+ mClosed = true;
+
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ uint32_t numRescheduled = CancelPipeline(reason);
+
+ // numRescheduled can be 0 if there is just a single response in the
+ // pipeline object. That isn't really a meaningful pipeline that
+ // has been forced to be rescheduled so it does not need to generate
+ // negative feedback.
+ if (ci && numRescheduled)
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ ci, nsHttpConnectionMgr::RedCanceledPipeline, nullptr, 0);
+
+ nsAHttpTransaction *trans = Response(0);
+ if (!trans)
+ return;
+
+ // The current transaction can be restarted via reset
+ // if the response has not started to arrive and the reason
+ // for failure is innocuous (e.g. not an SSL error)
+ if (!mResponseIsPartial &&
+ (reason == NS_ERROR_NET_RESET ||
+ reason == NS_OK ||
+ reason == NS_ERROR_NET_TIMEOUT ||
+ reason == NS_BASE_STREAM_CLOSED)) {
+ trans->Close(NS_ERROR_NET_RESET);
+ }
+ else {
+ trans->Close(reason);
+ }
+
+ mResponseQ.Clear();
+}
+
+nsresult
+nsHttpPipeline::OnReadSegment(const char *segment,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ return mSendBufOut->Write(segment, count, countRead);
+}
+
+nsresult
+nsHttpPipeline::FillSendBuf()
+{
+ // reads from request queue, moving transactions to response queue
+ // when they have been completely read.
+
+ nsresult rv;
+
+ if (!mSendBufIn) {
+ // allocate a single-segment pipe
+ rv = NS_NewPipe(getter_AddRefs(mSendBufIn),
+ getter_AddRefs(mSendBufOut),
+ nsIOService::gDefaultSegmentSize, /* segment size */
+ nsIOService::gDefaultSegmentSize, /* max size */
+ true, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ uint32_t n;
+ uint64_t avail;
+ RefPtr<nsAHttpTransaction> trans;
+ nsITransport *transport = Transport();
+
+ while ((trans = Request(0)) != nullptr) {
+ avail = trans->Available();
+ if (avail) {
+ // if there is already a response in the responseq then this
+ // new data comprises a pipeline. Update the transaction in the
+ // response queue to reflect that if necessary. We are now sending
+ // out a request while we haven't received all responses.
+ nsAHttpTransaction *response = Response(0);
+ if (response && !response->PipelinePosition())
+ response->SetPipelinePosition(1);
+ rv = trans->ReadSegments(this, (uint32_t)std::min(avail, (uint64_t)UINT32_MAX), &n);
+ if (NS_FAILED(rv)) return rv;
+
+ if (n == 0) {
+ LOG(("send pipe is full"));
+ break;
+ }
+
+ mSendingToProgress += n;
+ if (!mSuppressSendEvents && transport) {
+ // Simulate a SENDING_TO event
+ trans->OnTransportStatus(transport,
+ NS_NET_STATUS_SENDING_TO,
+ mSendingToProgress);
+ }
+ }
+
+ avail = trans->Available();
+ if (avail == 0) {
+ // move transaction from request queue to response queue
+ mRequestQ.RemoveElementAt(0);
+ mResponseQ.AppendElement(trans);
+ mRequestIsPartial = false;
+
+ if (!mSuppressSendEvents && transport) {
+ // Simulate a WAITING_FOR event
+ trans->OnTransportStatus(transport,
+ NS_NET_STATUS_WAITING_FOR,
+ mSendingToProgress);
+ }
+
+ // It would be good to re-enable data read handlers via ResumeRecv()
+ // except the read handler code can be synchronously dispatched on
+ // the stack.
+ }
+ else
+ mRequestIsPartial = true;
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpPipeline.h b/netwerk/protocol/http/nsHttpPipeline.h
new file mode 100644
index 0000000000..6dc027f19b
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpPipeline.h
@@ -0,0 +1,105 @@
+/* -*- 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 nsHttpPipeline_h__
+#define nsHttpPipeline_h__
+
+#include "nsAHttpConnection.h"
+#include "nsAHttpTransaction.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla { namespace net {
+
+class nsHttpPipeline final : public nsAHttpConnection
+ , public nsAHttpTransaction
+ , public nsAHttpSegmentReader
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPCONNECTION(mConnection)
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPSEGMENTREADER
+
+ nsHttpPipeline();
+
+ bool ResponseTimeoutEnabled() const override final {
+ return true;
+ }
+
+private:
+ virtual ~nsHttpPipeline();
+
+ nsresult FillSendBuf();
+
+ static nsresult ReadFromPipe(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ // convenience functions
+ nsAHttpTransaction *Request(int32_t i)
+ {
+ if (mRequestQ.Length() == 0)
+ return nullptr;
+
+ return mRequestQ[i];
+ }
+ nsAHttpTransaction *Response(int32_t i)
+ {
+ if (mResponseQ.Length() == 0)
+ return nullptr;
+
+ return mResponseQ[i];
+ }
+
+ // overload of nsAHttpTransaction::QueryPipeline()
+ nsHttpPipeline *QueryPipeline() override;
+
+ RefPtr<nsAHttpConnection> mConnection;
+ nsTArray<RefPtr<nsAHttpTransaction> > mRequestQ;
+ nsTArray<RefPtr<nsAHttpTransaction> > mResponseQ;
+ nsresult mStatus;
+
+ // these flags indicate whether or not the first request or response
+ // is partial. a partial request means that Request(0) has been
+ // partially written out to the socket. a partial response means
+ // that Response(0) has been partially read in from the socket.
+ bool mRequestIsPartial;
+ bool mResponseIsPartial;
+
+ // indicates whether or not the pipeline has been explicitly closed.
+ bool mClosed;
+
+ // indicates whether or not a true pipeline (more than 1 request without
+ // a synchronous response) has been formed.
+ bool mUtilizedPipeline;
+
+ // used when calling ReadSegments/WriteSegments on a transaction.
+ nsAHttpSegmentReader *mReader;
+
+ // send buffer
+ nsCOMPtr<nsIInputStream> mSendBufIn;
+ nsCOMPtr<nsIOutputStream> mSendBufOut;
+
+ // the push back buffer. not exceeding nsIOService::gDefaultSegmentSize bytes.
+ char *mPushBackBuf;
+ uint32_t mPushBackLen;
+ uint32_t mPushBackMax;
+
+ // The number of transactions completed on this pipeline.
+ uint32_t mHttp1xTransactionCount;
+
+ // For support of OnTransportStatus()
+ int64_t mReceivingFromProgress;
+ int64_t mSendingToProgress;
+ bool mSuppressSendEvents;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpPipeline_h__
diff --git a/netwerk/protocol/http/nsHttpRequestHead.cpp b/netwerk/protocol/http/nsHttpRequestHead.cpp
new file mode 100644
index 0000000000..094a794570
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -0,0 +1,369 @@
+/* -*- 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 "nsHttpRequestHead.h"
+#include "nsIHttpHeaderVisitor.h"
+
+//-----------------------------------------------------------------------------
+// nsHttpRequestHead
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+nsHttpRequestHead::nsHttpRequestHead()
+ : mMethod(NS_LITERAL_CSTRING("GET"))
+ , mVersion(NS_HTTP_VERSION_1_1)
+ , mParsedMethod(kMethod_Get)
+ , mHTTPS(false)
+ , mReentrantMonitor("nsHttpRequestHead.mReentrantMonitor")
+ , mInVisitHeaders(false)
+{
+ MOZ_COUNT_CTOR(nsHttpRequestHead);
+}
+
+nsHttpRequestHead::~nsHttpRequestHead()
+{
+ MOZ_COUNT_DTOR(nsHttpRequestHead);
+}
+
+// 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.mReentrantMonitor.AssertCurrentThreadIn();
+ return mHeaders;
+}
+
+void
+nsHttpRequestHead::SetHeaders(const nsHttpHeaderArray& aHeaders)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mHeaders = aHeaders;
+}
+
+void
+nsHttpRequestHead::SetVersion(nsHttpVersion version)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mVersion = version;
+}
+
+void
+nsHttpRequestHead::SetRequestURI(const nsCSubstring &s)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mRequestURI = s;
+}
+
+void
+nsHttpRequestHead::SetPath(const nsCSubstring &s)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mPath = s;
+}
+
+uint32_t
+nsHttpRequestHead::HeaderCount()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHeaders.Count();
+}
+
+nsresult
+nsHttpRequestHead::VisitHeaders(nsIHttpHeaderVisitor *visitor,
+ nsHttpHeaderArray::VisitorFilter filter /* = nsHttpHeaderArray::eFilterAll*/)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+void
+nsHttpRequestHead::Method(nsACString &aMethod)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ aMethod = mMethod;
+}
+
+nsHttpVersion
+nsHttpRequestHead::Version()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mVersion;
+}
+
+void
+nsHttpRequestHead::RequestURI(nsACString &aRequestURI)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ aRequestURI = mRequestURI;
+}
+
+void
+nsHttpRequestHead::Path(nsACString &aPath)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ aPath = mPath.IsEmpty() ? mRequestURI : mPath;
+}
+
+void
+nsHttpRequestHead::SetHTTPS(bool val)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mHTTPS = val;
+}
+
+void
+nsHttpRequestHead::Origin(nsACString &aOrigin)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ aOrigin = mOrigin;
+}
+
+nsresult
+nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v,
+ bool m /*= false*/)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ 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)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m, variety);
+}
+
+nsresult
+nsHttpRequestHead::SetEmptyHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetEmptyHeader(h,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult
+nsHttpRequestHead::GetHeader(nsHttpAtom h, nsACString &v)
+{
+ v.Truncate();
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHeaders.GetHeader(h, v);
+}
+
+nsresult
+nsHttpRequestHead::ClearHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mHeaders.ClearHeader(h);
+ return NS_OK;
+}
+
+void
+nsHttpRequestHead::ClearHeaders()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return;
+ }
+
+ mHeaders.Clear();
+}
+
+bool
+nsHttpRequestHead::HasHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHeaders.HasHeader(h);
+}
+
+bool
+nsHttpRequestHead::HasHeaderValue(nsHttpAtom h, const char *v)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+nsresult
+nsHttpRequestHead::SetHeaderOnce(nsHttpAtom h, const char *v,
+ bool merge /*= false */)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ 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()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mParsedMethod;
+}
+
+bool
+nsHttpRequestHead::EqualsMethod(ParsedMethodType aType)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mParsedMethod == aType;
+}
+
+void
+nsHttpRequestHead::ParseHeaderSet(const char *buffer)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ nsHttpAtom hdr;
+ nsAutoCString val;
+ while (buffer) {
+ const char *eof = strchr(buffer, '\r');
+ if (!eof) {
+ break;
+ }
+ if (NS_SUCCEEDED(nsHttpHeaderArray::ParseHeaderLine(
+ nsDependentCSubstring(buffer, eof - buffer),
+ &hdr,
+ &val))) {
+
+ mHeaders.SetHeaderFromNet(hdr, val, false);
+ }
+ buffer = eof + 1;
+ if (*buffer == '\n') {
+ buffer++;
+ }
+ }
+}
+
+bool
+nsHttpRequestHead::IsHTTPS()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHTTPS;
+}
+
+void
+nsHttpRequestHead::SetMethod(const nsACString &method)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mParsedMethod = kMethod_Custom;
+ mMethod = method;
+ if (!strcmp(mMethod.get(), "GET")) {
+ mParsedMethod = kMethod_Get;
+ } else if (!strcmp(mMethod.get(), "POST")) {
+ mParsedMethod = kMethod_Post;
+ } else if (!strcmp(mMethod.get(), "OPTIONS")) {
+ mParsedMethod = kMethod_Options;
+ } else if (!strcmp(mMethod.get(), "CONNECT")) {
+ mParsedMethod = kMethod_Connect;
+ } else if (!strcmp(mMethod.get(), "HEAD")) {
+ mParsedMethod = kMethod_Head;
+ } else if (!strcmp(mMethod.get(), "PUT")) {
+ mParsedMethod = kMethod_Put;
+ } else if (!strcmp(mMethod.get(), "TRACE")) {
+ mParsedMethod = kMethod_Trace;
+ }
+}
+
+void
+nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host,
+ int32_t port)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mOrigin.Assign(scheme);
+ mOrigin.Append(NS_LITERAL_CSTRING("://"));
+ mOrigin.Append(host);
+ if (port >= 0) {
+ mOrigin.Append(NS_LITERAL_CSTRING(":"));
+ mOrigin.AppendInt(port);
+ }
+}
+
+bool
+nsHttpRequestHead::IsSafeMethod()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ // 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)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ // note: the first append is intentional.
+
+ buf.Append(mMethod);
+ buf.Append(' ');
+ buf.Append(mRequestURI);
+ buf.AppendLiteral(" HTTP/");
+
+ switch (mVersion) {
+ case NS_HTTP_VERSION_1_1:
+ buf.AppendLiteral("1.1");
+ break;
+ case NS_HTTP_VERSION_0_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..4159680834
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -0,0 +1,128 @@
+/* -*- 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 nsHttpRequestHead_h__
+#define nsHttpRequestHead_h__
+
+#include "nsHttp.h"
+#include "nsHttpHeaderArray.h"
+#include "nsString.h"
+#include "mozilla/ReentrantMonitor.h"
+
+class nsIHttpHeaderVisitor;
+
+namespace mozilla { namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpRequestHead represents the request line and headers from an HTTP
+// request.
+//-----------------------------------------------------------------------------
+
+class nsHttpRequestHead
+{
+public:
+ nsHttpRequestHead();
+ ~nsHttpRequestHead();
+
+ // 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() { mReentrantMonitor.Enter(); }
+ void Exit() { mReentrantMonitor.Exit(); }
+
+ void SetHeaders(const nsHttpHeaderArray& aHeaders);
+
+ void SetMethod(const nsACString &method);
+ void SetVersion(nsHttpVersion version);
+ void SetRequestURI(const nsCSubstring &s);
+ void SetPath(const nsCSubstring &s);
+ uint32_t HeaderCount();
+
+ // Using this function it is possible to itereate through all headers
+ // automatically under one lock.
+ nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor,
+ nsHttpHeaderArray::VisitorFilter filter =
+ nsHttpHeaderArray::eFilterAll);
+ void Method(nsACString &aMethod);
+ nsHttpVersion 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);
+
+ nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false);
+ nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m,
+ nsHttpHeaderArray::HeaderVariety variety);
+ nsresult SetEmptyHeader(nsHttpAtom h);
+ nsresult GetHeader(nsHttpAtom h, nsACString &v);
+
+ 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
+ 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
+ };
+
+ 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;
+ nsHttpVersion 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 ReentrantMonitor instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ ReentrantMonitor mReentrantMonitor;
+
+ // During VisitHeader we sould not allow cal to SetHeader.
+ bool mInVisitHeaders;
+};
+
+} // 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..6d384c4887
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -0,0 +1,1221 @@
+/* -*- 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 "nsHttpResponseHead.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsPrintfCString.h"
+#include "prtime.h"
+#include "plstr.h"
+#include "nsURLHelper.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead <public>
+//-----------------------------------------------------------------------------
+
+nsHttpResponseHead::nsHttpResponseHead(const nsHttpResponseHead &aOther)
+ : mReentrantMonitor("nsHttpResponseHead.mReentrantMonitor")
+ , mInVisitHeaders(false)
+{
+ nsHttpResponseHead &other = const_cast<nsHttpResponseHead&>(aOther);
+ ReentrantMonitorAutoEnter monitor(other.mReentrantMonitor);
+
+ mHeaders = other.mHeaders;
+ mVersion = other.mVersion;
+ mStatus = other.mStatus;
+ mStatusText = other.mStatusText;
+ mContentLength = other.mContentLength;
+ mContentType = other.mContentType;
+ mContentCharset = other.mContentCharset;
+ mCacheControlPrivate = other.mCacheControlPrivate;
+ mCacheControlNoStore = other.mCacheControlNoStore;
+ mCacheControlNoCache = other.mCacheControlNoCache;
+ mCacheControlImmutable = other.mCacheControlImmutable;
+ mPragmaNoCache = other.mPragmaNoCache;
+}
+
+nsHttpResponseHead&
+nsHttpResponseHead::operator=(const nsHttpResponseHead &aOther)
+{
+ nsHttpResponseHead &other = const_cast<nsHttpResponseHead&>(aOther);
+ ReentrantMonitorAutoEnter monitorOther(other.mReentrantMonitor);
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+
+ mHeaders = other.mHeaders;
+ mVersion = other.mVersion;
+ mStatus = other.mStatus;
+ mStatusText = other.mStatusText;
+ mContentLength = other.mContentLength;
+ mContentType = other.mContentType;
+ mContentCharset = other.mContentCharset;
+ mCacheControlPrivate = other.mCacheControlPrivate;
+ mCacheControlNoStore = other.mCacheControlNoStore;
+ mCacheControlNoCache = other.mCacheControlNoCache;
+ mCacheControlImmutable = other.mCacheControlImmutable;
+ mPragmaNoCache = other.mPragmaNoCache;
+
+ return *this;
+}
+
+nsHttpVersion
+nsHttpResponseHead::Version()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mVersion;
+}
+
+uint16_t
+nsHttpResponseHead::Status()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mStatus;
+}
+
+void
+nsHttpResponseHead::StatusText(nsACString &aStatusText)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ aStatusText = mStatusText;
+}
+
+int64_t
+nsHttpResponseHead::ContentLength()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mContentLength;
+}
+
+void
+nsHttpResponseHead::ContentType(nsACString &aContentType)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ aContentType = mContentType;
+}
+
+void
+nsHttpResponseHead::ContentCharset(nsACString &aContentCharset)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ aContentCharset = mContentCharset;
+}
+
+bool
+nsHttpResponseHead::Private()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mCacheControlPrivate;
+}
+
+bool
+nsHttpResponseHead::NoStore()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mCacheControlNoStore;
+}
+
+bool
+nsHttpResponseHead::NoCache()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return (mCacheControlNoCache || mPragmaNoCache);
+}
+
+bool
+nsHttpResponseHead::Immutable()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mCacheControlImmutable;
+}
+
+nsresult
+nsHttpResponseHead::SetHeader(nsHttpAtom hdr,
+ const nsACString &val,
+ bool merge)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return SetHeader_locked(hdr, val, merge);
+}
+
+nsresult
+nsHttpResponseHead::SetHeader_locked(nsHttpAtom hdr,
+ const nsACString &val,
+ bool merge)
+{
+ nsresult rv = mHeaders.SetHeader(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 (hdr == nsHttp::Cache_Control)
+ ParseCacheControl(mHeaders.PeekHeader(hdr));
+ else if (hdr == nsHttp::Pragma)
+ ParsePragma(mHeaders.PeekHeader(hdr));
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpResponseHead::GetHeader(nsHttpAtom h, nsACString &v)
+{
+ v.Truncate();
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mHeaders.GetHeader(h, v);
+}
+
+void
+nsHttpResponseHead::ClearHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mHeaders.ClearHeader(h);
+}
+
+void
+nsHttpResponseHead::ClearHeaders()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mHeaders.Clear();
+}
+
+bool
+nsHttpResponseHead::HasHeaderValue(nsHttpAtom h, const char *v)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+bool
+nsHttpResponseHead::HasHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mHeaders.HasHeader(h);
+}
+
+void
+nsHttpResponseHead::SetContentType(const nsACString &s)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mContentType = s;
+}
+
+void
+nsHttpResponseHead::SetContentCharset(const nsACString &s)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mContentCharset = s;
+}
+
+void
+nsHttpResponseHead::SetContentLength(int64_t len)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+
+ mContentLength = len;
+ if (len < 0)
+ mHeaders.ClearHeader(nsHttp::Content_Length);
+ else
+ mHeaders.SetHeader(nsHttp::Content_Length,
+ nsPrintfCString("%lld", len),
+ false,
+ nsHttpHeaderArray::eVarietyResponse);
+}
+
+void
+nsHttpResponseHead::Flatten(nsACString &buf, bool pruneTransients)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ if (mVersion == NS_HTTP_VERSION_0_9)
+ return;
+
+ buf.AppendLiteral("HTTP/");
+ if (mVersion == NS_HTTP_VERSION_2_0)
+ buf.AppendLiteral("2.0 ");
+ else if (mVersion == NS_HTTP_VERSION_1_1)
+ buf.AppendLiteral("1.1 ");
+ else
+ buf.AppendLiteral("1.0 ");
+
+ buf.Append(nsPrintfCString("%u", unsigned(mStatus)) +
+ NS_LITERAL_CSTRING(" ") +
+ mStatusText +
+ NS_LITERAL_CSTRING("\r\n"));
+
+
+ mHeaders.Flatten(buf, false, pruneTransients);
+}
+
+void
+nsHttpResponseHead::FlattenNetworkOriginalHeaders(nsACString &buf)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ if (mVersion == NS_HTTP_VERSION_0_9) {
+ return;
+ }
+
+ mHeaders.FlattenOriginalHeader(buf);
+}
+
+nsresult
+nsHttpResponseHead::ParseCachedHead(const char *block)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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;
+
+ ParseHeaderLine_locked(nsDependentCSubstring(block, p - block), false);
+
+ } while (1);
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpResponseHead::ParseCachedOriginalHeaders(char *block)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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 = {0};
+ 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, &val))) {
+
+ return NS_OK;
+ }
+
+ rv = mHeaders.SetResponseHeaderFromCache(hdr,
+ val,
+ nsHttpHeaderArray::eVarietyResponseNetOriginal);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ p = p + 2;
+ } while (1);
+
+ 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 421:
+ mStatusText.AssignLiteral("Misdirected Request");
+ 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;
+ default:
+ mStatusText.AssignLiteral("No Reason Phrase");
+ break;
+ }
+}
+
+void
+nsHttpResponseHead::ParseStatusLine(const nsACString &line)
+{
+
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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 == NS_HTTP_VERSION_0_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);
+ }
+ }
+
+ LOG(("Have status line [version=%u status=%u statusText=%s]\n",
+ unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
+}
+
+nsresult
+nsHttpResponseHead::ParseHeaderLine(const nsACString &line)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return ParseHeaderLine_locked(line, true);
+}
+
+nsresult
+nsHttpResponseHead::ParseHeaderLine_locked(const nsACString &line, bool originalFromNetHeaders)
+{
+ nsHttpAtom hdr = {0};
+ nsAutoCString val;
+
+ if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(line, &hdr, &val))) {
+ return NS_OK;
+ }
+ nsresult rv;
+ if (originalFromNetHeaders) {
+ rv = mHeaders.SetHeaderFromNet(hdr,
+ val,
+ true);
+ } else {
+ rv = mHeaders.SetResponseHeaderFromCache(hdr,
+ 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)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ *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;
+ }
+
+ // 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()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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::ProcessResponse
+ switch (mStatus) {
+ // Success codes
+ case 200:
+ case 203:
+ 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:
+ 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 (mCacheControlNoCache || mPragmaNoCache) {
+ 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::IsResumable()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ // 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 >= NS_HTTP_VERSION_1_1 &&
+ mHeaders.PeekHeader(nsHttp::Content_Length) &&
+ (mHeaders.PeekHeader(nsHttp::ETag) ||
+ mHeaders.PeekHeader(nsHttp::Last_Modified)) &&
+ mHeaders.HasHeaderValue(nsHttp::Accept_Ranges, "bytes");
+}
+
+bool
+nsHttpResponseHead::ExpiresInPast()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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;
+}
+
+nsresult
+nsHttpResponseHead::UpdateHeaders(nsHttpResponseHead *aOther)
+{
+ LOG(("nsHttpResponseHead::UpdateHeaders [this=%p]\n", this));
+
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ ReentrantMonitorAutoEnter monitorOther(aOther->mReentrantMonitor);
+
+ uint32_t i, count = aOther->mHeaders.Count();
+ for (i=0; i<count; ++i) {
+ nsHttpAtom header;
+ const char *val = aOther->mHeaders.PeekHeaderAt(i, header);
+
+ 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...
+ SetHeader_locked(header, nsDependentCString(val));
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttpResponseHead::Reset()
+{
+ LOG(("nsHttpResponseHead::Reset\n"));
+
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+
+ mHeaders.Clear();
+
+ mVersion = NS_HTTP_VERSION_1_1;
+ mStatus = 200;
+ mContentLength = -1;
+ mCacheControlPrivate = false;
+ mCacheControlNoStore = false;
+ mCacheControlNoCache = false;
+ mCacheControlImmutable = false;
+ 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)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return GetMaxAgeValue_locked(result);
+}
+
+nsresult
+nsHttpResponseHead::GetMaxAgeValue_locked(uint32_t *result) const
+{
+ const char *val = mHeaders.PeekHeader(nsHttp::Cache_Control);
+ if (!val)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ const char *p = nsHttp::FindToken(val, "max-age", HTTP_HEADER_VALUE_SEPS "=");
+ if (!p)
+ return NS_ERROR_NOT_AVAILABLE;
+ p += 7;
+ while (*p == ' ' || *p == '\t')
+ ++p;
+ if (*p != '=')
+ return NS_ERROR_NOT_AVAILABLE;
+ ++p;
+ while (*p == ' ' || *p == '\t')
+ ++p;
+
+ int maxAgeValue = atoi(p);
+ if (maxAgeValue < 0)
+ maxAgeValue = 0;
+ *result = static_cast<uint32_t>(maxAgeValue);
+ return NS_OK;
+}
+
+nsresult
+nsHttpResponseHead::GetDateValue(uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return GetDateValue_locked(result);
+}
+
+nsresult
+nsHttpResponseHead::GetExpiresValue(uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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);
+ ReentrantMonitorAutoEnter monitorOther(other.mReentrantMonitor);
+ ReentrantMonitorAutoEnter monitor(curr.mReentrantMonitor);
+
+ return mHeaders == aOther.mHeaders &&
+ mVersion == aOther.mVersion &&
+ mStatus == aOther.mStatus &&
+ mStatusText == aOther.mStatusText &&
+ mContentLength == aOther.mContentLength &&
+ mContentType == aOther.mContentType &&
+ mContentCharset == aOther.mContentCharset &&
+ mCacheControlPrivate == aOther.mCacheControlPrivate &&
+ mCacheControlNoCache == aOther.mCacheControlNoCache &&
+ mCacheControlNoStore == aOther.mCacheControlNoStore &&
+ mCacheControlImmutable == aOther.mCacheControlImmutable &&
+ mPragmaNoCache == aOther.mPragmaNoCache;
+}
+
+int64_t
+nsHttpResponseHead::TotalEntitySize()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ 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));
+
+ // make sure we have HTTP at the beginning
+ if (PL_strncasecmp(str, "HTTP", 4) != 0) {
+ 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 = NS_HTTP_VERSION_1_0;
+ return;
+ }
+ LOG(("looks like a HTTP/0.9 response\n"));
+ mVersion = NS_HTTP_VERSION_0_9;
+ return;
+ }
+ str += 4;
+
+ if (*str != '/') {
+ 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 = NS_HTTP_VERSION_1_0;
+ return;
+ }
+
+ char *p = PL_strchr(str, '.');
+ if (p == nullptr) {
+ LOG(("mal-formed server version; assuming HTTP/1.0\n"));
+ mVersion = NS_HTTP_VERSION_1_0;
+ return;
+ }
+
+ ++p; // let b point to the minor version
+
+ int major = atoi(str + 1);
+ int minor = atoi(p);
+
+ if ((major > 2) || ((major == 2) && (minor >= 0)))
+ mVersion = NS_HTTP_VERSION_2_0;
+ else if ((major == 1) && (minor >= 1))
+ // at least HTTP/1.1
+ mVersion = NS_HTTP_VERSION_1_1;
+ else
+ // treat anything else as version 1.0
+ mVersion = NS_HTTP_VERSION_1_0;
+}
+
+void
+nsHttpResponseHead::ParseCacheControl(const char *val)
+{
+ if (!(val && *val)) {
+ // clear flags
+ mCacheControlPrivate = false;
+ mCacheControlNoCache = false;
+ mCacheControlNoStore = false;
+ mCacheControlImmutable = false;
+ return;
+ }
+
+ // search header value for occurrence of "private"
+ if (nsHttp::FindToken(val, "private", HTTP_HEADER_VALUE_SEPS))
+ mCacheControlPrivate = true;
+
+ // search header value for occurrence(s) of "no-cache" but ignore
+ // occurrence(s) of "no-cache=blah"
+ if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS))
+ mCacheControlNoCache = true;
+
+ // search header value for occurrence of "no-store"
+ if (nsHttp::FindToken(val, "no-store", HTTP_HEADER_VALUE_SEPS))
+ mCacheControlNoStore = true;
+
+ // search header value for occurrence of "immutable"
+ if (nsHttp::FindToken(val, "immutable", HTTP_HEADER_VALUE_SEPS)) {
+ mCacheControlImmutable = true;
+ }
+}
+
+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.
+ if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS))
+ mPragmaNoCache = true;
+}
+
+nsresult
+nsHttpResponseHead::VisitHeaders(nsIHttpHeaderVisitor *visitor,
+ nsHttpHeaderArray::VisitorFilter filter)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+nsresult
+nsHttpResponseHead::GetOriginalHeader(nsHttpAtom aHeader,
+ nsIHttpHeaderVisitor *aVisitor)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.GetOriginalHeader(aHeader, aVisitor);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+bool
+nsHttpResponseHead::HasContentType()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return !mContentType.IsEmpty();
+}
+
+bool
+nsHttpResponseHead::HasContentCharset()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return !mContentCharset.IsEmpty();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpResponseHead.h b/netwerk/protocol/http/nsHttpResponseHead.h
new file mode 100644
index 0000000000..0a912f4b40
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpResponseHead_h__
+#define nsHttpResponseHead_h__
+
+#include "nsHttpHeaderArray.h"
+#include "nsHttp.h"
+#include "nsString.h"
+#include "mozilla/ReentrantMonitor.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 {
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead represents the status line and headers from an HTTP
+// response.
+//-----------------------------------------------------------------------------
+
+class nsHttpResponseHead
+{
+public:
+ nsHttpResponseHead() : mVersion(NS_HTTP_VERSION_1_1)
+ , mStatus(200)
+ , mContentLength(-1)
+ , mCacheControlPrivate(false)
+ , mCacheControlNoStore(false)
+ , mCacheControlNoCache(false)
+ , mCacheControlImmutable(false)
+ , mPragmaNoCache(false)
+ , mReentrantMonitor("nsHttpResponseHead.mReentrantMonitor")
+ , mInVisitHeaders(false) {}
+
+ nsHttpResponseHead(const nsHttpResponseHead &aOther);
+ nsHttpResponseHead &operator=(const nsHttpResponseHead &aOther);
+
+ void Enter() { mReentrantMonitor.Enter(); }
+ void Exit() { mReentrantMonitor.Exit(); }
+
+ nsHttpVersion Version();
+// X11's Xlib.h #defines 'Status' to 'int' on some systems!
+#undef Status
+ uint16_t Status();
+ void StatusText(nsACString &aStatusText);
+ int64_t ContentLength();
+ void ContentType(nsACString &aContentType);
+ void ContentCharset(nsACString &aContentCharset);
+ 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();
+
+ nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false);
+ nsresult GetHeader(nsHttpAtom h, nsACString &v);
+ void ClearHeader(nsHttpAtom h);
+ void ClearHeaders();
+ bool HasHeaderValue(nsHttpAtom h, const char *v);
+ bool HasHeader(nsHttpAtom h);
+
+ 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.
+ nsresult ParseCachedHead(const char *block);
+ nsresult ParseCachedOriginalHeaders(char *block);
+
+ // parse the status line.
+ void ParseStatusLine(const nsACString &line);
+
+ // parse a header line.
+ nsresult ParseHeaderLine(const nsACString &line);
+
+ // cache validation support methods
+ nsresult ComputeFreshnessLifetime(uint32_t *);
+ nsresult ComputeCurrentAge(uint32_t now, uint32_t requestTime,
+ uint32_t *result);
+ bool MustValidate();
+ bool MustValidateIfExpired();
+
+ // 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...
+ nsresult UpdateHeaders(nsHttpResponseHead *headers);
+
+ // reset the response head to it's initial state
+ void Reset();
+
+ nsresult GetAgeValue(uint32_t *result);
+ nsresult GetMaxAgeValue(uint32_t *result);
+ nsresult GetDateValue(uint32_t *result);
+ nsresult GetExpiresValue(uint32_t *result);
+ 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.
+ nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor,
+ nsHttpHeaderArray::VisitorFilter filter);
+ nsresult GetOriginalHeader(nsHttpAtom aHeader,
+ nsIHttpHeaderVisitor *aVisitor);
+
+ bool HasContentType();
+ bool HasContentCharset();
+private:
+ nsresult SetHeader_locked(nsHttpAtom 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);
+ nsresult ParseHeaderLine_locked(const nsACString &line, bool originalFromNetHeaders);
+
+ // these return failure if the header does not exist.
+ nsresult ParseDateHeader(nsHttpAtom header, uint32_t *result) const;
+
+ bool ExpiresInPast_locked() const;
+ nsresult GetAgeValue_locked(uint32_t *result) const;
+ nsresult GetExpiresValue_locked(uint32_t *result) const;
+ nsresult GetMaxAgeValue_locked(uint32_t *result) const;
+
+ nsresult GetDateValue_locked(uint32_t *result) const
+ {
+ return ParseDateHeader(nsHttp::Date, result);
+ }
+
+ nsresult GetLastModifiedValue_locked(uint32_t *result) const
+ {
+ return ParseDateHeader(nsHttp::Last_Modified, result);
+ }
+
+private:
+ // All members must be copy-constructable and assignable
+ nsHttpHeaderArray mHeaders;
+ nsHttpVersion mVersion;
+ uint16_t mStatus;
+ nsCString mStatusText;
+ int64_t mContentLength;
+ nsCString mContentType;
+ nsCString mContentCharset;
+ bool mCacheControlPrivate;
+ bool mCacheControlNoStore;
+ bool mCacheControlNoCache;
+ bool mCacheControlImmutable;
+ bool mPragmaNoCache;
+
+ // We are using ReentrantMonitor instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ ReentrantMonitor mReentrantMonitor;
+ // 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..ee3a884892
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -0,0 +1,2461 @@
+/* -*- 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 "base/basictypes.h"
+
+#include "nsHttpHandler.h"
+#include "nsHttpTransaction.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsHttpChunkedDecoder.h"
+#include "nsTransportUtils.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsIPipe.h"
+#include "nsCRT.h"
+#include "mozilla/Tokenizer.h"
+
+#include "nsISeekableStream.h"
+#include "nsMultiplexInputStream.h"
+#include "nsStringStream.h"
+
+#include "nsComponentManagerUtils.h" // do_CreateInstance
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "nsIHttpActivityObserver.h"
+#include "nsSocketTransportService2.h"
+#include "nsICancelable.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInputStream.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITransport.h"
+#include "nsIOService.h"
+#include "nsIRequestContext.h"
+#include "nsIHttpAuthenticator.h"
+#include <algorithm>
+
+#ifdef MOZ_WIDGET_GONK
+#include "NetStatistics.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+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 {
+
+//-----------------------------------------------------------------------------
+// helpers
+//-----------------------------------------------------------------------------
+
+static void
+LogHeaders(const char *lineStart)
+{
+ nsAutoCString buf;
+ char *endOfLine;
+ while ((endOfLine = PL_strstr(lineStart, "\r\n"))) {
+ buf.Assign(lineStart, endOfLine - lineStart);
+ if (PL_strcasestr(buf.get(), "authorization: ") ||
+ PL_strcasestr(buf.get(), "proxy-authorization: ")) {
+ char *p = PL_strchr(PL_strchr(buf.get(), ' ') + 1, ' ');
+ while (p && *++p)
+ *p = '*';
+ }
+ LOG3((" %s\n", buf.get()));
+ lineStart = endOfLine + 2;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction <public>
+//-----------------------------------------------------------------------------
+
+nsHttpTransaction::nsHttpTransaction()
+ : mLock("transaction lock")
+ , 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)
+ , mClassification(CLASS_GENERAL)
+ , mPipelinePosition(0)
+ , mHttpVersion(NS_HTTP_VERSION_UNKNOWN)
+ , mHttpResponseCode(0)
+ , mCurrentHttpResponseHeaderSize(0)
+ , mCapsToClear(0)
+ , mResponseIsComplete(false)
+ , mClosed(false)
+ , mConnected(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)
+ , mReportedStart(false)
+ , mReportedResponseHeader(false)
+ , mForTakeResponseHead(nullptr)
+ , mResponseHeadTaken(false)
+ , mSubmittedRatePacing(false)
+ , mPassedRatePacing(false)
+ , mSynchronousRatePaceRequest(false)
+ , mCountRecv(0)
+ , mCountSent(0)
+ , mAppId(NECKO_NO_APP_ID)
+ , mIsInIsolatedMozBrowser(false)
+ , mClassOfService(0)
+ , m0RTTInProgress(false)
+{
+ LOG(("Creating nsHttpTransaction @%p\n", this));
+ gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize);
+
+#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;
+}
+
+nsHttpTransaction::~nsHttpTransaction()
+{
+ LOG(("Destroying nsHttpTransaction @%p\n", this));
+ if (mTransactionObserver) {
+ mTransactionObserver->Complete(this, NS_OK);
+ }
+ 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 mForTakeResponseHead;
+ delete mChunkedDecoder;
+ ReleaseBlockingTransaction();
+}
+
+nsHttpTransaction::Classifier
+nsHttpTransaction::Classify()
+{
+ if (!(mCaps & NS_HTTP_ALLOW_PIPELINING))
+ return (mClassification = CLASS_SOLO);
+
+ if (mRequestHead->HasHeader(nsHttp::If_Modified_Since) ||
+ mRequestHead->HasHeader(nsHttp::If_None_Match))
+ return (mClassification = CLASS_REVALIDATION);
+
+ nsAutoCString accept;
+ bool hasAccept = NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Accept, accept));
+ if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("image/"))) {
+ return (mClassification = CLASS_IMAGE);
+ }
+
+ if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("text/css"))) {
+ return (mClassification = CLASS_SCRIPT);
+ }
+
+ mClassification = CLASS_GENERAL;
+
+ nsAutoCString requestURI;
+ mRequestHead->RequestURI(requestURI);
+ int32_t queryPos = requestURI.FindChar('?');
+ if (queryPos == kNotFound) {
+ if (StringEndsWith(requestURI,
+ NS_LITERAL_CSTRING(".js")))
+ mClassification = CLASS_SCRIPT;
+ }
+ else if (queryPos >= 3 &&
+ Substring(requestURI, queryPos - 3, 3).
+ EqualsLiteral(".js")) {
+ mClassification = CLASS_SCRIPT;
+ }
+
+ return mClassification;
+}
+
+nsresult
+nsHttpTransaction::Init(uint32_t caps,
+ nsHttpConnectionInfo *cinfo,
+ nsHttpRequestHead *requestHead,
+ nsIInputStream *requestBody,
+ bool requestBodyHasHeaders,
+ nsIEventTarget *target,
+ nsIInterfaceRequestor *callbacks,
+ nsITransportEventSink *eventsink,
+ nsIAsyncInputStream **responseBody)
+{
+ nsresult rv;
+
+ LOG(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
+
+ MOZ_ASSERT(cinfo);
+ MOZ_ASSERT(requestHead);
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ 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;
+ }
+ mChannel = do_QueryInterface(eventsink);
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(eventsink);
+ if (channel) {
+ NS_GetAppInfo(channel, &mAppId, &mIsInIsolatedMozBrowser);
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ if (mAppId != NECKO_NO_APP_ID) {
+ nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+ GetActiveNetworkInfo(activeNetworkInfo);
+ mActiveNetworkInfo =
+ new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo);
+ }
+#endif
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(eventsink);
+ if (httpChannelInternal) {
+ rv = httpChannelInternal->GetResponseTimeoutEnabled(
+ &mResponseTimeoutEnabled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ httpChannelInternal->GetInitialRwin(&mInitialRwin);
+ }
+
+ // 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;
+ }
+
+ // 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 ((requestHead->IsPost() || requestHead->IsPut()) &&
+ !requestBody && !requestHead->HasHeader(nsHttp::Transfer_Encoding)) {
+ requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0"));
+ }
+
+ // grab a weak reference to the request head
+ mRequestHead = requestHead;
+
+ // make sure we eliminate any proxy specific headers from
+ // the request if we are using CONNECT
+ bool pruneProxyHeaders = cinfo->UsingConnect();
+
+ mReqHeaderBuf.Truncate();
+ requestHead->Flatten(mReqHeaderBuf, pruneProxyHeaders);
+
+ if (LOG3_ENABLED()) {
+ LOG3(("http request [\n"));
+ LogHeaders(mReqHeaderBuf.get());
+ LOG3(("]\n"));
+ }
+
+ // If the request body does not include headers or if there is no request
+ // body, then we must add the header/body separator manually.
+ if (!requestBodyHasHeaders || !requestBody)
+ mReqHeaderBuf.AppendLiteral("\r\n");
+
+ // report the request header
+ if (mActivityDistributor)
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER,
+ PR_Now(), 0,
+ mReqHeaderBuf);
+
+ // 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.get(),
+ mReqHeaderBuf.Length());
+ if (NS_FAILED(rv)) return rv;
+
+ mHasRequestBody = !!requestBody;
+ if (mHasRequestBody) {
+ // some non standard methods set a 0 byte content-length for
+ // clarity, we can avoid doing the mulitplexed request stream for them
+ uint64_t size;
+ if (NS_SUCCEEDED(requestBody->Available(&size)) && !size) {
+ mHasRequestBody = false;
+ }
+ }
+
+ if (mHasRequestBody) {
+ // wrap the headers and request body in a multiplexed input stream.
+ nsCOMPtr<nsIMultiplexInputStream> multi =
+ do_CreateInstance(kMultiplexInputStream, &rv);
+ 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).
+ rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), multi,
+ nsIOService::gDefaultSegmentSize);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else
+ mRequestStream = headers;
+
+ nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mChannel);
+ nsIInputChannelThrottleQueue* queue;
+ if (throttled) {
+ rv = throttled->GetThrottleQueue(&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));
+ mRequestStream = do_QueryInterface(wrappedStream);
+ }
+ }
+ }
+
+ uint64_t size_u64;
+ rv = mRequestStream->Available(&size_u64);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // make sure it fits within js MAX_SAFE_INTEGER
+ mRequestSize = InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -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;
+
+#ifdef WIN32 // bug 1153929
+ MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
+ uint32_t * vtable = (uint32_t *) mPipeOut.get();
+ MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
+#endif // WIN32
+
+ Classify();
+
+ nsCOMPtr<nsIAsyncInputStream> tmp(mPipeIn);
+ tmp.forget(responseBody);
+ return NS_OK;
+}
+
+// This method should only be used on the socket thread
+nsAHttpConnection *
+nsHttpTransaction::Connection()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mConnection.get();
+}
+
+already_AddRefed<nsAHttpConnection>
+nsHttpTransaction::GetConnectionReference()
+{
+ MutexAutoLock lock(mLock);
+ RefPtr<nsAHttpConnection> connection(mConnection);
+ return connection.forget();
+}
+
+nsHttpResponseHead *
+nsHttpTransaction::TakeResponseHead()
+{
+ MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
+
+ // Lock RestartInProgress() and TakeResponseHead() against main thread
+ MutexAutoLock lock(*nsHttp::GetLock());
+
+ mResponseHeadTaken = true;
+
+ // Prefer mForTakeResponseHead over mResponseHead. It is always a complete
+ // set of headers.
+ nsHttpResponseHead *head;
+ if (mForTakeResponseHead) {
+ head = mForTakeResponseHead;
+ mForTakeResponseHead = nullptr;
+ return head;
+ }
+
+ // 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;
+ }
+
+ head = mResponseHead;
+ mResponseHead = nullptr;
+ return head;
+}
+
+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;
+ }
+}
+
+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)
+{
+ LOG(("nsHttpTransaction::OnSocketStatus [this=%p status=%x progress=%lld]\n",
+ this, 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);
+ }
+ }
+
+ // 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) {
+ SetConnectEnd(TimeStamp::Now());
+ }
+ }
+
+ if (!mTransportSink)
+ return;
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // 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))
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT,
+ PR_Now(), 0, EmptyCString());
+
+ // report the status and progress
+ if (!mRestartInProgressVerifier.IsDiscardingContent())
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
+ static_cast<uint32_t>(status),
+ PR_Now(),
+ progress,
+ EmptyCString());
+ }
+
+ // 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) {
+ LOG(("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) {
+ LOG(("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(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!");
+ mCapsToClear |= NS_HTTP_REFRESH_DNS;
+}
+
+uint64_t
+nsHttpTransaction::Available()
+{
+ uint64_t size;
+ if (NS_FAILED(mRequestStream->Available(&size)))
+ size = 0;
+ return size;
+}
+
+nsresult
+nsHttpTransaction::ReadRequestSegment(nsIInputStream *stream,
+ void *closure,
+ const char *buf,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ nsHttpTransaction *trans = (nsHttpTransaction *) closure;
+ nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
+ if (NS_FAILED(rv)) return rv;
+
+ if (trans->TimingEnabled()) {
+ // Set the timestamp to Now(), only if it null
+ trans->SetRequestStart(TimeStamp::Now(), true);
+ }
+
+ trans->CountSentBytes(*countRead);
+ trans->mSentData = true;
+ return NS_OK;
+}
+
+nsresult
+nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTransactionDone) {
+ *countRead = 0;
+ return mStatus;
+ }
+
+ if (!mConnected && !m0RTTInProgress) {
+ mConnected = true;
+ mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+ }
+
+ mDeferredSendProgress = false;
+ mReader = reader;
+ nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
+ mReader = nullptr;
+
+ 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;
+ 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
+
+ MOZ_ASSERT(*countWritten > 0, "bad writer");
+ trans->CountRecvBytes(*countWritten);
+ 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.
+}
+
+nsresult
+nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten)
+{
+ static bool reentrantFlag = false;
+ LOG(("nsHttpTransaction::WriteSegments %p reentrantFlag=%d",
+ this, reentrantFlag));
+ MOZ_DIAGNOSTIC_ASSERT(!reentrantFlag);
+ reentrantFlag = true;
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTransactionDone) {
+ reentrantFlag = false;
+ return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
+ }
+
+ mWriter = writer;
+
+#ifdef WIN32 // bug 1153929
+ MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
+ uint32_t * vtable = (uint32_t *) mPipeOut.get();
+ MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
+#endif // WIN32
+
+ if (!mPipeOut) {
+ reentrantFlag = false;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ 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;
+ 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;
+ }
+ }
+
+ reentrantFlag = false;
+ return rv;
+}
+
+nsresult
+nsHttpTransaction::SaveNetworkStats(bool enforce)
+{
+#ifdef MOZ_WIDGET_GONK
+ // Check if active network and appid are valid.
+ if (!mActiveNetworkInfo || mAppId == NECKO_NO_APP_ID) {
+ return NS_OK;
+ }
+
+ if (mCountRecv <= 0 && mCountSent <= 0) {
+ // There is no traffic, no need to save.
+ return NS_OK;
+ }
+
+ // If |enforce| is false, the traffic amount is saved
+ // only when the total amount exceeds the predefined
+ // threshold.
+ uint64_t totalBytes = mCountRecv + mCountSent;
+ if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
+ return NS_OK;
+ }
+
+ // Create the event to save the network statistics.
+ // the event is then dispatched to the main thread.
+ RefPtr<Runnable> event =
+ new SaveNetworkStatsEvent(mAppId, mIsInIsolatedMozBrowser, mActiveNetworkInfo,
+ mCountRecv, mCountSent, false);
+ NS_DispatchToMainThread(event);
+
+ // Reset the counters after saving.
+ mCountSent = 0;
+ mCountRecv = 0;
+
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+void
+nsHttpTransaction::Close(nsresult reason)
+{
+ LOG(("nsHttpTransaction::Close [this=%p reason=%x]\n", this, reason));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (reason == NS_BINDING_RETARGETED) {
+ LOG((" close %p skipped due to ERETARGETED\n", this));
+ return;
+ }
+
+ if (mClosed) {
+ LOG((" already closed\n"));
+ return;
+ }
+
+ if (mTransactionObserver) {
+ mTransactionObserver->Complete(this, reason);
+ mTransactionObserver = nullptr;
+ }
+
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(reason);
+ mTokenBucketCancel = nullptr;
+ }
+
+ if (mActivityDistributor) {
+ // report the reponse is complete if not already reported
+ if (!mResponseIsComplete)
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
+ PR_Now(),
+ static_cast<uint64_t>(mContentRead),
+ EmptyCString());
+
+ // report that this transaction is closing
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,
+ PR_Now(), 0, EmptyCString());
+ }
+
+ // we must no longer reference the connection! find out if the
+ // connection was being reused before letting it go.
+ bool connReused = false;
+ if (mConnection) {
+ connReused = mConnection->IsReused();
+ }
+ 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) &&
+ (!(mCaps & NS_HTTP_STICKY_CONNECTION) || (mCaps & NS_HTTP_CONNECTION_RESTARTABLE))) {
+
+ 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;
+ LOG(("transaction force restarted\n"));
+ 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 (!mReceivedData &&
+ ((mRequestHead && mRequestHead->IsSafeMethod()) ||
+ !reallySentData || connReused)) {
+ // if restarting fails, then we must proceed to close the pipe,
+ // which will notify the channel that the transaction failed.
+
+ if (mPipelinePosition) {
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline,
+ nullptr, 0);
+ }
+ if (NS_SUCCEEDED(Restart()))
+ return;
+ }
+ else if (!mResponseIsComplete && mPipelinePosition &&
+ reason == NS_ERROR_NET_RESET) {
+ // due to unhandled rst on a pipeline - safe to
+ // restart as only idempotent is found there
+
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, nullptr, 0);
+ if (NS_SUCCEEDED(RestartInProgress()))
+ return;
+ }
+ }
+
+ if ((mChunkedDecoder || (mContentLength >= int64_t(0))) &&
+ (NS_SUCCEEDED(reason) && !mResponseIsComplete)) {
+
+ NS_WARNING("Partial transfer, incomplete HTTP response received");
+
+ if ((mHttpResponseCode / 100 == 2) &&
+ (mHttpVersion >= NS_HTTP_VERSION_1_1)) {
+ FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing();
+ if (clevel >= FRAMECHECK_BARELY) {
+ if ((clevel == FRAMECHECK_STRICT) ||
+ (mChunkedDecoder && mChunkedDecoder->GetChunkRemaining()) ||
+ (!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)) {
+ if (!mResponseIsComplete) {
+ // The response has not been delimited with a high-confidence
+ // algorithm like Content-Length or Chunked Encoding. We
+ // need to use a strong framing mechanism to pipeline.
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
+ nullptr, mClassification);
+ }
+ else if (mPipelinePosition) {
+ // report this success as feedback
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::GoodCompletedOK,
+ nullptr, mPipelinePosition);
+ }
+
+ // 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;
+ ParseHead(&data, 1, &unused);
+
+ if (mResponseHead->Version() == NS_HTTP_VERSION_0_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)
+ relConn = false;
+ }
+
+ // 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 (relConn && mConnection) {
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+ }
+
+ // save network statistics in the end of transaction
+ SaveNetworkStats(true);
+
+ mStatus = reason;
+ mTransactionDone = true; // forcibly flag the transaction as complete
+ mClosed = true;
+ ReleaseBlockingTransaction();
+
+ // release some resources that we no longer need
+ mRequestStream = nullptr;
+ mReqHeaderBuf.Truncate();
+ mLineBuf.Truncate();
+ if (mChunkedDecoder) {
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ }
+
+ // closing this pipe triggers the channel's OnStopRequest method.
+ mPipeOut->CloseWithStatus(reason);
+
+#ifdef WIN32 // bug 1153929
+ MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
+ uint32_t * vtable = (uint32_t *) mPipeOut.get();
+ MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
+ mPipeOut = nullptr; // just in case
+#endif // WIN32
+}
+
+nsHttpConnectionInfo *
+nsHttpTransaction::ConnectionInfo()
+{
+ return mConnInfo.get();
+}
+
+nsresult
+nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+nsHttpTransaction::PipelineDepth()
+{
+ return IsDone() ? 0 : 1;
+}
+
+nsresult
+nsHttpTransaction::SetPipelinePosition(int32_t position)
+{
+ mPipelinePosition = position;
+ return NS_OK;
+}
+
+int32_t
+nsHttpTransaction::PipelinePosition()
+{
+ return mPipelinePosition;
+}
+
+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::RestartInProgress()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if ((mRestartCount + 1) >= gHttpHandler->MaxRequestAttempts()) {
+ LOG(("nsHttpTransaction::RestartInProgress() "
+ "reached max request attempts, failing transaction %p\n", this));
+ return NS_ERROR_NET_RESET;
+ }
+
+ // Lock RestartInProgress() and TakeResponseHead() against main thread
+ MutexAutoLock lock(*nsHttp::GetLock());
+
+ // Don't try and RestartInProgress() things that haven't gotten a response
+ // header yet. Those should be handled under the normal restart() path if
+ // they are eligible.
+ if (!mHaveAllHeaders)
+ return NS_ERROR_NET_RESET;
+
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ return NS_ERROR_NET_RESET;
+ }
+
+ // don't try and restart 0.9 or non 200/Get HTTP/1
+ if (!mRestartInProgressVerifier.IsSetup())
+ return NS_ERROR_NET_RESET;
+
+ LOG(("Will restart transaction %p and skip first %lld bytes, "
+ "old Content-Length %lld",
+ this, mContentRead, mContentLength));
+
+ mRestartInProgressVerifier.SetAlreadyProcessed(
+ std::max(mRestartInProgressVerifier.AlreadyProcessed(), mContentRead));
+
+ if (!mResponseHeadTaken && !mForTakeResponseHead) {
+ // TakeResponseHeader() has not been called yet and this
+ // is the first restart. Store the resp headers exclusively
+ // for TakeResponseHead() which is called from the main thread and
+ // could happen at any time - so we can't continue to modify those
+ // headers (which restarting will effectively do)
+ mForTakeResponseHead = mResponseHead;
+ mResponseHead = nullptr;
+ }
+
+ 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;
+
+ return Restart();
+}
+
+nsresult
+nsHttpTransaction::Restart()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // 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;
+
+ // 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...
+ 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;
+
+ // disable pipelining for the next attempt in case pipelining caused the
+ // reset. this is being overly cautious since we don't know if pipelining
+ // was the problem here.
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ SetPipelinePosition(0);
+
+ if (!mConnInfo->GetRoutedHost().IsEmpty()) {
+ MutexAutoLock lock(*nsHttp::GetLock());
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ if (mRequestHead) {
+ mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, NS_LITERAL_CSTRING("0"));
+ }
+ }
+
+ return gHttpHandler->InitiateTransaction(this, mPriority);
+}
+
+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.0";
+ static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 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 0;
+ }
+ // 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 0;
+ }
+
+ // 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;
+ }
+
+ // 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 0;
+}
+
+nsresult
+nsHttpTransaction::ParseLine(nsACString &line)
+{
+ LOG(("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() == NS_HTTP_VERSION_0_9)
+ mHaveAllHeaders = true;
+ }
+ else {
+ rv = mResponseHead->ParseHeaderLine(line);
+ }
+ return rv;
+}
+
+nsresult
+nsHttpTransaction::ParseLineSegment(char *segment, uint32_t len)
+{
+ NS_PRECONDITION(!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)) {
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedCorruptedContent,
+ nullptr, 0);
+ 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;
+
+ NS_PRECONDITION(!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;
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START,
+ PR_Now(), 0, EmptyCString());
+ }
+ }
+
+ 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(EmptyCString());
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ if (mResponseHead) {
+ 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;
+ if (!mRestartInProgressVerifier.IsSetup())
+ mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset);
+
+ // 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;
+ MOZ_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->ConnMgr()->ClearHostMapping(mConnInfo);
+
+ // 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;
+ }
+
+ if (mResponseHead->Status() == 200 &&
+ mConnection->IsProxyConnectInProgress()) {
+ // successful CONNECTs do not have response bodies
+ mNoContent = true;
+ }
+ mConnection->SetLastTransactionExpectedNoContent(mNoContent);
+ if (mInvalidResponseBytesRead)
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
+ nullptr, mClassification);
+
+ if (mNoContent)
+ mContentLength = 0;
+ else {
+ // grab the content-length from the response headers
+ mContentLength = mResponseHead->ContentLength();
+
+ if ((mClassification != CLASS_SOLO) &&
+ (mContentLength > mMaxPipelineObjectSize))
+ CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
+
+ // 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() >= NS_HTTP_VERSION_1_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"));
+ }
+ if (mRestartInProgressVerifier.IsSetup() &&
+ !mRestartInProgressVerifier.Verify(mContentLength, mResponseHead)) {
+ LOG(("Restart in progress subsequent transaction failed to match"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ mDidContentStart = true;
+
+ // The verifier only initializes itself once (from the first iteration of
+ // a transaction that gets far enough to have response headers)
+ if (mRequestHead->IsGet())
+ mRestartInProgressVerifier.Set(mContentLength, mResponseHead);
+
+ 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 >= NS_HTTP_VERSION_1_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;
+ }
+
+ int64_t toReadBeforeRestart =
+ mRestartInProgressVerifier.ToReadBeforeRestart();
+
+ if (toReadBeforeRestart && *contentRead) {
+ uint32_t ignore =
+ static_cast<uint32_t>(std::min<int64_t>(toReadBeforeRestart, UINT32_MAX));
+ ignore = std::min(*contentRead, ignore);
+ LOG(("Due To Restart ignoring %d of remaining %ld",
+ ignore, toReadBeforeRestart));
+ *contentRead -= ignore;
+ mContentRead += ignore;
+ mRestartInProgressVerifier.HaveReadBeforeRestart(ignore);
+ memmove(buf, buf + ignore, *contentRead + *contentRemaining);
+ }
+
+ if (*contentRead) {
+ // update count of content bytes read and report progress...
+ mContentRead += *contentRead;
+ }
+
+ LOG(("nsHttpTransaction::HandleContent [this=%p count=%u read=%u mContentRead=%lld mContentLength=%lld]\n",
+ this, count, *contentRead, mContentRead, mContentLength));
+
+ // Check the size of chunked responses. If we exceed the max pipeline size
+ // for this response reschedule the pipeline
+ if ((mClassification != CLASS_SOLO) &&
+ mChunkedDecoder &&
+ ((mContentRead + mChunkedDecoder->GetChunkRemaining()) >
+ mMaxPipelineObjectSize)) {
+ CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
+ }
+
+ // check for end-of-file
+ if ((mContentRead == mContentLength) ||
+ (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
+ // 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)
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
+ PR_Now(),
+ static_cast<uint64_t>(mContentRead),
+ EmptyCString());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpTransaction::ProcessData(char *buf, uint32_t count, uint32_t *countRead)
+{
+ nsresult rv;
+
+ LOG(("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");
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER,
+ PR_Now(), 0,
+ completeResponseHeaders);
+ }
+ }
+
+ // 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 pipelined transaction
+ //
+ // 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) {
+ MOZ_ASSERT(mConnection);
+ mConnection->PushBack(buf + *countRead, countRemaining);
+ }
+
+ if (!mContentDecodingCheck && mResponseHead) {
+ mContentDecoding =
+ mResponseHead->HasHeader(nsHttp::Content_Encoding);
+ mContentDecodingCheck = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttpTransaction::CancelPipeline(uint32_t reason)
+{
+ // reason is casted through a uint to avoid compiler header deps
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo,
+ static_cast<nsHttpConnectionMgr::PipelineFeedbackInfoType>(reason),
+ nullptr, mClassification);
+
+ mConnection->CancelPipeline(NS_ERROR_ABORT);
+
+ // Avoid pipelining this transaction on restart by classifying it as solo.
+ // This also prevents BadUnexpectedLarge from being reported more
+ // than one time per transaction.
+ mClassification = CLASS_SOLO;
+}
+
+
+void
+nsHttpTransaction::SetRequestContext(nsIRequestContext *aRequestContext)
+{
+ LOG(("nsHttpTransaction %p SetRequestContext %p\n", this, aRequestContext));
+ mRequestContext = aRequestContext;
+}
+
+// Called when the transaction marked for blocking is associated with a connection
+// (i.e. added to a new h1 conn, an idle http connection, or placed into
+// a http pipeline). 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)
+ 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()));
+ gHttpHandler->ConnMgr()->ProcessPendingQ();
+ }
+
+ 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::CheckForStickyAuthScheme()
+{
+ LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p"));
+
+ MOZ_ASSERT(mHaveAllHeaders);
+ MOZ_ASSERT(mResponseHead);
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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;
+ }
+
+ Tokenizer p(auth);
+ nsAutoCString schema;
+ while (p.ReadWord(schema)) {
+ ToLowerCase(schema);
+
+ nsAutoCString contractid;
+ contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
+ contractid.Append(schema);
+
+ // using a new instance because of thread safety of auth modules refcnt
+ nsCOMPtr<nsIHttpAuthenticator> authenticator(do_CreateInstance(contractid.get()));
+ if (authenticator) {
+ uint32_t flags;
+ authenticator->GetAuthFlags(&flags);
+ if (flags & nsIHttpAuthenticator::CONNECTION_BASED) {
+ LOG((" connection made sticky, found %s auth shema", schema.get()));
+ // 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;
+ break;
+ }
+ }
+
+ // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader
+ p.SkipUntil(Tokenizer::Token::NewLine());
+ p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
+ }
+}
+
+const TimingStruct
+nsHttpTransaction::Timings()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ TimingStruct timings = mTimings;
+ return timings;
+}
+
+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::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)
+ : 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;
+ gHttpHandler->SubmitPacedRequest(this, getter_AddRefs(mTokenBucketCancel));
+ mSynchronousRatePaceRequest = false;
+ return mPassedRatePacing;
+}
+
+void
+nsHttpTransaction::OnTokenBucketAdmitted()
+{
+ mPassedRatePacing = true;
+ mTokenBucketCancel = nullptr;
+
+ if (!mSynchronousRatePaceRequest)
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+}
+
+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;
+ NS_PRECONDITION(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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ mWaitingOnPipeOut = false;
+ if (mConnection) {
+ mConnection->TransactionHasDataToRecv(this);
+ nsresult rv = mConnection->ResumeRecv();
+ if (NS_FAILED(rv))
+ NS_ERROR("ResumeRecv failed");
+ }
+ return NS_OK;
+}
+
+// nsHttpTransaction::RestartVerifier
+
+static bool
+matchOld(nsHttpResponseHead *newHead, nsCString &old,
+ nsHttpAtom headerAtom)
+{
+ nsAutoCString val;
+
+ newHead->GetHeader(headerAtom, val);
+ if (!val.IsEmpty() && old.IsEmpty())
+ return false;
+ if (val.IsEmpty() && !old.IsEmpty())
+ return false;
+ if (!val.IsEmpty() && !old.Equals(val))
+ return false;
+ return true;
+}
+
+bool
+nsHttpTransaction::RestartVerifier::Verify(int64_t contentLength,
+ nsHttpResponseHead *newHead)
+{
+ if (mContentLength != contentLength)
+ return false;
+
+ if (newHead->Status() != 200)
+ return false;
+
+ if (!matchOld(newHead, mContentRange, nsHttp::Content_Range))
+ return false;
+
+ if (!matchOld(newHead, mLastModified, nsHttp::Last_Modified))
+ return false;
+
+ if (!matchOld(newHead, mETag, nsHttp::ETag))
+ return false;
+
+ if (!matchOld(newHead, mContentEncoding, nsHttp::Content_Encoding))
+ return false;
+
+ if (!matchOld(newHead, mTransferEncoding, nsHttp::Transfer_Encoding))
+ return false;
+
+ return true;
+}
+
+void
+nsHttpTransaction::RestartVerifier::Set(int64_t contentLength,
+ nsHttpResponseHead *head)
+{
+ if (mSetup)
+ return;
+
+ // If mSetup does not transition to true RestartInPogress() is later
+ // forbidden
+
+ // Only RestartInProgress with 200 response code
+ if (!head || (head->Status() != 200)) {
+ return;
+ }
+
+ mContentLength = contentLength;
+
+ nsAutoCString val;
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::ETag, val))) {
+ mETag = val;
+ }
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Last_Modified, val))) {
+ mLastModified = val;
+ }
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Range, val))) {
+ mContentRange = val;
+ }
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Encoding, val))) {
+ mContentEncoding = val;
+ }
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Transfer_Encoding, val))) {
+ mTransferEncoding = val;
+ }
+
+ // We can only restart with any confidence if we have a stored etag or
+ // last-modified header
+ if (mETag.IsEmpty() && mLastModified.IsEmpty()) {
+ return;
+ }
+
+ mSetup = true;
+}
+
+void
+nsHttpTransaction::GetNetworkAddresses(NetAddr &self, NetAddr &peer)
+{
+ MutexAutoLock lock(mLock);
+ self = mSelfAddr;
+ peer = mPeerAddr;
+}
+
+bool
+nsHttpTransaction::Do0RTT()
+{
+ if (mRequestHead->IsSafeMethod() &&
+ !mConnection->IsProxyConnectInProgress()) {
+ m0RTTInProgress = true;
+ }
+ return m0RTTInProgress;
+}
+
+nsresult
+nsHttpTransaction::Finish0RTT(bool aRestart)
+{
+ MOZ_ASSERT(m0RTTInProgress);
+ m0RTTInProgress = false;
+ 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;
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h
new file mode 100644
index 0000000000..ab0b267a75
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -0,0 +1,487 @@
+/* -*- 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 nsHttpTransaction_h__
+#define nsHttpTransaction_h__
+
+#include "nsHttp.h"
+#include "nsAHttpTransaction.h"
+#include "nsAHttpConnection.h"
+#include "EventTokenBucket.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "TimingStruct.h"
+#include "Http2Push.h"
+#include "mozilla/net/DNS.h"
+#include "ARefBase.h"
+#include "AlternateServices.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsProxyRelease.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+class nsIHttpActivityObserver;
+class nsIEventTarget;
+class nsIInputStream;
+class nsIOutputStream;
+class nsIRequestContext;
+
+namespace mozilla { namespace net {
+
+class nsHttpChunkedDecoder;
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction represents a single HTTP transaction. It is thread-safe,
+// intended to run on the socket thread.
+//-----------------------------------------------------------------------------
+
+class nsHttpTransaction final : public nsAHttpTransaction
+ , public ATokenBucketEvent
+ , public nsIInputStreamCallback
+ , public nsIOutputStreamCallback
+ , public ARefBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ nsHttpTransaction();
+
+ //
+ // 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 responseBody
+ // the input stream that will contain the response data. async
+ // wait on this input stream for data. on first notification,
+ // headers should be available (check transaction status).
+ //
+ nsresult Init(uint32_t caps,
+ nsHttpConnectionInfo *connInfo,
+ nsHttpRequestHead *reqHeaders,
+ nsIInputStream *reqBody,
+ bool reqBodyIncludesHeaders,
+ nsIEventTarget *consumerTarget,
+ nsIInterfaceRequestor *callbacks,
+ nsITransportEventSink *eventsink,
+ nsIAsyncInputStream **responseBody);
+
+ // attributes
+ nsHttpResponseHead *ResponseHead() { return mHaveAllHeaders ? mResponseHead : nullptr; }
+ nsISupports *SecurityInfo() { return mSecurityInfo; }
+
+ nsIEventTarget *ConsumerTarget() { return mConsumerTarget; }
+ nsISupports *HttpChannel() { return mChannel; }
+
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
+
+ // Called to take ownership of the response headers; the transaction
+ // will drop any reference to the response headers after this call.
+ nsHttpResponseHead *TakeResponseHead();
+
+ // Provides a thread safe reference of the connection
+ // nsHttpTransaction::Connection should only be used on the socket thread
+ already_AddRefed<nsAHttpConnection> GetConnectionReference();
+
+ // Called to set/find out if the transaction generated a complete response.
+ bool ResponseIsComplete() { return mResponseIsComplete; }
+ void SetResponseIsComplete() { mResponseIsComplete = true; }
+
+ bool ProxyConnectFailed() { return mProxyConnectFailed; }
+
+ void EnableKeepAlive() { mCaps |= NS_HTTP_ALLOW_KEEPALIVE; }
+ void MakeSticky() { mCaps |= NS_HTTP_STICKY_CONNECTION; }
+
+ // SetPriority() may only be used by the connection manager.
+ void SetPriority(int32_t priority) { mPriority = priority; }
+ int32_t Priority() { return mPriority; }
+
+ enum Classifier Classification() { return mClassification; }
+
+ 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) { mPendingTime = now ? TimeStamp::Now() : TimeStamp(); }
+ const TimeStamp GetPendingTime() { return mPendingTime; }
+ bool UsesPipelining() const { return mCaps & NS_HTTP_ALLOW_PIPELINING; }
+
+ // overload of nsAHttpTransaction::RequestContext()
+ nsIRequestContext *RequestContext() override { return mRequestContext.get(); }
+ void SetRequestContext(nsIRequestContext *aRequestContext);
+ void DispatchedAsBlocking();
+ void RemoveDispatchedAsBlocking();
+
+ nsHttpTransaction *QueryHttpTransaction() override { return this; }
+
+ Http2PushedStream *GetPushedStream() { return mPushedStream; }
+ Http2PushedStream *TakePushedStream()
+ {
+ Http2PushedStream *r = mPushedStream;
+ mPushedStream = nullptr;
+ return r;
+ }
+ void SetPushedStream(Http2PushedStream *push) { mPushedStream = push; }
+ uint32_t InitialRwin() const { return mInitialRwin; };
+ bool ChannelPipeFull() { return mWaitingOnPipeOut; }
+
+ // Locked methods to get and set timing info
+ const TimingStruct Timings();
+ void SetDomainLookupStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetDomainLookupEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ 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);
+
+ mozilla::TimeStamp GetDomainLookupStart();
+ mozilla::TimeStamp GetDomainLookupEnd();
+ mozilla::TimeStamp GetConnectStart();
+ mozilla::TimeStamp GetConnectEnd();
+ mozilla::TimeStamp GetRequestStart();
+ mozilla::TimeStamp GetResponseStart();
+ mozilla::TimeStamp GetResponseEnd();
+
+ int64_t GetTransferSize() { return mTransferSize; }
+
+ bool Do0RTT() override;
+ nsresult Finish0RTT(bool aRestart) override;
+private:
+ friend class DeleteHttpTransaction;
+ virtual ~nsHttpTransaction();
+
+ nsresult Restart();
+ nsresult RestartInProgress();
+ char *LocateHttpStart(char *buf, uint32_t len,
+ bool aAllowPartialMatch);
+ nsresult ParseLine(nsACString &line);
+ nsresult ParseLineSegment(char *seg, uint32_t len);
+ nsresult ParseHead(char *, uint32_t count, uint32_t *countRead);
+ nsresult HandleContentStart();
+ nsresult HandleContent(char *, uint32_t count, uint32_t *contentRead, uint32_t *contentRemaining);
+ nsresult ProcessData(char *, uint32_t, uint32_t *);
+ void DeleteSelfOnConsumerThread();
+ void ReleaseBlockingTransaction();
+
+ Classifier Classify();
+ void CancelPipeline(uint32_t reason);
+
+ static nsresult ReadRequestSegment(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+ static nsresult WritePipeSegment(nsIOutputStream *, void *, char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ bool TimingEnabled() const { return mCaps & NS_HTTP_TIMING_ENABLED; }
+
+ bool ResponseTimeoutEnabled() const final;
+
+ void DisableSpdy() override;
+ 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);
+
+private:
+ class UpdateSecurityCallbacks : public Runnable
+ {
+ public:
+ UpdateSecurityCallbacks(nsHttpTransaction* aTrans,
+ nsIInterfaceRequestor* aCallbacks)
+ : 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;
+
+ nsCOMPtr<nsISupports> mChannel;
+ nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
+
+ nsCString mReqHeaderBuf; // flattened request headers
+ nsCOMPtr<nsIInputStream> mRequestStream;
+ int64_t mRequestSize;
+
+ RefPtr<nsAHttpConnection> mConnection;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ 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;
+
+ Http2PushedStream *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;
+ enum Classifier mClassification;
+ int32_t mPipelinePosition;
+ int64_t mMaxPipelineObjectSize;
+
+ nsHttpVersion mHttpVersion;
+ uint16_t mHttpResponseCode;
+
+ uint32_t mCurrentHttpResponseHeaderSize;
+
+ // 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;
+
+ // state flags, all logically boolean, but not packed together into a
+ // bitfield so as to avoid bitfield-induced races. See bug 560579.
+ bool mClosed;
+ bool mConnected;
+ 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;
+
+ // 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()
+ nsHttpResponseHead *mForTakeResponseHead;
+ bool mResponseHeadTaken;
+
+ // The time when the transaction was submitted to the Connection Manager
+ TimeStamp mPendingTime;
+
+ class RestartVerifier
+ {
+
+ // When a idemptotent transaction has received part of its response body
+ // and incurs an error it can be restarted. To do this we mark the place
+ // where we stopped feeding the body to the consumer and start the
+ // network call over again. If everything we track (headers, length, etc..)
+ // matches up to the place where we left off then the consumer starts being
+ // fed data again with the new information. This can be done N times up
+ // to the normal restart (i.e. with no response info) limit.
+
+ public:
+ RestartVerifier()
+ : mContentLength(-1)
+ , mAlreadyProcessed(0)
+ , mToReadBeforeRestart(0)
+ , mSetup(false)
+ {}
+ ~RestartVerifier() {}
+
+ void Set(int64_t contentLength, nsHttpResponseHead *head);
+ bool Verify(int64_t contentLength, nsHttpResponseHead *head);
+ bool IsDiscardingContent() { return mToReadBeforeRestart != 0; }
+ bool IsSetup() { return mSetup; }
+ int64_t AlreadyProcessed() { return mAlreadyProcessed; }
+ void SetAlreadyProcessed(int64_t val) {
+ mAlreadyProcessed = val;
+ mToReadBeforeRestart = val;
+ }
+ int64_t ToReadBeforeRestart() { return mToReadBeforeRestart; }
+ void HaveReadBeforeRestart(uint32_t amt)
+ {
+ MOZ_ASSERT(amt <= mToReadBeforeRestart,
+ "too large of a HaveReadBeforeRestart deduction");
+ mToReadBeforeRestart -= amt;
+ }
+
+ private:
+ // This is the data from the first complete response header
+ // used to make sure that all subsequent response headers match
+
+ int64_t mContentLength;
+ nsCString mETag;
+ nsCString mLastModified;
+ nsCString mContentRange;
+ nsCString mContentEncoding;
+ nsCString mTransferEncoding;
+
+ // This is the amount of data that has been passed to the channel
+ // from previous iterations of the transaction and must therefore
+ // be skipped in the new one.
+ int64_t mAlreadyProcessed;
+
+ // The amount of data that must be discarded in the current iteration
+ // (where iteration > 0) to reach the mAlreadyProcessed high water
+ // mark.
+ int64_t mToReadBeforeRestart;
+
+ // true when ::Set has been called with a response header
+ bool mSetup;
+ } mRestartInProgressVerifier;
+
+// 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);
+
+private:
+ bool mSubmittedRatePacing;
+ bool mPassedRatePacing;
+ bool mSynchronousRatePaceRequest;
+ nsCOMPtr<nsICancelable> mTokenBucketCancel;
+
+// These members are used for network per-app metering (bug 746073)
+// Currently, they are only available on gonk.
+ uint64_t mCountRecv;
+ uint64_t mCountSent;
+ uint32_t mAppId;
+ bool mIsInIsolatedMozBrowser;
+#ifdef MOZ_WIDGET_GONK
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+ nsresult SaveNetworkStats(bool);
+ void CountRecvBytes(uint64_t recvBytes)
+ {
+ mCountRecv += recvBytes;
+ SaveNetworkStats(false);
+ }
+ void CountSentBytes(uint64_t sentBytes)
+ {
+ mCountSent += sentBytes;
+ SaveNetworkStats(false);
+ }
+public:
+ void SetClassOfService(uint32_t cos) { mClassOfService = cos; }
+ uint32_t ClassOfService() { return mClassOfService; }
+private:
+ uint32_t 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; }
+
+private:
+ RefPtr<ASpdySession> mTunnelProvider;
+
+public:
+ void SetTransactionObserver(TransactionObserver *arg) { mTransactionObserver = arg; }
+private:
+ RefPtr<TransactionObserver> mTransactionObserver;
+public:
+ void GetNetworkAddresses(NetAddr &self, NetAddr &peer);
+
+private:
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+
+ bool m0RTTInProgress;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpTransaction_h__
diff --git a/netwerk/protocol/http/nsICorsPreflightCallback.h b/netwerk/protocol/http/nsICorsPreflightCallback.h
new file mode 100644
index 0000000000..49ca392a8a
--- /dev/null
+++ b/netwerk/protocol/http/nsICorsPreflightCallback.h
@@ -0,0 +1,28 @@
+/* -*- 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/nsIHstsPrimingCallback.idl b/netwerk/protocol/http/nsIHstsPrimingCallback.idl
new file mode 100644
index 0000000000..01f53a5b21
--- /dev/null
+++ b/netwerk/protocol/http/nsIHstsPrimingCallback.idl
@@ -0,0 +1,50 @@
+/* -*- 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"
+
+/**
+ * HSTS priming attempts to prevent mixed-content by looking for the
+ * Strict-Transport-Security header as a signal from the server that it is
+ * safe to upgrade HTTP to HTTPS.
+ *
+ * Since mixed-content blocking happens very early in the process in AsyncOpen2,
+ * the status of mixed-content blocking is stored in the LoadInfo and then used
+ * to determine whether to send a priming request or not.
+ *
+ * This interface is implemented by nsHttpChannel so that it can receive the
+ * result of HSTS priming.
+ */
+[builtinclass, uuid(eca6daca-3f2a-4a2a-b3bf-9f24f79bc999)]
+interface nsIHstsPrimingCallback : nsISupports
+{
+ /**
+ * HSTS priming has succeeded with an STS header, and the site asserts it is
+ * safe to upgrade the request from HTTP to HTTPS. The request may still be
+ * blocked based on the user's preferences.
+ *
+ * May be invoked synchronously if HSTS priming has already been performed
+ * for the host.
+ *
+ * @param aCached whether the result was already in the HSTS cache
+ */
+ [noscript, nostdcall]
+ void onHSTSPrimingSucceeded(in bool aCached);
+ /**
+ * HSTS priming has seen no STS header, the request itself has failed,
+ * or some other failure which does not constitute a positive signal that the
+ * site can be upgraded safely to HTTPS. The request may still be allowed
+ * based on the user's preferences.
+ *
+ * May be invoked synchronously if HSTS priming has already been performed
+ * for the host.
+ *
+ * @param aError The error which caused this failure, or NS_ERROR_CONTENT_BLOCKED
+ * @param aCached whether the result was already in the HSTS cache
+ */
+ [noscript, nostdcall]
+ void onHSTSPrimingFailed(in nsresult aError, in bool aCached);
+};
diff --git a/netwerk/protocol/http/nsIHttpActivityObserver.idl b/netwerk/protocol/http/nsIHttpActivityObserver.idl
new file mode 100644
index 0000000000..72a6f28d29
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpActivityObserver.idl
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * 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
+ */
+ 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.
+ */
+ readonly attribute boolean isActive;
+
+ 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, 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..9cfe1f6a73
--- /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, 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
+ */
+ 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
+ */
+ 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.
+ */
+ void clearAll();
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl
new file mode 100644
index 0000000000..f02e20b912
--- /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;
+
+[scriptable, uuid(701093ac-5c7f-429c-99e3-423b041fccb4)]
+interface nsIHttpAuthenticableChannel : nsIProxiedChannel
+{
+ /**
+ * If the channel being authenticated is using SSL.
+ */
+ readonly attribute boolean isSSL;
+
+ /**
+ * Returns if the proxy HTTP method used is CONNECT. If no proxy is being
+ * used it must return PR_FALSE.
+ */
+ readonly attribute boolean proxyMethodIsConnect;
+
+ /**
+ * Cancels the current request. See nsIRequest.
+ */
+ void cancel(in nsresult aStatus);
+
+ /**
+ * The load flags of this request. See nsIRequest.
+ */
+ readonly attribute nsLoadFlags loadFlags;
+
+ /**
+ * The URI corresponding to the channel. See nsIChannel.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The load group of this request. It is here for querying its
+ * notificationCallbacks. See nsIRequest.
+ */
+ readonly attribute nsILoadGroup loadGroup;
+
+ /**
+ * The notification callbacks for the channel. See nsIChannel.
+ */
+ readonly attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * The HTTP request method. See nsIHttpChannel.
+ */
+ readonly attribute ACString requestMethod;
+
+ /**
+ * The "Server" response header.
+ * Return NS_ERROR_NOT_AVAILABLE if not available.
+ */
+ readonly attribute ACString serverResponseHeader;
+
+ /**
+ * The Proxy-Authenticate response header.
+ */
+ readonly attribute ACString proxyChallenges;
+
+ /**
+ * The WWW-Authenticate response header.
+ */
+ readonly attribute ACString WWWChallenges;
+
+ /**
+ * Sets the Proxy-Authorization request header. An empty string
+ * will clear it.
+ */
+ void setProxyCredentials(in ACString credentials);
+
+ /**
+ * Sets the Authorization request header. An empty string
+ * will clear it.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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..6777245b32
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthenticator.idl
@@ -0,0 +1,223 @@
+/* -*- 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.
+ */
+[scriptable, 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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);
+};
+
+%{C++
+#define NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX \
+ "@mozilla.org/network/http-authenticator;1?scheme="
+%}
diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl
new file mode 100644
index 0000000000..75ec2c7398
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -0,0 +1,472 @@
+/* -*- 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;
+
+/**
+ * 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 : nsIChannel
+{
+ /**************************************************************************
+ * 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.
+ */
+ attribute ACString requestMethod;
+
+ /**
+ * Get/set the HTTP referrer URI. This is the address (URI) of the
+ * resource from which this channel's URI was obtained (see RFC2616 section
+ * 14.36).
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * 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.
+ */
+ attribute nsIURI referrer;
+
+ /**
+ * Referrer policies. See ReferrerPolicy.h for more details.
+ */
+
+
+ /* The undefined state, usually used to check the need of
+ overruling document-wide referrer policy. */
+ const unsigned long REFERRER_POLICY_UNSET = 4294967295; // UINT32_MAX
+
+ /* default state, a shorter name for no-referrer-when-downgrade */
+ const unsigned long REFERRER_POLICY_DEFAULT = 0;
+ /* default state, doesn't send referrer from https->http */
+ const unsigned long REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE = 0;
+ /* sends no referrer */
+ const unsigned long REFERRER_POLICY_NO_REFERRER = 1;
+ /* only sends the origin of the referring URL */
+ const unsigned long REFERRER_POLICY_ORIGIN = 2;
+ /* same as default, but reduced to ORIGIN when cross-origin. */
+ const unsigned long REFERRER_POLICY_ORIGIN_WHEN_XORIGIN = 3;
+ /* always sends the referrer, even on downgrade. */
+ const unsigned long REFERRER_POLICY_UNSAFE_URL = 4;
+ /* send referrer when same-origin, no referrer when cross-origin */
+ const unsigned long REFERRER_POLICY_SAME_ORIGIN = 5;
+ /* send origin when request from https->https or http->http(s)
+ No referrer when request from https->http. */
+ const unsigned long REFERRER_POLICY_STRICT_ORIGIN = 6;
+ /* send referrer when same-origin,
+ Send origin when cross-origin from https->https or http->http(s)
+ No referrer when request from https->http. */
+ const unsigned long REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN = 7;
+
+ /**
+ * Get the HTTP referrer policy. The policy is retrieved from the meta
+ * referrer tag, which can be one of many values (see ReferrerPolicy.h for
+ * more details).
+ */
+ readonly attribute unsigned long referrerPolicy;
+
+ /**
+ * Set the HTTP referrer URI with a referrer policy.
+ * @throws NS_ERROR_FAILURE if called during visitRequestHeaders.
+ */
+ void setReferrerWithPolicy(in nsIURI referrer, in unsigned long referrerPolicy);
+
+ /**
+ * 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).
+ */
+ readonly attribute ACString protocolVersion;
+
+ /**
+ * size consumed by the response header fields and the response payload body
+ */
+ readonly attribute uint64_t transferSize;
+
+ /**
+ * The size of the message body received by the client,
+ * after removing any applied content-codings
+ */
+ readonly attribute uint64_t decodedBodySize;
+
+ /**
+ * The size in octets of the payload body, prior to removing content-codings
+ */
+ 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.
+ */
+ 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.
+ */
+ void setRequestHeader(in ACString aHeader,
+ in ACString aValue,
+ in boolean aMerge);
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ void visitNonDefaultRequestHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * This attribute is a hint to the channel to indicate whether or not
+ * the underlying HTTP transaction should be allowed to be pipelined
+ * with other transactions. This should be set to FALSE, for example,
+ * if the application knows that the corresponding document is likely
+ * to be very large.
+ *
+ * This attribute is true by default, though other factors may prevent
+ * pipelining.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * @throws NS_ERROR_FAILURE if set after the channel has been opened.
+ */
+ 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.
+ */
+ 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ 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 iff LOAD_DOCUMENT_URI is set in the channel's loadflags.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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).
+ */
+ 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.
+ */
+ 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ 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.
+ */
+ void redirectTo(in nsIURI aTargetURI);
+
+ /**
+ * Identifies the request context for this load.
+ */
+ [noscript] attribute nsID requestContextID;
+
+ /**
+ * 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.
+ */
+ attribute ACString channelId;
+
+ /**
+ * ID of the top-level document's inner window. Identifies the content
+ * this channels is being load in.
+ */
+ attribute uint64_t topLevelContentWindowId;
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
new file mode 100644
index 0000000000..eaece8c545
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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.
+ */
+
+[scriptable, 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ void addAuthorizationHeaders(in boolean dontUseCachedWWWCreds);
+
+ /**
+ * Check if an unnecessary(and maybe malicious) url authentication has been
+ * provided.
+ */
+ void checkForSuperfluousAuth();
+
+ /**
+ * Cancel pending user auth prompts and release the callback and channel
+ * weak references.
+ */
+ void disconnect(in nsresult status);
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelChild.idl b/netwerk/protocol/http/nsIHttpChannelChild.idl
new file mode 100644
index 0000000000..187a3342cc
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelChild.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"
+
+[ptr] native RequestHeaderTuples(mozilla::net::RequestHeaderTuples);
+[ref] native OptionalCorsPreflightArgsRef(mozilla::OptionalCorsPreflightArgs);
+
+interface nsIPrincipal;
+interface nsIURI;
+
+[uuid(d02b96ed-2789-4f42-a25c-7abe63de7c18)]
+interface nsIHttpChannelChild : nsISupports
+{
+ void addCookiesToRequest();
+
+ // Mark this channel as requiring an interception; this will propagate
+ // to the corresponding parent channel when a redirect occurs.
+ void forceIntercepted(in boolean postRedirectChannelShouldIntercept,
+ in boolean postRedirectChannelShouldUpgrade);
+
+ // Headers that the channel client has set via SetRequestHeader.
+ readonly attribute RequestHeaderTuples clientSetRequestHeaders;
+
+ // Headers that the channel client has set via SetRequestHeader.
+ [notxpcom, nostdcall]
+ void GetClientSetCorsPreflightParameters(in OptionalCorsPreflightArgsRef args);
+
+ // This method is called by nsCORSListenerProxy if we need to remove
+ // an entry from the CORS preflight cache in the parent process.
+ void removeCorsPreflightCacheEntry(in nsIURI aURI, in nsIPrincipal aRequestingPrincipal);
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl
new file mode 100644
index 0000000000..e0332286f0
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -0,0 +1,314 @@
+/* -*- 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++
+#include "nsTArrayForwardDeclare.h"
+template<class T> class nsCOMArray;
+class nsCString;
+namespace mozilla { namespace net { class nsHttpChannel; } }
+%}
+[ptr] native StringArray(nsTArray<nsCString>);
+[ref] native StringArrayRef(const nsTArray<nsCString>);
+[ref] native securityMessagesArray(nsCOMArray<nsISecurityConsoleMessage>);
+[ptr] native nsHttpChannelPtr(mozilla::net::nsHttpChannel);
+
+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
+{
+ void onTransportAvailable(in nsISocketTransport aTransport,
+ in nsIAsyncInputStream aSocketIn,
+ in nsIAsyncOutputStream aSocketOut);
+};
+
+/**
+ * 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
+ */
+ attribute nsIURI documentURI;
+
+ /**
+ * Get the major/minor version numbers for the request
+ */
+ void getRequestVersion(out unsigned long major, out unsigned long minor);
+
+ /**
+ * Get the major/minor version numbers for the response
+ */
+ 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] 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.
+ */
+ void setCookie(in string aCookieHeader);
+
+ /**
+ * Setup this channel as an application cache fallback channel.
+ */
+ void setupFallbackChannel(in string aFallbackKey);
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ attribute boolean forceAllowThirdPartyCookie;
+
+ /**
+ * True iff the channel has been canceled.
+ */
+ readonly attribute boolean canceled;
+
+ /**
+ * External handlers may set this to true to notify the channel
+ * that it is open on behalf of a download.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ readonly attribute int32_t remotePort;
+
+ /**
+ * Transfer chain of redirected cache-keys.
+ */
+ [noscript] 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.
+ *
+ * The onStartRequest and onStopRequest events are still delivered and the
+ * listener gets full control over the socket if and when onTransportAvailable
+ * is delievered.
+ *
+ * @param aProtocolName
+ * The value of the HTTP Upgrade request header
+ * @param aListener
+ * The callback object used to handle a successful upgrade
+ */
+ void HTTPUpgrade(in ACString aProtocolName,
+ in nsIHttpUpgradeListener aListener);
+
+ /**
+ * Enable/Disable Spdy negotiation on per channel basis.
+ * The network.http.spdy.enabled preference is still a pre-requisite
+ * for starting spdy.
+ */
+ attribute boolean allowSpdy;
+
+ /**
+ * This attribute en/disables the timeout for the first byte of an HTTP
+ * response. Enabled by default.
+ */
+ 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.
+ */
+ attribute unsigned long initialRwin;
+
+ /**
+ * Get value of the URI passed to nsIHttpChannel.redirectTo() if any.
+ * May return null when redirectTo() has not been called.
+ */
+ readonly attribute nsIURI apiRedirectToURI;
+
+ /**
+ * Enable/Disable use of Alternate Services with this channel.
+ * The network.http.altsvc.enabled preference is still a pre-requisite.
+ */
+ 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.
+ */
+ attribute boolean beConservative;
+
+ readonly attribute PRTime lastModifiedTime;
+
+ /**
+ * Force a channel that has not been AsyncOpen'ed to skip any check for possible
+ * interception and proceed immediately to open a previously-synthesized cache
+ * entry using the provided ID.
+ */
+ void forceIntercepted(in uint64_t aInterceptionID);
+
+ readonly attribute boolean responseSynthesized;
+
+ /**
+ * Set by nsCORSListenerProxy if credentials should be included in
+ * cross-origin requests. false indicates "same-origin", users should still
+ * check flag LOAD_ANONYMOUS!
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ attribute unsigned long fetchCacheMode;
+
+ /**
+ * The URI of the top-level window that's associated with this channel.
+ */
+ readonly attribute nsIURI topWindowURI;
+
+ /**
+ * The network interface id that's associated with this channel.
+ */
+ attribute ACString networkInterfaceId;
+
+ /**
+ * Read the proxy URI, which, if non-null, will be used to resolve
+ * proxies for this channel.
+ */
+ 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 StringArrayRef unsafeHeaders);
+
+ /**
+ * 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.
+ */
+ attribute AString integrityMetadata;
+
+ /**
+ * The connection info's hash key. We use it to test connection separation.
+ */
+ readonly attribute ACString connectionInfoHashKey;
+
+ /**
+ * Returns nsHttpChannel (self) when this actually is implementing nsHttpChannel.
+ */
+ [noscript, notxpcom, nostdcall]
+ nsHttpChannelPtr queryHttpChannelImpl();
+};
diff --git a/netwerk/protocol/http/nsIHttpEventSink.idl b/netwerk/protocol/http/nsIHttpEventSink.idl
new file mode 100644
index 0000000000..d003fbe15f
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpEventSink.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 nsIChannel;
+interface nsIHttpChannel;
+interface nsIURI;
+
+/**
+ * nsIHttpEventSink
+ *
+ * Implement this interface to receive control over various HTTP events. The
+ * HTTP channel will try to get this interface from its notificationCallbacks
+ * attribute, and if it doesn't find it there it will look for it from its
+ * loadGroup's notificationCallbacks attribute.
+ *
+ * These methods are called before onStartRequest, and should be handled
+ * SYNCHRONOUSLY.
+ *
+ * @deprecated Newly written code should use nsIChannelEventSink instead of this
+ * interface.
+ */
+[scriptable, uuid(9475a6af-6352-4251-90f9-d65b1cd2ea15)]
+interface nsIHttpEventSink : nsISupports
+{
+ /**
+ * Called when a redirect occurs due to a HTTP response like 302. The
+ * redirection may be to a non-http channel.
+ *
+ * @return failure cancels redirect
+ */
+ void onRedirect(in nsIHttpChannel httpChannel,
+ in nsIChannel newChannel);
+};
diff --git a/netwerk/protocol/http/nsIHttpHeaderVisitor.idl b/netwerk/protocol/http/nsIHttpHeaderVisitor.idl
new file mode 100644
index 0000000000..809f7c4a4c
--- /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
+ */
+ 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..f333a557ce
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
@@ -0,0 +1,126 @@
+/* -*- 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"
+
+[scriptable, uuid(c48126d9-2ddd-485b-a51a-378e917e75f8)]
+interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler
+{
+ /**
+ * Get the HTTP advertised user agent string.
+ */
+ readonly attribute ACString userAgent;
+
+ /**
+ * Get the application name.
+ *
+ * @return The name of this application (eg. "Mozilla").
+ */
+ readonly attribute ACString appName;
+
+ /**
+ * Get the application version string.
+ *
+ * @return The complete version (major and minor) string. (eg. "5.0")
+ */
+ readonly attribute ACString appVersion;
+
+ /**
+ * Get the current platform.
+ *
+ * @return The platform this application is running on
+ * (eg. "Windows", "Macintosh", "X11")
+ */
+ readonly attribute ACString platform;
+
+ /**
+ * Get the current oscpu.
+ *
+ * @return The oscpu this application is running on
+ */
+ readonly attribute ACString oscpu;
+
+ /**
+ * Get the application comment misc portion.
+ */
+ readonly attribute ACString misc;
+
+};
+
+%{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"
+
+/**
+ * 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"
+
+/**
+ * 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"
+
+/**
+ * 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 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"
+
+/**
+ * Before an HTTP request corresponding to a channel with the LOAD_DOCUMENT_URI
+ * flag is sent to the server, this observer topic is notified. The observer of
+ * this topic can then choose to modify the user agent for this request before
+ * the request is actually sent to the server. Additionally, the modified user
+ * agent will be propagated to sub-resource requests from the same load group.
+ */
+#define NS_HTTP_ON_USERAGENT_REQUEST_TOPIC "http-on-useragent-request"
+
+
+%}
diff --git a/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
new file mode 100644
index 0000000000..cf4437904a
--- /dev/null
+++ b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ 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
+{
+ void verify(in ACString aJSON,
+ in ACString aOrigin,
+ in long aAlternatePort);
+
+ readonly attribute bool valid;
+ readonly attribute bool mixed; /* mixed-scheme */
+ readonly attribute long lifetime;
+};
diff --git a/netwerk/protocol/moz.build b/netwerk/protocol/moz.build
new file mode 100644
index 0000000000..e47f73df44
--- /dev/null
+++ b/netwerk/protocol/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 += sorted(CONFIG['NECKO_PROTOCOLS'])
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
new file mode 100644
index 0000000000..238bc344a5
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -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/. */
+
+#include "ExtensionProtocolHandler.h"
+
+#include "nsIAddonPolicyService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIStreamListener.h"
+#include "nsIRequestObserver.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsIPipe.h"
+#include "nsNetUtil.h"
+#include "LoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler, nsISubstitutingProtocolHandler,
+ nsIProtocolHandler, nsIProtocolHandlerWithDynamicFlags,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+
+nsresult
+ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags)
+{
+ // 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.
+ nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
+ bool loadableByAnyone = false;
+ if (aps) {
+ nsresult rv = aps->ExtensionURILoadableByAnyone(aURI, &loadableByAnyone);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aFlags = URI_STD | URI_IS_LOCAL_RESOURCE | (loadableByAnyone ? (URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE) : URI_DANGEROUS_TO_LOAD);
+ return NS_OK;
+}
+
+class PipeCloser : public nsIRequestObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit PipeCloser(nsIOutputStream* aOutputStream) :
+ mOutputStream(aOutputStream)
+ {
+ }
+
+ NS_IMETHOD OnStartRequest(nsIRequest*, nsISupports*) override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest*, nsISupports*, nsresult aStatusCode) override
+ {
+ NS_ENSURE_TRUE(mOutputStream, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = mOutputStream->Close();
+ mOutputStream = nullptr;
+ return rv;
+ }
+
+protected:
+ virtual ~PipeCloser() {}
+
+private:
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+};
+
+NS_IMPL_ISUPPORTS(PipeCloser, nsIRequestObserver)
+
+bool
+ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult)
+{
+ // Create special moz-extension:-pages such as moz-extension://foo/_blank.html
+ // 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("/_blank.html")) {
+ aResult.AssignLiteral("about:blank");
+ return true;
+ }
+ if (aPathname.EqualsLiteral("/_generated_background_page.html")) {
+ nsCOMPtr<nsIAddonPolicyService> aps =
+ do_GetService("@mozilla.org/addons/policy-service;1");
+ if (!aps) {
+ return false;
+ }
+ nsresult rv = aps->GetGeneratedBackgroundPageUrl(aHost, aResult);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (!aResult.IsEmpty()) {
+ MOZ_RELEASE_ASSERT(Substring(aResult, 0, 5).Equals("data:"));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult
+ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ext;
+ rv = url->GetFileExtension(ext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!ext.LowerCaseEqualsLiteral("css")) {
+ return NS_OK;
+ }
+
+ // Filter CSS files to replace locale message tokens with localized strings.
+
+ nsCOMPtr<nsIStreamConverterService> convService =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
+ const char* kToType = "text/css";
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ if (aLoadInfo &&
+ aLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
+ // If the channel needs to enforce CORS, we need to open the channel async.
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(outputStream),
+ 0, UINT32_MAX, true, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsIRequestObserver> observer = new PipeCloser(outputStream);
+ rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), outputStream, observer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = convService->AsyncConvertData(kFromType, kToType, listener,
+ aURI, getter_AddRefs(converter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest();
+ (*result)->SetLoadInfo(loadInfo);
+
+ rv = (*result)->AsyncOpen2(converter);
+ } else {
+ // Stylesheet loads for extension content scripts require a sync channel.
+
+ nsCOMPtr<nsIInputStream> sourceStream;
+ if (aLoadInfo && aLoadInfo->GetEnforceSecurity()) {
+ rv = (*result)->Open2(getter_AddRefs(sourceStream));
+ } else {
+ rv = (*result)->Open(getter_AddRefs(sourceStream));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = convService->Convert(sourceStream, kFromType, kToType,
+ aURI, getter_AddRefs(inputStream));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI, inputStream,
+ NS_LITERAL_CSTRING("text/css"),
+ NS_LITERAL_CSTRING("utf-8"),
+ aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.swap(*result);
+ return NS_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..081f8cd624
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.h
@@ -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/. */
+
+#ifndef ExtensionProtocolHandler_h___
+#define ExtensionProtocolHandler_h___
+
+#include "SubstitutingProtocolHandler.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+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::)
+
+ ExtensionProtocolHandler() : SubstitutingProtocolHandler("moz-extension") {}
+
+protected:
+ ~ExtensionProtocolHandler() {}
+
+ bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ virtual nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* ExtensionProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
new file mode 100644
index 0000000000..99061e0f71
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -0,0 +1,404 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Unused.h"
+
+#include "SubstitutingProtocolHandler.h"
+#include "nsIChannel.h"
+#include "nsIIOService.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsEscape.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);
+
+//---------------------------------------------------------------------------------
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile resolution
+//---------------------------------------------------------------------------------
+
+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;
+}
+
+NS_IMETHODIMP
+SubstitutingURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kSubstitutingURLCID;
+ return NS_OK;
+}
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme, uint32_t aFlags,
+ bool aEnforceFileOrJar)
+ : mScheme(aScheme)
+ , mSubstitutions(16)
+ , mEnforceFileOrJar(aEnforceFileOrJar)
+{
+ mFlags.emplace(aFlags);
+ ConstructInternal();
+}
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme)
+ : mScheme(aScheme)
+ , 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(InfallibleTArray<SubstitutionMapping>& aMappings)
+{
+ for (auto iter = mSubstitutions.ConstIter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIURI> uri = iter.Data();
+ SerializedURI serialized;
+ if (uri) {
+ nsresult rv = uri->GetSpec(serialized.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uri->GetOriginCharset(serialized.charset);
+ }
+ SubstitutionMapping substitution = { mScheme, nsCString(iter.Key()), serialized };
+ aMappings.AppendElement(substitution);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
+{
+ 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);
+ aBaseURI->GetOriginCharset(mapping.resolvedURI.charset);
+ }
+
+ 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 **result)
+{
+ nsresult rv;
+
+ RefPtr<SubstitutingURL> url = new SubstitutingURL();
+ if (!url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // 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)
+ 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 (last < src)
+ spec.Append(last, src-last);
+
+ rv = url->Init(nsIStandardURL::URLTYPE_STANDARD, -1, spec, aCharset, aBaseURI);
+ if (NS_SUCCEEDED(rv)) {
+ url.forget(result);
+ }
+ return rv;
+}
+
+nsresult
+SubstitutingProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ 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);
+
+ rv = NS_NewChannelInternal(result, newURI, aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsLoadFlags loadFlags = 0;
+ (*result)->GetLoadFlags(&loadFlags);
+ (*result)->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
+ rv = (*result)->SetOriginalURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SubstituteChannel(uri, aLoadInfo, result);
+}
+
+nsresult
+SubstitutingProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, 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)
+{
+ if (!baseURI) {
+ mSubstitutions.Remove(root);
+ return SendSubstitution(root, baseURI);
+ }
+
+ // 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")) {
+ NS_WARNING("Refusing to create substituting URI to non-file:// target");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mSubstitutions.Put(root, baseURI);
+ return SendSubstitution(root, baseURI);
+ }
+
+ // baseURI is a same-type substituting URI, let's resolve it first.
+ nsAutoCString newBase;
+ rv = ResolveURI(baseURI, newBase);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> newBaseURI;
+ rv = mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSubstitutions.Put(root, newBaseURI);
+ return SendSubstitution(root, newBaseURI);
+}
+
+nsresult
+SubstitutingProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ if (mSubstitutions.Get(root, result))
+ return NS_OK;
+
+ return GetSubstitutionInternal(root, result);
+}
+
+nsresult
+SubstitutingProtocolHandler::HasSubstitution(const nsACString& root, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *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->GetPath(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.
+ path.InsertLiteral(".", 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..a59c5595d5
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -0,0 +1,107 @@
+/* -*- 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 "nsInterfaceHashtable.h"
+#include "nsIOService.h"
+#include "nsStandardURL.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/Maybe.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_REFCOUNTING(SubstitutingProtocolHandler);
+ NS_DECL_NON_VIRTUAL_NSIPROTOCOLHANDLER;
+ NS_DECL_NON_VIRTUAL_NSISUBSTITUTINGPROTOCOLHANDLER;
+
+ bool HasSubstitution(const nsACString& aRoot) const { return mSubstitutions.Get(aRoot, nullptr); }
+
+ nsresult CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aResources);
+
+protected:
+ virtual ~SubstitutingProtocolHandler() {}
+ void ConstructInternal();
+
+ nsresult SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI);
+
+ // Override this in the subclass to try additional lookups after checking
+ // mSubstitutions.
+ virtual nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult)
+ {
+ *aResult = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Override this in the subclass to check for special case when resolving URIs
+ // _before_ checking substitutions.
+ virtual bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult)
+ {
+ return false;
+ }
+
+ // Override this in the subclass to check for special case when opening
+ // channels.
+ virtual nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result)
+ {
+ return NS_OK;
+ }
+
+ nsIIOService* IOService() { return mIOService; }
+
+private:
+ nsCString mScheme;
+ Maybe<uint32_t> mFlags;
+ nsInterfaceHashtable<nsCStringHashKey,nsIURI> mSubstitutions;
+ nsCOMPtr<nsIIOService> mIOService;
+
+ // In general, we expect the principal of a document loaded from a
+ // substituting URI to be a codebase 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;
+};
+
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile resolution
+class SubstitutingURL : public nsStandardURL
+{
+public:
+ SubstitutingURL() : nsStandardURL(true) {}
+ virtual nsStandardURL* StartClone();
+ virtual nsresult EnsureFile();
+ NS_IMETHOD GetClassIDNoAlloc(nsCID *aCID);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/moz.build b/netwerk/protocol/res/moz.build
new file mode 100644
index 0000000000..37e2316b05
--- /dev/null
+++ b/netwerk/protocol/res/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 += [
+ 'nsIResProtocolHandler.idl',
+ 'nsISubstitutingProtocolHandler.idl',
+]
+
+XPIDL_MODULE = 'necko_res'
+
+UNIFIED_SOURCES += [
+ 'ExtensionProtocolHandler.cpp',
+ 'nsResProtocolHandler.cpp',
+ 'SubstitutingProtocolHandler.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
diff --git a/netwerk/protocol/res/nsIResProtocolHandler.idl b/netwerk/protocol/res/nsIResProtocolHandler.idl
new file mode 100644
index 0000000000..56c597f4c7
--- /dev/null
+++ b/netwerk/protocol/res/nsIResProtocolHandler.idl
@@ -0,0 +1,14 @@
+/* -*- 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
+{
+};
diff --git a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
new file mode 100644
index 0000000000..e2c816a855
--- /dev/null
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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
+{
+ /**
+ * Sets the substitution for the root key:
+ * resource://root/path ==> baseURI.resolve(path)
+ *
+ * A null baseURI removes the specified substitution.
+ *
+ * A root key should always be lowercase; however, this may not be
+ * enforced.
+ */
+ void setSubstitution(in ACString root, in nsIURI baseURI);
+
+ /**
+ * Gets the substitution for the root key.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if none exists.
+ */
+ nsIURI getSubstitution(in ACString root);
+
+ /**
+ * Returns TRUE if the substitution exists and FALSE otherwise.
+ */
+ 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.
+ */
+ 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..265bab9ec9
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Unused.h"
+
+#include "nsResProtocolHandler.h"
+#include "nsIIOService.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+
+#include "mozilla/Omnijar.h"
+
+using mozilla::dom::ContentParent;
+using mozilla::LogLevel;
+using mozilla::Unused;
+
+#define kAPP "app"
+#define kGRE "gre"
+
+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;
+ }
+
+ //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;
+}
+
+//----------------------------------------------------------------------------
+// nsResProtocolHandler::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(nsResProtocolHandler, nsIResProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+
+nsresult
+nsResProtocolHandler::GetSubstitutionInternal(const nsACString& root, nsIURI **result)
+{
+ nsAutoCString uri;
+
+ if (!ResolveSpecialCases(root, NS_LITERAL_CSTRING("/"), NS_LITERAL_CSTRING("/"), uri)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_NewURI(result, uri);
+}
+
+bool
+nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult)
+{
+ if (aHost.Equals("") || aHost.Equals(kAPP)) {
+ aResult.Assign(mAppURI);
+ } else if (aHost.Equals(kGRE)) {
+ aResult.Assign(mGREURI);
+ } else {
+ return false;
+ }
+ aResult.Append(aPath);
+ return true;
+}
+
+nsresult
+nsResProtocolHandler::SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
+{
+ MOZ_ASSERT(!aRoot.Equals(""));
+ MOZ_ASSERT(!aRoot.Equals(kAPP));
+ MOZ_ASSERT(!aRoot.Equals(kGRE));
+ return SubstitutingProtocolHandler::SetSubstitution(aRoot, aBaseURI);
+}
diff --git a/netwerk/protocol/res/nsResProtocolHandler.h b/netwerk/protocol/res/nsResProtocolHandler.h
new file mode 100644
index 0000000000..00f8d1b132
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -0,0 +1,65 @@
+/* -*- 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 nsResProtocolHandler_h___
+#define nsResProtocolHandler_h___
+
+#include "SubstitutingProtocolHandler.h"
+
+#include "nsIResProtocolHandler.h"
+#include "nsInterfaceHashtable.h"
+#include "nsWeakReference.h"
+#include "nsStandardURL.h"
+
+struct SubstitutionMapping;
+class nsResProtocolHandler final : public nsIResProtocolHandler,
+ public mozilla::SubstitutingProtocolHandler,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRESPROTOCOLHANDLER
+
+ NS_FORWARD_NSIPROTOCOLHANDLER(mozilla::SubstitutingProtocolHandler::)
+
+ nsResProtocolHandler()
+ : SubstitutingProtocolHandler("resource", URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE,
+ /* aEnforceFileOrJar = */ false)
+ {}
+
+ nsresult Init();
+
+ NS_IMETHOD SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI) override;
+
+ NS_IMETHOD GetSubstitution(const nsACString& aRoot, nsIURI** aResult) override
+ {
+ return mozilla::SubstitutingProtocolHandler::GetSubstitution(aRoot, aResult);
+ }
+
+ NS_IMETHOD HasSubstitution(const nsACString& aRoot, bool* aResult) override
+ {
+ return mozilla::SubstitutingProtocolHandler::HasSubstitution(aRoot, aResult);
+ }
+
+ NS_IMETHOD ResolveURI(nsIURI *aResURI, nsACString& aResult) override
+ {
+ return mozilla::SubstitutingProtocolHandler::ResolveURI(aResURI, aResult);
+ }
+
+protected:
+ nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override;
+ virtual ~nsResProtocolHandler() {}
+
+ bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+private:
+ nsCString mAppURI;
+ nsCString mGREURI;
+};
+
+#endif /* nsResProtocolHandler_h___ */
diff --git a/netwerk/protocol/viewsource/moz.build b/netwerk/protocol/viewsource/moz.build
new file mode 100644
index 0000000000..4e3303c407
--- /dev/null
+++ b/netwerk/protocol/viewsource/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 += [
+ 'nsIViewSourceChannel.idl',
+]
+
+XPIDL_MODULE = 'necko_viewsource'
+
+UNIFIED_SOURCES += [
+ 'nsViewSourceChannel.cpp',
+ 'nsViewSourceHandler.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
diff --git a/netwerk/protocol/viewsource/nsIViewSourceChannel.idl b/netwerk/protocol/viewsource/nsIViewSourceChannel.idl
new file mode 100644
index 0000000000..9ed01ef310
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsIViewSourceChannel.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 "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.
+ */
+ attribute ACString originalContentType;
+
+ /**
+ * Whether the channel was created to view the source of a srcdoc document.
+ */
+ 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.
+ */
+ attribute nsIURI baseURI;
+};
+
+
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
new file mode 100644
index 0000000000..9ed71c4ef0
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -0,0 +1,1018 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewSourceChannel.h"
+#include "nsIIOService.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsContentSecurityManager.h"
+#include "nsNullPrincipal.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInputStreamChannel.h"
+#include "mozilla/DebugOnly.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_CONDITIONAL(nsIHttpChannel, 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_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)
+{
+ mOriginalURI = uri;
+
+ nsAutoCString path;
+ nsresult rv = uri->GetPath(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.LowerCaseEqualsLiteral("javascript")) {
+ NS_WARNING("blocking view-source:javascript:");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // This function is called from within nsViewSourceHandler::NewChannel2
+ // and sets the right loadInfo right after returning from this function.
+ // Until then we follow the principal of least privilege and use
+ // nullPrincipal as the loadingPrincipal and the least permissive
+ // securityflag.
+ nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
+
+ rv = pService->NewChannel2(path,
+ nullptr, // aOriginCharset
+ nullptr, // aCharSet
+ nullptr, // aLoadingNode
+ nullPrincipal,
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_OTHER,
+ getter_AddRefs(mChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsSrcdocChannel = false;
+
+ mChannel->SetOriginalURI(mOriginalURI);
+ 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);
+
+ 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),
+ NS_LITERAL_STRING("about:srcdoc"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel),
+ inStreamURI,
+ aSrcdoc,
+ NS_LITERAL_CSTRING("text/html"),
+ aLoadInfo,
+ true);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOriginalURI = aURI;
+ mIsSrcdocChannel = true;
+
+ mChannel->SetOriginalURI(mOriginalURI);
+ mHttpChannel = do_QueryInterface(mChannel);
+ mHttpChannelInternal = do_QueryInterface(mChannel);
+ mCachingChannel = do_QueryInterface(mChannel);
+ mCacheInfoChannel = do_QueryInterface(mChannel);
+ mApplicationCacheChannel = do_QueryInterface(mChannel);
+ mUploadChannel = do_QueryInterface(mChannel);
+
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(mChannel);
+ MOZ_ASSERT(isc);
+ isc->SetBaseURI(aBaseURI);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetName(nsACString &result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTransferSize(uint64_t *aTransferSize)
+{
+ 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::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)
+{
+ NS_ASSERTION(aURI, "Null out param!");
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ 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;
+
+ // protect ourselves against broken channel implementations
+ if (!uri) {
+ NS_ERROR("inner channel returned NS_OK and a null URI");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ /* XXX Gross hack -- NS_NewURI goes into an infinite loop on
+ non-flat specs. See bug 136980 */
+ return NS_NewURI(aURI, nsAutoCString(NS_LITERAL_CSTRING("view-source:")+spec), nullptr);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Open(nsIInputStream **_retval)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ nsresult rv = NS_MaybeOpenChannelUsingOpen2(mChannel, _retval);
+ if (NS_SUCCEEDED(rv)) {
+ mOpened = true;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Open2(nsIInputStream** aStream)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ if(!loadInfo) {
+ MOZ_ASSERT(loadInfo, "can not enforce security without loadInfo");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // setting the flag on the loadInfo indicates that the underlying
+ // channel will be openend using Open2() and hence performs
+ // the necessary security checks.
+ loadInfo->SetEnforceSecurity(true);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt)
+{
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ MOZ_ASSERT(!loadInfo || loadInfo->GetSecurityMode() == 0 ||
+ loadInfo->GetEnforceSecurity(),
+ "security flags in loadInfo but asyncOpen2() not called");
+ }
+#endif
+
+ 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;
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ rv = mChannel->AsyncOpen2(this);
+ }
+ else {
+ rv = mChannel->AsyncOpen(this, ctxt);
+ }
+
+ if (NS_FAILED(rv) && loadGroup)
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>
+ (this),
+ nullptr, rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ mOpened = true;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ if(!loadInfo) {
+ MOZ_ASSERT(loadInfo, "can not enforce security without loadInfo");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // setting the flag on the loadInfo indicates that the underlying
+ // channel will be openend using AsyncOpen2() and hence performs
+ // the necessary security checks.
+ loadInfo->SetEnforceSecurity(true);
+ return AsyncOpen(aListener, nullptr);
+}
+/*
+ * 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::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.Equals(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);
+
+ return mChannel->SetLoadInfo(aLoadInfo);
+}
+
+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;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetProtocolVersion(nsACString& aProtocolVersion)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIRequestObserver methods
+NS_IMETHODIMP
+nsViewSourceChannel::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ // The channel may have gotten redirected... Time to update our info
+ mChannel = do_QueryInterface(aRequest);
+ mHttpChannel = do_QueryInterface(aRequest);
+ mCachingChannel = do_QueryInterface(aRequest);
+ mCacheInfoChannel = do_QueryInterface(mChannel);
+ mUploadChannel = do_QueryInterface(aRequest);
+
+ return mListener->OnStartRequest(static_cast<nsIViewSourceChannel*>
+ (this),
+ aContext);
+}
+
+
+NS_IMETHODIMP
+nsViewSourceChannel::OnStopRequest(nsIRequest *aRequest, nsISupports* aContext,
+ 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);
+ }
+ }
+ return mListener->OnStopRequest(static_cast<nsIViewSourceChannel*>
+ (this),
+ aContext, aStatus);
+}
+
+
+// nsIStreamListener methods
+NS_IMETHODIMP
+nsViewSourceChannel::OnDataAvailable(nsIRequest *aRequest, nsISupports* aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ return mListener->OnDataAvailable(static_cast<nsIViewSourceChannel*>
+ (this),
+ aContext, 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(nsACString& aChannelId)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetChannelId(aChannelId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetChannelId(const nsACString& 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::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::GetReferrer(nsIURI * *aReferrer)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetReferrer(aReferrer);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetReferrer(nsIURI * aReferrer)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetReferrer(aReferrer);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetReferrerPolicy(uint32_t *aReferrerPolicy)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetReferrerPolicy(aReferrerPolicy);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetReferrerWithPolicy(nsIURI * aReferrer,
+ uint32_t aReferrerPolicy)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetReferrerWithPolicy(aReferrer, aReferrerPolicy);
+}
+
+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::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::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(NS_LITERAL_CSTRING("Content-Type"),
+ nsCaseInsensitiveCStringComparator()) &&
+ !aHeader.Equals(NS_LITERAL_CSTRING("Content-Security-Policy"),
+ nsCaseInsensitiveCStringComparator()) &&
+ !aHeader.Equals(NS_LITERAL_CSTRING("Content-Security-Policy-Report-Only"),
+ nsCaseInsensitiveCStringComparator()) &&
+ !aHeader.Equals(NS_LITERAL_CSTRING("X-Frame-Options"),
+ nsCaseInsensitiveCStringComparator())) {
+ return NS_OK;
+ }
+
+ 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;
+
+ NS_NAMED_LITERAL_CSTRING(contentTypeStr, "Content-Type");
+ nsAutoCString contentType;
+ nsresult rv =
+ mHttpChannel->GetResponseHeader(contentTypeStr, contentType);
+ if (NS_SUCCEEDED(rv))
+ 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;
+ }
+ aVisitor->VisitHeader(aHeader, value);
+ return NS_OK;
+}
+
+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::GetRequestContextID(nsID *_retval)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetRequestContextID(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestContextID(const nsID 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 since these are [notxpcom]
+void
+nsViewSourceChannel::SetCorsPreflightParameters(const nsTArray<nsCString>& aUnsafeHeaders)
+{
+ mHttpChannelInternal->SetCorsPreflightParameters(aUnsafeHeaders);
+}
+
+mozilla::net::nsHttpChannel *
+nsViewSourceChannel::QueryHttpChannelImpl()
+{
+ return mHttpChannelInternal->QueryHttpChannelImpl();
+}
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.h b/netwerk/protocol/viewsource/nsViewSourceChannel.h
new file mode 100644
index 0000000000..45e561aa75
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.h
@@ -0,0 +1,78 @@
+/* -*- 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 nsViewSourceChannel_h___
+#define nsViewSourceChannel_h___
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIViewSourceChannel.h"
+#include "nsIURI.h"
+#include "nsIStreamListener.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsICachingChannel.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "mozilla/Attributes.h"
+
+class nsViewSourceChannel final : public nsIViewSourceChannel,
+ public nsIStreamListener,
+ public nsIHttpChannel,
+ public nsIHttpChannelInternal,
+ public nsICachingChannel,
+ public nsIApplicationCacheChannel,
+ public nsIFormPOSTActionChannel
+{
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIVIEWSOURCECHANNEL
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIHTTPCHANNEL
+ 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) {}
+
+ nsresult Init(nsIURI* uri);
+
+ nsresult InitSrcdoc(nsIURI* aURI,
+ nsIURI* aBaseURI,
+ const nsAString &aSrcdoc,
+ nsILoadInfo* aLoadInfo);
+
+protected:
+ ~nsViewSourceChannel() {}
+
+ 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<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..e8d4711d8c
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewSourceHandler.h"
+#include "nsViewSourceChannel.h"
+#include "nsNetUtil.h"
+#include "nsSimpleNestedURI.h"
+
+#define VIEW_SOURCE "view-source"
+
+namespace mozilla {
+namespace net {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsViewSourceHandler, nsIProtocolHandler)
+
+////////////////////////////////////////////////////////////////////////////////
+// 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 = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+ URI_NON_PERSISTABLE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::NewURI(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);
+
+ // We can't swap() from an RefPtr<nsSimpleNestedURI> to an nsIURI**,
+ // sadly.
+ nsSimpleNestedURI* ourURI = new nsSimpleNestedURI(innerURI);
+ nsCOMPtr<nsIURI> uri = ourURI;
+ if (!uri)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = ourURI->SetSpec(asciiSpec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Make the URI immutable so it's impossible to get it out of sync
+ // with its inner URI.
+ ourURI->SetMutable(false);
+
+ uri.swap(*aResult);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ nsViewSourceChannel *channel = new nsViewSourceChannel();
+ if (!channel)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(channel);
+
+ nsresult rv = channel->Init(uri);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ *result = static_cast<nsIViewSourceChannel*>(channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+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..76f7c8d011
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.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 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 nsIProtocolHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ nsViewSourceHandler();
+
+ // Creates a new nsViewSourceChannel to view the source of an about:srcdoc
+ // URI with contents specified by srcdoc.
+ nsresult NewSrcdocChannel(nsIURI *aURI,
+ nsIURI *aBaseURI,
+ const nsAString &aSrcdoc,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel** outChannel);
+
+ static nsViewSourceHandler* GetInstance();
+
+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..bf3dbf9f7d
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
@@ -0,0 +1,381 @@
+/* -*- 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 "nsAutoPtr.h"
+#include "nsProxyRelease.h"
+#include "nsStandardURL.h"
+#include "LoadInfo.h"
+#include "nsIDOMNode.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(0)
+ , mPingForced(0)
+ , mIsServerSide(false)
+ , mPingInterval(0)
+ , mPingResponseTimeout(10000)
+{
+ // 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;
+ NS_ADDREF(*aOriginalURI = mOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetURI(nsIURI **aURI)
+{
+ LOG(("BaseWebSocketChannel::GetURI() %p\n", this));
+
+ if (!mOriginalURI)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (mURI)
+ NS_ADDREF(*aURI = mURI);
+ else
+ NS_ADDREF(*aURI = mOriginalURI);
+ 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)
+{
+ 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::InitLoadInfo(nsIDOMNode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aLoadingNode);
+ mLoadInfo = new LoadInfo(aLoadingPrincipal, aTriggeringPrincipal,
+ node, aSecurityFlags, aContentPolicyType);
+ return NS_OK;
+}
+
+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;
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::NewURI(const nsACString & aSpec, const char *aOriginCharset,
+ nsIURI *aBaseURI, nsIURI **_retval)
+{
+ LOG(("BaseWebSocketChannel::NewURI() %p\n", this));
+
+ int32_t port;
+ nsresult rv = GetDefaultPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<nsStandardURL> url = new nsStandardURL();
+ rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, port, aSpec,
+ aOriginCharset, aBaseURI);
+ if (NS_FAILED(rv))
+ return rv;
+ url.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel)
+{
+ LOG(("BaseWebSocketChannel::NewChannel2() %p\n", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ 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 = do_QueryInterface(aTargetThread);
+ MOZ_ASSERT(mTargetThread);
+ 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(mListener.forget());
+ NS_ReleaseOnMainThread(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..2cb622f7ce
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h
@@ -0,0 +1,114 @@
+/* -*- 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 InitLoadInfo(nsIDOMNode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, uint32_t aSecurityFlags,
+ uint32_t 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;
+
+ // 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;
+};
+
+} // 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..356cf2d57c
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.cpp
@@ -0,0 +1,104 @@
+/* -*- 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)
+
+TransportProviderParent::TransportProviderParent()
+{
+ MOZ_COUNT_CTOR(TransportProviderParent);
+}
+
+TransportProviderParent::~TransportProviderParent()
+{
+ MOZ_COUNT_DTOR(TransportProviderParent);
+}
+
+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;
+}
+
+void
+TransportProviderParent::MaybeNotify()
+{
+ if (!mListener || !mTransport) {
+ return;
+ }
+
+ mListener->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
+}
+
+
+NS_IMPL_ISUPPORTS(TransportProviderChild,
+ nsITransportProvider)
+
+TransportProviderChild::TransportProviderChild()
+{
+ MOZ_COUNT_CTOR(TransportProviderChild);
+}
+
+TransportProviderChild::~TransportProviderChild()
+{
+ MOZ_COUNT_DTOR(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;
+}
+
+} // net
+} // mozilla
diff --git a/netwerk/protocol/websocket/IPCTransportProvider.h b/netwerk/protocol/websocket/IPCTransportProvider.h
new file mode 100644
index 0000000000..343d3c0380
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.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 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. For example in the
+ * PFlyWebPublishedServer.WebSocketRequest message.
+ *
+ * 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();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSPORTPROVIDER
+ NS_DECL_NSIHTTPUPGRADELISTENER
+
+ void ActorDestroy(ActorDestroyReason aWhy) override {};
+
+private:
+ ~TransportProviderParent();
+
+ void MaybeNotify();
+
+ nsCOMPtr<nsIHttpUpgradeListener> mListener;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+
+class TransportProviderChild final : public PTransportProviderChild
+ , public nsITransportProvider
+{
+public:
+ TransportProviderChild();
+
+ 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..236798429c
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocket.ipdl
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 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 InputStreamParams;
+include URIParams;
+include NeckoChannelParams;
+
+include protocol PBlob; //FIXME: bug #792908
+
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace net {
+
+union OptionalTransportProvider
+{
+ PTransportProvider;
+ void_t;
+};
+
+async protocol PWebSocket
+{
+ manager PNecko;
+
+parent:
+ // Forwarded methods corresponding to methods on nsIWebSocketChannel
+ async AsyncOpen(OptionalURIParams 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,
+ OptionalLoadInfoArgs aLoadInfoArgs,
+ OptionalTransportProvider aProvider,
+ nsCString aNegotiatedExtensions);
+ async Close(uint16_t code, nsCString reason);
+ async SendMsg(nsCString aMsg);
+ async SendBinaryMsg(nsCString aMsg);
+ async SendBinaryStream(InputStreamParams aStream, uint32_t aLength);
+
+ async DeleteSelf();
+
+child:
+ // Forwarded notifications corresponding to the nsIWebSocketListener interface
+ async OnStart(nsCString aProtocol, nsCString aExtensions,
+ nsString aEffectiveURL, bool aEncrypted);
+ async OnStop(nsresult aStatusCode);
+ async OnMessageAvailable(nsCString aMsg);
+ async OnBinaryMessageAvailable(nsCString aMsg);
+ 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..35a1074578
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+using mozilla::net::WebSocketFrameData from "ipc/IPCMessageUtils.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);
+
+ 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..a6254a088d
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -0,0 +1,4107 @@
+/* -*- 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/net/WebSocketEventService.h"
+
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsICryptoHash.h"
+#include "nsIRunnable.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsICancelable.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIStreamConverterService.h"
+#include "nsIIOService2.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIProxyInfo.h"
+#include "nsIProxiedChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIDashboardEventNotifier.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpChannel.h"
+#include "nsILoadGroup.h"
+#include "nsIProtocolHandler.h"
+#include "nsIRandomGenerator.h"
+#include "nsISocketTransport.h"
+#include "nsThreadUtils.h"
+#include "nsINetworkLinkService.h"
+#include "nsIObserverService.h"
+#include "nsITransportProvider.h"
+#include "nsCharSeparatedTokenizer.h"
+
+#include "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include "nsError.h"
+#include "nsStringStream.h"
+#include "nsAlgorithm.h"
+#include "nsProxyRelease.h"
+#include "nsNetUtil.h"
+#include "nsINode.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "nsSocketTransportService2.h"
+
+#include "plbase64.h"
+#include "prmem.h"
+#include "prnetdb.h"
+#include "zlib.h"
+#include <algorithm>
+
+#ifdef MOZ_WIDGET_GONK
+#include "NetStatistics.h"
+#endif
+
+// 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)
+
+// 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(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 %lu",
+ 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);
+ for (uint32_t i = 0; i < mEntries.Length(); i++) {
+ delete mEntries[i];
+ }
+ }
+
+ void Add(nsCString &address, int32_t port)
+ {
+ if (mDelaysDisabled)
+ return;
+
+ FailDelay *record = new FailDelay(address, port);
+ mEntries.AppendElement(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];
+ 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);
+ delete fail;
+ }
+ }
+ 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;
+ ws->mReconnectDelayTimer =
+ do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = ws->mReconnectDelayTimer->InitWithCallback(
+ 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);
+ delete fail;
+ }
+ }
+ }
+
+ // 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];
+ if ((entry->mAddress.Equals(address) && entry->mPort == port) ||
+ entry->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(i);
+ delete entry;
+ }
+ }
+ }
+
+private:
+ nsTArray<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
+ nsOpenConn *newdata = new nsOpenConn(ws->mAddress, ws);
+ LOG(("Websocket: adding conn %p to the queue", newdata));
+ sManager->mQueue.AppendElement(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%08x]", aChannel,
+ 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);
+ for (uint32_t i = 0; i < mQueue.Length(); i++)
+ delete mQueue[i];
+ }
+
+ class nsOpenConn
+ {
+ public:
+ nsOpenConn(nsCString &addr, WebSocketChannel *channel)
+ : mAddress(addr), mChannel(channel) { MOZ_COUNT_CTOR(nsOpenConn); }
+ ~nsOpenConn() { MOZ_COUNT_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) {
+ nsOpenConn *olddata = mQueue[index];
+ mQueue.RemoveElementAt(index);
+ LOG(("Websocket: removing conn %p from the queue", olddata));
+ delete olddata;
+ }
+ }
+
+ 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<nsOpenConn *> mQueue;
+
+ FailDelayManager mFailures;
+
+ static nsWSAdmissionManager *sManager;
+ static StaticMutex sLock;
+};
+
+nsWSAdmissionManager *nsWSAdmissionManager::sManager;
+StaticMutex nsWSAdmissionManager::sLock;
+
+//-----------------------------------------------------------------------------
+// CallOnMessageAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnMessageAvailable final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CallOnMessageAvailable(WebSocketChannel* aChannel,
+ nsACString& aData,
+ int32_t aLen)
+ : mChannel(aChannel),
+ mListenerMT(aChannel->mListenerMT),
+ mData(aData),
+ mLen(aLen) {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ if (mLen < 0) {
+ mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext,
+ mData);
+ } else {
+ mListenerMT->mListener->OnBinaryMessageAvailable(mListenerMT->mContext,
+ mData);
+ }
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~CallOnMessageAvailable() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ nsCString mData;
+ int32_t mLen;
+};
+NS_IMPL_ISUPPORTS(CallOnMessageAvailable, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// CallOnStop
+//-----------------------------------------------------------------------------
+
+class CallOnStop final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CallOnStop(WebSocketChannel* aChannel,
+ nsresult aReason)
+ : mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mReason(aReason)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ mListenerMT->mListener->OnStop(mListenerMT->mContext, mReason);
+ mChannel->mListenerMT = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~CallOnStop() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ nsresult mReason;
+};
+NS_IMPL_ISUPPORTS(CallOnStop, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// CallOnServerClose
+//-----------------------------------------------------------------------------
+
+class CallOnServerClose final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CallOnServerClose(WebSocketChannel* aChannel,
+ uint16_t aCode,
+ nsACString& aReason)
+ : mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mCode(aCode),
+ mReason(aReason) {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ mListenerMT->mListener->OnServerClose(mListenerMT->mContext, mCode,
+ mReason);
+ }
+ return NS_OK;
+ }
+
+private:
+ ~CallOnServerClose() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ uint16_t mCode;
+ nsCString mReason;
+};
+NS_IMPL_ISUPPORTS(CallOnServerClose, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// CallAcknowledge
+//-----------------------------------------------------------------------------
+
+class CallAcknowledge final : public CancelableRunnable
+{
+public:
+ CallAcknowledge(WebSocketChannel* aChannel,
+ uint32_t aSize)
+ : mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mSize(aSize) {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
+ if (mListenerMT) {
+ mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, mSize);
+ }
+ return NS_OK;
+ }
+
+private:
+ ~CallAcknowledge() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ uint32_t mSize;
+};
+
+//-----------------------------------------------------------------------------
+// CallOnTransportAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnTransportAvailable final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CallOnTransportAvailable(WebSocketChannel *aChannel,
+ nsISocketTransport *aTransport,
+ nsIAsyncInputStream *aSocketIn,
+ nsIAsyncOutputStream *aSocketOut)
+ : 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() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+NS_IMPL_ISUPPORTS(CallOnTransportAvailable, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// PMCECompression
+//-----------------------------------------------------------------------------
+
+class PMCECompression
+{
+public:
+ PMCECompression(bool aNoContextTakeover,
+ int32_t aLocalMaxWindowBits,
+ int32_t aRemoteMaxWindowBits)
+ : mActive(false)
+ , mNoContextTakeover(aNoContextTakeover)
+ , mResetDeflater(false)
+ , mMessageDeflated(false)
+ {
+ 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) {
+ _retval.Append(reinterpret_cast<char *>(mBuffer), inflated);
+ }
+
+ 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, nsCString *str)
+ : mMsgType(type), mDeflated(false), mOrigLength(0)
+ {
+ MOZ_COUNT_CTOR(OutboundMessage);
+ mMsg.pString.mValue = str;
+ mMsg.pString.mOrigValue = nullptr;
+ mLength = str ? str->Length() : 0;
+ }
+
+ OutboundMessage(nsIInputStream *stream, uint32_t length)
+ : mMsgType(kMsgTypeStream), mLength(length), mDeflated(false)
+ , mOrigLength(0)
+ {
+ MOZ_COUNT_CTOR(OutboundMessage);
+ mMsg.pStream = stream;
+ mMsg.pStream->AddRef();
+ }
+
+ ~OutboundMessage() {
+ MOZ_COUNT_DTOR(OutboundMessage);
+ switch (mMsgType) {
+ case kMsgTypeString:
+ case kMsgTypeBinaryString:
+ case kMsgTypePing:
+ case kMsgTypePong:
+ delete mMsg.pString.mValue;
+ if (mMsg.pString.mOrigValue)
+ delete mMsg.pString.mOrigValue;
+ break;
+ case kMsgTypeStream:
+ // for now this only gets hit if msg deleted w/o being sent
+ if (mMsg.pStream) {
+ mMsg.pStream->Close();
+ mMsg.pStream->Release();
+ }
+ break;
+ case kMsgTypeFin:
+ break; // do-nothing: avoid compiler warning
+ }
+ }
+
+ WsMsgType GetMsgType() const { return mMsgType; }
+ int32_t Length() const { return mLength; }
+ int32_t OrigLength() const { return mDeflated ? mOrigLength : mLength; }
+
+ uint8_t* BeginWriting() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginWriting() : nullptr);
+ }
+
+ uint8_t* BeginReading() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginReading() : nullptr);
+ }
+
+ uint8_t* BeginOrigReading() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ if (!mDeflated)
+ return BeginReading();
+ return (uint8_t *)(mMsg.pString.mOrigValue ? mMsg.pString.mOrigValue->BeginReading() : nullptr);
+ }
+
+ nsresult ConvertStreamToString()
+ {
+ MOZ_ASSERT(mMsgType == kMsgTypeStream, "Not a stream!");
+
+#ifdef DEBUG
+ // Make sure we got correct length from Blob
+ uint64_t bytes;
+ mMsg.pStream->Available(&bytes);
+ NS_ASSERTION(bytes == mLength, "Stream length != blob length!");
+#endif
+
+ nsAutoPtr<nsCString> temp(new nsCString());
+ nsresult rv = NS_ReadInputStreamToString(mMsg.pStream, *temp, mLength);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mMsg.pStream->Close();
+ mMsg.pStream->Release();
+ mMsg.pString.mValue = temp.forget();
+ mMsg.pString.mOrigValue = nullptr;
+ 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;
+
+ if (mLength == 0) {
+ // Empty message
+ return false;
+ }
+
+ nsAutoPtr<nsCString> temp(new nsCString());
+ rv = aCompressor->Deflate(BeginReading(), mLength, *temp);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::OutboundMessage: Deflating payload failed "
+ "[rv=0x%08x]\n", rv));
+ return false;
+ }
+
+ if (!aCompressor->UsingContextTakeover() && temp->Length() > mLength) {
+ // 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(), mLength));
+ return false;
+ }
+
+ mOrigLength = mLength;
+ mDeflated = true;
+ mLength = temp->Length();
+ mMsg.pString.mOrigValue = mMsg.pString.mValue;
+ mMsg.pString.mValue = temp.forget();
+ return true;
+ }
+
+private:
+ union {
+ struct {
+ nsCString *mValue;
+ nsCString *mOrigValue;
+ } pString;
+ nsIInputStream *pStream;
+ } mMsg;
+ WsMsgType mMsgType;
+ uint32_t mLength;
+ bool mDeflated;
+ uint32_t mOrigLength;
+};
+
+//-----------------------------------------------------------------------------
+// OutboundEnqueuer
+//-----------------------------------------------------------------------------
+
+class OutboundEnqueuer final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ OutboundEnqueuer(WebSocketChannel *aChannel, OutboundMessage *aMsg)
+ : mChannel(aChannel), mMessage(aMsg) {}
+
+ NS_IMETHOD Run() override
+ {
+ mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage);
+ return NS_OK;
+ }
+
+private:
+ ~OutboundEnqueuer() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ OutboundMessage *mMessage;
+};
+NS_IMPL_ISUPPORTS(OutboundEnqueuer, nsIRunnable)
+
+
+//-----------------------------------------------------------------------------
+// WebSocketChannel
+//-----------------------------------------------------------------------------
+
+WebSocketChannel::WebSocketChannel() :
+ mPort(0),
+ mCloseTimeout(20000),
+ mOpenTimeout(20000),
+ mConnecting(NOT_CONNECTING),
+ mMaxConcurrentConnections(200),
+ mGotUpgradeOK(0),
+ mRecvdHttpUpgradeTransport(0),
+ mAutoFollowRedirects(0),
+ mAllowPMCE(1),
+ mPingOutstanding(0),
+ mReleaseOnTransmit(0),
+ mDataStarted(0),
+ mRequestedClose(0),
+ mClientClosed(0),
+ mServerClosed(0),
+ mStopped(0),
+ mCalledOnStop(0),
+ mTCPClosed(0),
+ mOpenedHttpChannel(0),
+ mIncrementedSessionCount(0),
+ mDecrementedSessionCount(0),
+ 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),
+ mDynamicOutputSize(0),
+ mDynamicOutput(nullptr),
+ mPrivateBrowsing(false),
+ mConnectionLogService(nullptr),
+ mCountRecv(0),
+ mCountSent(0),
+ mAppId(NECKO_NO_APP_ID),
+ mIsInIsolatedMozBrowser(false)
+{
+ 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 = (OutboundMessage *) mOutgoingPingMessages.PopFront()))
+ delete mCurrentOut;
+ while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront()))
+ delete mCurrentOut;
+ while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront()))
+ delete mCurrentOut;
+
+ NS_ReleaseOnMainThread(mURI.forget());
+ NS_ReleaseOnMainThread(mOriginalURI.forget());
+
+ mListenerMT = nullptr;
+
+ NS_ReleaseOnMainThread(mLoadGroup.forget());
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+ NS_ReleaseOnMainThread(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(this, &WebSocketChannel::OnNetworkChanged),
+ NS_DISPATCH_NORMAL);
+ } else {
+ OnNetworkChanged();
+ }
+ }
+ }
+ }
+
+ 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(this, &WebSocketChannel::OnNetworkChanged),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not 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.
+ nsresult rv;
+ mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocket: unable to create ping timer!"));
+ NS_WARNING("unable to create ping timer!");
+ return rv;
+ }
+ }
+ // Trigger the ping timeout asap to fire off a new ping. Wait just
+ // a little bit to better avoid multi-triggers.
+ mPingForced = 1;
+ 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(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;
+ }
+
+ if (localChannel) {
+ NS_GetAppInfo(localChannel, &mAppId, &mIsInIsolatedMozBrowser);
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ if (mAppId != NECKO_NO_APP_ID) {
+ nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+ GetActiveNetworkInfo(activeNetworkInfo);
+ mActiveNetworkInfo =
+ new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo);
+ }
+#endif
+
+ rv = NS_MaybeOpenChannelUsingAsyncOpen2(localChannel, this);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
+ AbortSession(NS_ERROR_CONNECTION_REFUSED);
+ return;
+ }
+ mOpenedHttpChannel = 1;
+
+ mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot create open timer\n"));
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ rv = mOpenTimer->InitWithCallback(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(PR_GetCurrentThread() == gSocketThread, "not 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];
+ } 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);
+ }
+
+ payload = mFramePtr + framingLength;
+ avail -= framingLength;
+
+ LOG(("WebSocketChannel::ProcessInput: payload %lld avail %lu\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 %ld\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, false)) {
+ 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 = 1;
+
+ 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, false)) {
+ 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()
+{
+ nsCString *buf = new nsCString();
+ buf->AssignLiteral("PING");
+ EnqueueOutgoingMessage(mOutgoingPingMessages,
+ new OutboundMessage(kMsgTypePing, buf));
+}
+
+void
+WebSocketChannel::GeneratePong(uint8_t *payload, uint32_t len)
+{
+ nsCString *buf = new nsCString();
+ buf->SetLength(len);
+ if (buf->Length() < len) {
+ LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
+ delete buf;
+ return;
+ }
+
+ memcpy(buf->BeginWriting(), payload, len);
+ EnqueueOutgoingMessage(mOutgoingPongMessages,
+ new OutboundMessage(kMsgTypePong, buf));
+}
+
+void
+WebSocketChannel::EnqueueOutgoingMessage(nsDeque &aQueue,
+ OutboundMessage *aMsg)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not 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(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+ MOZ_ASSERT(!mCurrentOut, "Current message in progress");
+
+ nsresult rv = NS_OK;
+
+ mCurrentOut = (OutboundMessage *)mOutgoingPongMessages.PopFront();
+ if (mCurrentOut) {
+ MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePong,
+ "Not pong message!");
+ } else {
+ mCurrentOut = (OutboundMessage *)mOutgoingPingMessages.PopFront();
+ if (mCurrentOut)
+ MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePing,
+ "Not ping message!");
+ else
+ mCurrentOut = (OutboundMessage *)mOutgoingMessages.PopFront();
+ }
+
+ if (!mCurrentOut)
+ return;
+
+ 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();
+ return;
+ }
+
+ mClientClosed = 1;
+ 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 */
+ mCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mCloseTimer->InitWithCallback(this, mCloseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ 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
+ MOZ_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)) {
+ // 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 %x\n", rv));
+ StopSession(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.
+}
+
+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)
+ : 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) {
+ 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 [%x]\n", this, 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
+
+ mStopped = 1;
+
+ if (!mOpenedHttpChannel) {
+ // The HTTP channel information will never be used in this case
+ NS_ReleaseOnMainThread(mChannel.forget());
+ NS_ReleaseOnMainThread(mHttpChannel.forget());
+ NS_ReleaseOnMainThread(mLoadGroup.forget());
+ NS_ReleaseOnMainThread(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) {
+ // 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::StopSession: Wait for Server TCP close"));
+
+ nsresult rv;
+ mLingeringCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv))
+ mLingeringCloseTimer->InitWithCallback(this, kLingeringCloseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ else
+ CleanupConnection();
+ } else {
+ CleanupConnection();
+ }
+
+ if (mCancelable) {
+ mCancelable->Cancel(NS_ERROR_UNEXPECTED);
+ mCancelable = nullptr;
+ }
+
+ mPMCECompressor = nullptr;
+
+ if (!mCalledOnStop) {
+ mCalledOnStop = 1;
+
+ 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 %x] stopped = %d\n",
+ this, reason, !!mStopped));
+
+ // 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;
+ }
+
+ if (mStopped)
+ return;
+ mStopped = 1;
+
+ if (mTransport && reason != NS_BASE_STREAM_CLOSED && !mRequestedClose &&
+ !mClientClosed && !mServerClosed && mConnecting == NOT_CONNECTING) {
+ mRequestedClose = 1;
+ mStopOnClose = reason;
+ mSocketThread->Dispatch(
+ new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
+ nsIEventTarget::DISPATCH_NORMAL);
+ } else {
+ StopSession(reason);
+ }
+}
+
+// ReleaseSession is called on orderly shutdown
+void
+WebSocketChannel::ReleaseSession()
+{
+ LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n",
+ this, !!mStopped));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+
+ if (mStopped)
+ return;
+ StopSession(NS_OK);
+}
+
+void
+WebSocketChannel::IncrementSessionCount()
+{
+ if (!mIncrementedSessionCount) {
+ nsWSAdmissionManager::IncrementSessionCount();
+ mIncrementedSessionCount = 1;
+ }
+}
+
+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 = 1;
+ }
+}
+
+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().Equals(NS_LITERAL_CSTRING("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(
+ NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), 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 = new 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;
+ }
+ }
+
+ nsCCharSeparatedTokenizer extList(aExtensions, ',');
+ while (extList.hasMoreTokens()) {
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ nsresult rv = ParseWebSocketExtension(extList.nextToken(),
+ 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 + NS_LITERAL_CSTRING("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+ 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(NS_LITERAL_CSTRING("websocket"), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHttpChannel->SetRequestHeader(
+ NS_LITERAL_CSTRING("Sec-WebSocket-Version"),
+ NS_LITERAL_CSTRING(SEC_WEBSOCKET_VERSION), false);
+
+ if (!mOrigin.IsEmpty())
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), mOrigin,
+ false);
+
+ if (!mProtocol.IsEmpty())
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
+ mProtocol, true);
+
+ if (mAllowPMCE)
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
+ NS_LITERAL_CSTRING("permessage-deflate"),
+ false);
+
+ uint8_t *secKey;
+ nsAutoCString secKeyString;
+
+ rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ char* b64 = PL_Base64Encode((const char *)secKey, 16, nullptr);
+ free(secKey);
+ if (!b64)
+ return NS_ERROR_OUT_OF_MEMORY;
+ secKeyString.Assign(b64);
+ PR_Free(b64);
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Key"),
+ secKeyString, false);
+ 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()));
+
+ 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<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ MOZ_ASSERT(!mCancelable);
+ return dns->AsyncResolve(hostName, 0, this, mainThread, 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_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ this, 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::StartWebsocketData()
+{
+ nsresult rv;
+
+ if (!IsOnTargetThread()) {
+ return mTargetThread->Dispatch(
+ NewRunnableMethod(this, &WebSocketChannel::StartWebsocketData),
+ NS_DISPATCH_NORMAL);
+ }
+
+ LOG(("WebSocketChannel::StartWebsocketData() %p", this));
+ MOZ_ASSERT(!mDataStarted, "StartWebsocketData twice");
+ mDataStarted = 1;
+
+ rv = mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::StartWebsocketData mSocketIn->AsyncWait() failed "
+ "with error 0x%08x", rv));
+ return mSocketThread->Dispatch(
+ NewRunnableMethod<nsresult>(this,
+ &WebSocketChannel::AbortSession,
+ rv),
+ NS_DISPATCH_NORMAL);
+ }
+
+ if (mPingInterval) {
+ rv = mSocketThread->Dispatch(
+ NewRunnableMethod(this, &WebSocketChannel::StartPinging),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::StartWebsocketData Could not start pinging, "
+ "rv=0x%08x", rv));
+ return rv;
+ }
+ }
+
+ LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p",
+ mListenerMT ? mListenerMT->mListener.get() : nullptr));
+
+ if (mListenerMT) {
+ mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+WebSocketChannel::StartPinging()
+{
+ LOG(("WebSocketChannel::StartPinging() %p", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+ MOZ_ASSERT(mPingInterval);
+ MOZ_ASSERT(!mPingTimer);
+
+ nsresult rv;
+ mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to create ping timer. Carrying on.");
+ } else {
+ LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
+ mPingInterval));
+ mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+
+void
+WebSocketChannel::ReportConnectionTelemetry()
+{
+ // 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 ? (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 %x]\n",
+ this, aRequest, aRecord, 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 {
+ nsresult rv = aRecord->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_FAILURE;
+}
+
+// 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 = false;
+ rv = newuri->SchemeIs("https", &newuriIsHttps);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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;
+ newuri->Clone(getter_AddRefs(mURI));
+ if (mEncrypted)
+ rv = mURI->SetScheme(NS_LITERAL_CSTRING("wss"));
+ else
+ rv = mURI->SetScheme(NS_LITERAL_CSTRING("ws"));
+
+ 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 = 0;
+ 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(PR_GetCurrentThread() == gSocketThread,
+ "not 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(!mGotUpgradeOK,
+ "Open Timer after open complete");
+ 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(PR_GetCurrentThread() == gSocketThread,
+ "not 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 = 0;
+ 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;
+}
+
+// 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 = do_GetMainThread();
+ }
+
+ 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);
+ mServerTransportProvider->SetListener(this);
+ 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;
+
+ mURI->Clone(getter_AddRefs(localURI));
+ if (mEncrypted)
+ rv = localURI->SetScheme(NS_LITERAL_CSTRING("https"));
+ else
+ rv = localURI->SetScheme(NS_LITERAL_CSTRING("http"));
+ 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;
+ }
+
+ nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("WebSocketChannel: unable to continue without ioservice2");
+ return rv;
+ }
+
+ // Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't
+ // allow setting proxy uri/flags
+ rv = io2->NewChannelFromURIWithProxyFlags2(
+ localURI,
+ mURI,
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ mLoadInfo->LoadingNode() ?
+ mLoadInfo->LoadingNode()->AsDOMNode() : nullptr,
+ mLoadInfo->LoadingPrincipal(),
+ 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");
+
+ // save the networkstats (bug 855949)
+ SaveNetworkStats(true);
+
+ if (mRequestedClose) {
+ return NS_OK;
+ }
+
+ // The API requires the UTF-8 string to be 123 or less bytes
+ if (reason.Length() > 123)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ mRequestedClose = 1;
+ mScriptCloseReason = reason;
+ mScriptCloseCode = code;
+
+ if (!mTransport || mConnecting != NOT_CONNECTING) {
+ 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;
+ }
+ StopSession(rv);
+ return rv;
+ }
+
+ return mSocketThread->Dispatch(
+ new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+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(nullptr, 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,
+ new nsCString(*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 StartWebsocketData();
+ }
+
+ 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 = new 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 StartWebsocketData();
+ }
+
+ return NS_OK;
+}
+
+// nsIRequestObserver (from nsIStreamListener)
+
+NS_IMETHODIMP
+WebSocketChannel::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ 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 (mOpenTimer) {
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ 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)) {
+ LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
+ AbortSession(NS_ERROR_CONNECTION_REFUSED);
+ return NS_ERROR_CONNECTION_REFUSED;
+ }
+
+ LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status));
+ if (status != 101) {
+ AbortSession(NS_ERROR_CONNECTION_REFUSED);
+ return NS_ERROR_CONNECTION_REFUSED;
+ }
+
+ nsAutoCString respUpgrade;
+ rv = mHttpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("Upgrade"), 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(
+ NS_LITERAL_CSTRING("Connection"), 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(
+ NS_LITERAL_CSTRING("Sec-WebSocket-Accept"),
+ 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()));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // If we sent a sub protocol header, verify the response matches
+ // If it does not, set mProtocol to "" so the protocol attribute
+ // of the WebSocket JS object reflects that
+ if (!mProtocol.IsEmpty()) {
+ nsAutoCString respProtocol;
+ rv = mHttpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
+ respProtocol);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ val = mProtocol.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (PL_strcasecmp(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: "
+ "subprotocol [%s] not found - %s returned",
+ mProtocol.get(), respProtocol.get()));
+ mProtocol.Truncate();
+ }
+ } 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 StartWebsocketData();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %x]\n",
+ this, aRequest, mHttpChannel.get(), aStatusCode));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ ReportConnectionTelemetry();
+
+ // 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(PR_GetCurrentThread() == gSocketThread, "not 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 %x\n", count, rv));
+
+ // accumulate received bytes
+ CountRecvBytes(count);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ mTCPClosed = true;
+ AbortSession(rv);
+ return rv;
+ }
+
+ if (count == 0) {
+ mTCPClosed = true;
+ 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(PR_GetCurrentThread() == gSocketThread, "not 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 %x\n",
+ amtSent, rv));
+
+ // accumulate sent bytes
+ CountSentBytes(amtSent);
+
+ 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,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %llu %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;
+}
+
+nsresult
+WebSocketChannel::SaveNetworkStats(bool enforce)
+{
+#ifdef MOZ_WIDGET_GONK
+ // Check if the active network and app id are valid.
+ if(!mActiveNetworkInfo || mAppId == NECKO_NO_APP_ID) {
+ return NS_OK;
+ }
+
+ uint64_t countRecv = 0;
+ uint64_t countSent = 0;
+
+ mCountRecv.exchange(countRecv);
+ mCountSent.exchange(countSent);
+
+ if (countRecv == 0 && countSent == 0) {
+ // There is no traffic, no need to save.
+ return NS_OK;
+ }
+
+ // If |enforce| is false, the traffic amount is saved
+ // only when the total amount exceeds the predefined
+ // threshold.
+ uint64_t totalBytes = countRecv + countSent;
+ if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
+ return NS_OK;
+ }
+
+ // Create the event to save the network statistics.
+ // the event is then dispatched to the main thread.
+ RefPtr<Runnable> event =
+ new SaveNetworkStatsEvent(mAppId, mIsInIsolatedMozBrowser, mActiveNetworkInfo,
+ countRecv, countSent, false);
+ NS_DispatchToMainThread(event);
+
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+} // 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..e2f332dabb
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -0,0 +1,337 @@
+/* -*- 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 "nsIObserver.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIChannelEventSink.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIStringStream.h"
+#include "BaseWebSocketChannel.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsProxyRelease.h"
+#endif
+
+#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;
+
+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
+{
+ 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
+
+ // 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
+ nsresult SendMsgCommon(const nsACString *aMsg, bool isBinary,
+ uint32_t length, nsIInputStream *aStream = nullptr);
+
+ void EnqueueOutgoingMessage(nsDeque &aQueue, OutboundMessage *aMsg);
+
+ void PrimeNewOutgoingMessage();
+ void DeleteCurrentOutGoingMessage();
+ void GeneratePong(uint8_t *payload, uint32_t len);
+ void GeneratePing();
+
+ nsresult OnNetworkChanged();
+ nsresult StartPinging();
+
+ void BeginOpen(bool aCalledFromAdmissionManager);
+ void BeginOpenInternal();
+ nsresult HandleExtensions();
+ nsresult SetupRequest();
+ nsresult ApplyForAdmission();
+ nsresult DoAdmissionDNS();
+ nsresult StartWebsocketData();
+ uint16_t ResultToCloseCode(nsresult resultCode);
+ void ReportConnectionTelemetry();
+
+ void StopSession(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();
+ nsresult ProcessInput(uint8_t *buffer, uint32_t count);
+ 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 mOutgoingMessages;
+ nsDeque mOutgoingPingMessages;
+ nsDeque mOutgoingPongMessages;
+ uint32_t mHdrOutToSend;
+ uint8_t *mHdrOut;
+ uint8_t mOutHeader[kCopyBreak + 16];
+ nsAutoPtr<PMCECompression> mPMCECompressor;
+ uint32_t mDynamicOutputSize;
+ uint8_t *mDynamicOutput;
+ bool mPrivateBrowsing;
+
+ nsCOMPtr<nsIDashboardEventNotifier> mConnectionLogService;
+
+// These members are used for network per-app metering (bug 855949)
+// Currently, they are only available on gonk.
+ Atomic<uint64_t, Relaxed> mCountRecv;
+ Atomic<uint64_t, Relaxed> mCountSent;
+ uint32_t mAppId;
+ bool mIsInIsolatedMozBrowser;
+#ifdef MOZ_WIDGET_GONK
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+ nsresult SaveNetworkStats(bool);
+ void CountRecvBytes(uint64_t recvBytes)
+ {
+ mCountRecv += recvBytes;
+ SaveNetworkStats(false);
+ }
+ void CountSentBytes(uint64_t sentBytes)
+ {
+ mCountSent += sentBytes;
+ SaveNetworkStats(false);
+ }
+};
+
+class WebSocketSSLChannel : public WebSocketChannel
+{
+public:
+ WebSocketSSLChannel() { BaseWebSocketChannel::mEncrypted = true; }
+protected:
+ virtual ~WebSocketSSLChannel() {}
+};
+
+} // 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..2444af45e1
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -0,0 +1,723 @@
+/* -*- 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/TabChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "WebSocketChannelChild.h"
+#include "nsITabChild.h"
+#include "nsNetUtil.h"
+#include "mozilla/ipc/InputStreamUtils.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()
+{
+ NS_PRECONDITION(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)
+ : 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()) {
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(NewRunnableMethod(this,
+ &WebSocketChannelChild::MaybeReleaseIPCObject)));
+ return;
+ }
+
+ SendDeleteSelf();
+}
+
+void
+WebSocketChannelChild::GetEffectiveURL(nsAString& aEffectiveURL) const
+{
+ aEffectiveURL = mEffectiveURL;
+}
+
+bool
+WebSocketChannelChild::IsEncrypted() const
+{
+ return mEncrypted;
+}
+
+class WrappedChannelEvent : public Runnable
+{
+public:
+ explicit WrappedChannelEvent(ChannelEvent *aChannelEvent)
+ : mChannelEvent(aChannelEvent)
+ {
+ MOZ_RELEASE_ASSERT(aChannelEvent);
+ }
+ NS_IMETHOD Run() override
+ {
+ mChannelEvent->Run();
+ return NS_OK;
+ }
+private:
+ nsAutoPtr<ChannelEvent> mChannelEvent;
+};
+
+void
+WebSocketChannelChild::DispatchToTargetThread(ChannelEvent *aChannelEvent)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(mTargetThread);
+ MOZ_RELEASE_ASSERT(aChannelEvent);
+
+ mTargetThread->Dispatch(new WrappedChannelEvent(aChannelEvent),
+ NS_DISPATCH_NORMAL);
+}
+
+class EventTargetDispatcher : public ChannelEvent
+{
+public:
+ EventTargetDispatcher(ChannelEvent* aChannelEvent,
+ nsIEventTarget* aEventTarget)
+ : mChannelEvent(aChannelEvent)
+ , mEventTarget(aEventTarget)
+ {}
+
+ void Run()
+ {
+ if (mEventTarget) {
+ mEventTarget->Dispatch(new WrappedChannelEvent(mChannelEvent.forget()),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ mChannelEvent->Run();
+ }
+
+private:
+ nsAutoPtr<ChannelEvent> mChannelEvent;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+class StartEvent : public ChannelEvent
+{
+ public:
+ StartEvent(WebSocketChannelChild* aChild,
+ const nsCString& aProtocol,
+ const nsCString& aExtensions,
+ const nsString& aEffectiveURL,
+ bool aEncrypted)
+ : mChild(aChild)
+ , mProtocol(aProtocol)
+ , mExtensions(aExtensions)
+ , mEffectiveURL(aEffectiveURL)
+ , mEncrypted(aEncrypted)
+ {}
+
+ void Run()
+ {
+ mChild->OnStart(mProtocol, mExtensions, mEffectiveURL, mEncrypted);
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCString mProtocol;
+ nsCString mExtensions;
+ nsString mEffectiveURL;
+ bool mEncrypted;
+};
+
+bool
+WebSocketChannelChild::RecvOnStart(const nsCString& aProtocol,
+ const nsCString& aExtensions,
+ const nsString& aEffectiveURL,
+ const bool& aEncrypted)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new StartEvent(this, aProtocol, aExtensions,
+ aEffectiveURL, aEncrypted),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnStart(const nsCString& aProtocol,
+ const nsCString& aExtensions,
+ const nsString& aEffectiveURL,
+ const bool& aEncrypted)
+{
+ LOG(("WebSocketChannelChild::RecvOnStart() %p\n", this));
+ SetProtocol(aProtocol);
+ mNegotiatedExtensions = aExtensions;
+ mEffectiveURL = aEffectiveURL;
+ mEncrypted = aEncrypted;
+
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ }
+}
+
+class StopEvent : public ChannelEvent
+{
+ public:
+ StopEvent(WebSocketChannelChild* aChild,
+ const nsresult& aStatusCode)
+ : mChild(aChild)
+ , mStatusCode(aStatusCode)
+ {}
+
+ void Run()
+ {
+ mChild->OnStop(mStatusCode);
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsresult mStatusCode;
+};
+
+bool
+WebSocketChannelChild::RecvOnStop(const nsresult& aStatusCode)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new StopEvent(this, aStatusCode),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnStop(const nsresult& aStatusCode)
+{
+ LOG(("WebSocketChannelChild::RecvOnStop() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnStop(mListenerMT->mContext, aStatusCode);
+ }
+}
+
+class MessageEvent : public ChannelEvent
+{
+ public:
+ MessageEvent(WebSocketChannelChild* aChild,
+ const nsCString& aMessage,
+ bool aBinary)
+ : mChild(aChild)
+ , mMessage(aMessage)
+ , mBinary(aBinary)
+ {}
+
+ void Run()
+ {
+ if (!mBinary) {
+ mChild->OnMessageAvailable(mMessage);
+ } else {
+ mChild->OnBinaryMessageAvailable(mMessage);
+ }
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCString mMessage;
+ bool mBinary;
+};
+
+bool
+WebSocketChannelChild::RecvOnMessageAvailable(const nsCString& aMsg)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new MessageEvent(this, aMsg, false),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnMessageAvailable(const nsCString& aMsg)
+{
+ LOG(("WebSocketChannelChild::RecvOnMessageAvailable() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext, aMsg);
+ }
+}
+
+bool
+WebSocketChannelChild::RecvOnBinaryMessageAvailable(const nsCString& aMsg)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new MessageEvent(this, aMsg, true),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnBinaryMessageAvailable(const nsCString& aMsg)
+{
+ LOG(("WebSocketChannelChild::RecvOnBinaryMessageAvailable() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnBinaryMessageAvailable(mListenerMT->mContext,
+ aMsg);
+ }
+}
+
+class AcknowledgeEvent : public ChannelEvent
+{
+ public:
+ AcknowledgeEvent(WebSocketChannelChild* aChild,
+ const uint32_t& aSize)
+ : mChild(aChild)
+ , mSize(aSize)
+ {}
+
+ void Run()
+ {
+ mChild->OnAcknowledge(mSize);
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ uint32_t mSize;
+};
+
+bool
+WebSocketChannelChild::RecvOnAcknowledge(const uint32_t& aSize)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new AcknowledgeEvent(this, aSize),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnAcknowledge(const uint32_t& aSize)
+{
+ LOG(("WebSocketChannelChild::RecvOnAcknowledge() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, aSize);
+ }
+}
+
+class ServerCloseEvent : public ChannelEvent
+{
+ public:
+ ServerCloseEvent(WebSocketChannelChild* aChild,
+ const uint16_t aCode,
+ const nsCString &aReason)
+ : mChild(aChild)
+ , mCode(aCode)
+ , mReason(aReason)
+ {}
+
+ void Run()
+ {
+ mChild->OnServerClose(mCode, mReason);
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+bool
+WebSocketChannelChild::RecvOnServerClose(const uint16_t& aCode,
+ const nsCString& aReason)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new ServerCloseEvent(this, aCode, aReason),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnServerClose(const uint16_t& aCode,
+ const nsCString& aReason)
+{
+ LOG(("WebSocketChannelChild::RecvOnServerClose() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnServerClose(mListenerMT->mContext, aCode,
+ aReason);
+ }
+}
+
+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::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsITabChild),
+ getter_AddRefs(iTabChild));
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+ if (MissingRequiredTabChild(tabChild, "websocket")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Corresponding release in DeallocPWebSocket
+ AddIPDLReference();
+
+ OptionalURIParams uri;
+ OptionalLoadInfoArgs loadInfoArgs;
+ OptionalTransportProvider transportProvider;
+
+ if (!mIsServerSide) {
+ uri = URIParams();
+ SerializeURI(aURI, uri.get_URIParams());
+ nsresult rv = LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transportProvider = void_t();
+ } else {
+ uri = void_t();
+ loadInfoArgs = void_t();
+
+ MOZ_ASSERT(mServerTransportProvider);
+ PTransportProviderChild *ipcChild;
+ nsresult rv = mServerTransportProvider->GetIPCChild(&ipcChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transportProvider = ipcChild;
+ }
+
+ gNeckoChild->SendPWebSocketConstructor(this, tabChild,
+ 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)
+ : 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(NS_GetCurrentThread() == mTargetThread);
+ return NS_DispatchToMainThread(new CloseEvent(this, code, reason));
+ }
+ 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)
+ : 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());
+ return NS_DispatchToMainThread(new MsgEvent(this, aMsg, false));
+ }
+ 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());
+ return NS_DispatchToMainThread(new MsgEvent(this, aMsg, true));
+ }
+ 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,
+ OptionalInputStreamParams *aStream,
+ uint32_t aLength)
+ : mChild(aChild)
+ , mStream(aStream)
+ , mLength(aLength)
+ {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mChild->SendBinaryStream(mStream, mLength);
+ return NS_OK;
+ }
+private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsAutoPtr<OptionalInputStreamParams> mStream;
+ uint32_t mLength;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendBinaryStream(nsIInputStream *aStream,
+ uint32_t aLength)
+{
+ OptionalInputStreamParams *stream = new OptionalInputStreamParams();
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ SerializeInputStream(aStream, *stream, fds);
+
+ MOZ_ASSERT(fds.IsEmpty());
+
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mTargetThread);
+ return NS_DispatchToMainThread(new BinaryStreamEvent(this, stream, aLength));
+ }
+ return SendBinaryStream(stream, aLength);
+}
+
+nsresult
+WebSocketChannelChild::SendBinaryStream(OptionalInputStreamParams *aStream,
+ uint32_t aLength)
+{
+ LOG(("WebSocketChannelChild::SendBinaryStream() %p\n", this));
+
+ nsAutoPtr<OptionalInputStreamParams> stream(aStream);
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendBinaryStream(*stream, 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;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelChild::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelChild::RetargetDeliveryTo(nsIEventTarget* aTargetThread)
+{
+ nsresult rv = BaseWebSocketChannel::RetargetDeliveryTo(aTargetThread);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ return mEventQ->RetargetDeliveryTo(aTargetThread);
+}
+
+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..9fbb707ed0
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.h
@@ -0,0 +1,97 @@
+/* -*- 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/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:
+ explicit WebSocketChannelChild(bool aSecure);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+
+ // 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;
+ nsresult SendBinaryStream(OptionalInputStreamParams *aStream, uint32_t aLength);
+ 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();
+
+ bool RecvOnStart(const nsCString& aProtocol, const nsCString& aExtensions,
+ const nsString& aEffectiveURL, const bool& aSecure) override;
+ bool RecvOnStop(const nsresult& aStatusCode) override;
+ bool RecvOnMessageAvailable(const nsCString& aMsg) override;
+ bool RecvOnBinaryMessageAvailable(const nsCString& aMsg) override;
+ bool RecvOnAcknowledge(const uint32_t& aSize) override;
+ bool RecvOnServerClose(const uint16_t& aCode, const nsCString &aReason) override;
+
+ void OnStart(const nsCString& aProtocol, const nsCString& aExtensions,
+ const nsString& aEffectiveURL, const bool& aSecure);
+ 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();
+
+ void DispatchToTargetThread(ChannelEvent *aChannelEvent);
+ bool IsOnTargetThread();
+
+ void MaybeReleaseIPCObject();
+
+ RefPtr<ChannelEventQueue> mEventQ;
+ nsString mEffectiveURL;
+
+ // 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;
+};
+
+} // 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..742f05b7dc
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -0,0 +1,306 @@
+/* -*- 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)
+ , mIPCOpen(true)
+ , mSerial(aSerial)
+{
+ // Websocket channels can't have a private browsing override
+ MOZ_ASSERT_IF(!aLoadContext, aOverrideStatus == kPBOverride_Unset);
+}
+
+WebSocketChannelParent::~WebSocketChannelParent()
+{
+}
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::PWebSocketChannelParent
+//-----------------------------------------------------------------------------
+
+bool
+WebSocketChannelParent::RecvDeleteSelf()
+{
+ LOG(("WebSocketChannelParent::RecvDeleteSelf() %p\n", this));
+ mChannel = nullptr;
+ mAuthProvider = nullptr;
+ return mIPCOpen ? Send__delete__(this) : true;
+}
+
+bool
+WebSocketChannelParent::RecvAsyncOpen(const OptionalURIParams& 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 OptionalLoadInfoArgs& aLoadInfoArgs,
+ const OptionalTransportProvider& aTransportProvider,
+ const nsCString& aNegotiatedExtensions)
+{
+ LOG(("WebSocketChannelParent::RecvAsyncOpen() %p\n", this));
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsILoadInfo> loadInfo;
+
+ 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.type() != OptionalTransportProvider::Tvoid_t) {
+ RefPtr<TransportProviderParent> provider =
+ static_cast<TransportProviderParent*>(
+ aTransportProvider.get_PTransportProviderParent());
+ rv = mChannel->SetServerParameters(provider, aNegotiatedExtensions);
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+ } else {
+ uri = DeserializeURI(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));
+ mChannel->SetPingInterval(aPingInterval / 1000);
+ }
+ if (aClientSetPingTimeout) {
+ MOZ_ASSERT(aPingTimeout >= 1000 && !(aPingTimeout % 1000));
+ mChannel->SetPingTimeout(aPingTimeout / 1000);
+ }
+
+ rv = mChannel->AsyncOpen(uri, aOrigin, aInnerWindowID, this, nullptr);
+ if (NS_FAILED(rv))
+ goto fail;
+
+ return true;
+
+fail:
+ mChannel = nullptr;
+ return SendOnStop(rv);
+}
+
+bool
+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, true);
+ }
+
+ return true;
+}
+
+bool
+WebSocketChannelParent::RecvSendMsg(const nsCString& aMsg)
+{
+ LOG(("WebSocketChannelParent::RecvSendMsg() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->SendMsg(aMsg);
+ NS_ENSURE_SUCCESS(rv, true);
+ }
+ return true;
+}
+
+bool
+WebSocketChannelParent::RecvSendBinaryMsg(const nsCString& aMsg)
+{
+ LOG(("WebSocketChannelParent::RecvSendBinaryMsg() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->SendBinaryMsg(aMsg);
+ NS_ENSURE_SUCCESS(rv, true);
+ }
+ return true;
+}
+
+bool
+WebSocketChannelParent::RecvSendBinaryStream(const InputStreamParams& aStream,
+ const uint32_t& aLength)
+{
+ LOG(("WebSocketChannelParent::RecvSendBinaryStream() %p\n", this));
+ if (mChannel) {
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(aStream, fds);
+ if (!stream) {
+ return false;
+ }
+ nsresult rv = mChannel->SendBinaryStream(stream, aLength);
+ NS_ENSURE_SUCCESS(rv, true);
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnStart(nsISupports *aContext)
+{
+ LOG(("WebSocketChannelParent::OnStart() %p\n", this));
+ nsAutoCString protocol, extensions;
+ nsString effectiveURL;
+ bool encrypted = false;
+ if (mChannel) {
+ mChannel->GetProtocol(protocol);
+ mChannel->GetExtensions(extensions);
+
+ RefPtr<WebSocketChannel> channel;
+ channel = static_cast<WebSocketChannel*>(mChannel.get());
+ MOZ_ASSERT(channel);
+
+ channel->GetEffectiveURL(effectiveURL);
+ encrypted = channel->IsEncrypted();
+ }
+ if (!mIPCOpen || !SendOnStart(protocol, extensions, effectiveURL, encrypted)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnStop(nsISupports *aContext, nsresult aStatusCode)
+{
+ LOG(("WebSocketChannelParent::OnStop() %p\n", this));
+ if (!mIPCOpen || !SendOnStop(aStatusCode)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnMessageAvailable(nsISupports *aContext, const nsACString& aMsg)
+{
+ LOG(("WebSocketChannelParent::OnMessageAvailable() %p\n", this));
+ if (!mIPCOpen || !SendOnMessageAvailable(nsCString(aMsg))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnBinaryMessageAvailable(nsISupports *aContext, const nsACString& aMsg)
+{
+ LOG(("WebSocketChannelParent::OnBinaryMessageAvailable() %p\n", this));
+ if (!mIPCOpen || !SendOnBinaryMessageAvailable(nsCString(aMsg))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
+{
+ LOG(("WebSocketChannelParent::OnAcknowledge() %p\n", this));
+ if (!mIPCOpen || !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 (!mIPCOpen || !SendOnServerClose(code, nsCString(reason))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void
+WebSocketChannelParent::ActorDestroy(ActorDestroyReason why)
+{
+ LOG(("WebSocketChannelParent::ActorDestroy() %p\n", this));
+ mIPCOpen = false;
+}
+
+//-----------------------------------------------------------------------------
+// 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)))
+ return mAuthProvider->GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL,
+ iid, result);
+
+ // 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..10c363ef2b
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.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_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
+{
+ ~WebSocketChannelParent();
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvider,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus,
+ uint32_t aSerial);
+
+ private:
+ bool RecvAsyncOpen(const OptionalURIParams& 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 OptionalLoadInfoArgs& aLoadInfoArgs,
+ const OptionalTransportProvider& aTransportProvider,
+ const nsCString& aNegotiatedExtensions) override;
+ bool RecvClose(const uint16_t & code, const nsCString & reason) override;
+ bool RecvSendMsg(const nsCString& aMsg) override;
+ bool RecvSendBinaryMsg(const nsCString& aMsg) override;
+ bool RecvSendBinaryStream(const InputStreamParams& aStream,
+ const uint32_t& aLength) override;
+ bool RecvDeleteSelf() override;
+
+ void ActorDestroy(ActorDestroyReason why) override;
+
+ nsCOMPtr<nsIAuthPromptProvider> mAuthProvider;
+ nsCOMPtr<nsIWebSocketChannel> mChannel;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ bool mIPCOpen;
+
+ 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..82553a49b1
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketEventListenerChild.h"
+
+#include "WebSocketEventService.h"
+#include "WebSocketFrame.h"
+
+namespace mozilla {
+namespace net {
+
+WebSocketEventListenerChild::WebSocketEventListenerChild(uint64_t aInnerWindowID)
+ : mService(WebSocketEventService::GetOrCreate())
+ , mInnerWindowID(aInnerWindowID)
+{}
+
+WebSocketEventListenerChild::~WebSocketEventListenerChild()
+{
+ MOZ_ASSERT(!mService);
+}
+
+bool
+WebSocketEventListenerChild::RecvWebSocketCreated(const uint32_t& aWebSocketSerialID,
+ const nsString& aURI,
+ const nsCString& aProtocols)
+{
+ if (mService) {
+ mService->WebSocketCreated(aWebSocketSerialID, mInnerWindowID, aURI,
+ aProtocols);
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvWebSocketOpened(const uint32_t& aWebSocketSerialID,
+ const nsString& aEffectiveURI,
+ const nsCString& aProtocols,
+ const nsCString& aExtensions)
+{
+ if (mService) {
+ mService->WebSocketOpened(aWebSocketSerialID, mInnerWindowID,
+ aEffectiveURI, aProtocols, aExtensions);
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvWebSocketMessageAvailable(const uint32_t& aWebSocketSerialID,
+ const nsCString& aData,
+ const uint16_t& aMessageType)
+{
+ if (mService) {
+ mService->WebSocketMessageAvailable(aWebSocketSerialID, mInnerWindowID,
+ aData, aMessageType);
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvWebSocketClosed(const uint32_t& aWebSocketSerialID,
+ const bool& aWasClean,
+ const uint16_t& aCode,
+ const nsString& aReason)
+{
+ if (mService) {
+ mService->WebSocketClosed(aWebSocketSerialID, mInnerWindowID,
+ aWasClean, aCode, aReason);
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvFrameReceived(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData)
+{
+ if (mService) {
+ RefPtr<WebSocketFrame> frame = new WebSocketFrame(aFrameData);
+ mService->FrameReceived(aWebSocketSerialID, mInnerWindowID, frame.forget());
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvFrameSent(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData)
+{
+ if (mService) {
+ RefPtr<WebSocketFrame> frame = new WebSocketFrame(aFrameData);
+ mService->FrameSent(aWebSocketSerialID, mInnerWindowID, frame.forget());
+ }
+
+ return true;
+}
+
+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..8a42abac2b
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerChild.h
@@ -0,0 +1,62 @@
+/* -*- 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/PWebSocketEventListenerChild.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketEventService;
+
+class WebSocketEventListenerChild final : public PWebSocketEventListenerChild
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(WebSocketEventListenerChild)
+
+ explicit WebSocketEventListenerChild(uint64_t aInnerWindowID);
+
+ bool RecvWebSocketCreated(const uint32_t& aWebSocketSerialID,
+ const nsString& aURI,
+ const nsCString& aProtocols) override;
+
+ bool RecvWebSocketOpened(const uint32_t& aWebSocketSerialID,
+ const nsString& aEffectiveURI,
+ const nsCString& aProtocols,
+ const nsCString& aExtensions) override;
+
+ bool RecvWebSocketMessageAvailable(const uint32_t& aWebSocketSerialID,
+ const nsCString& aData,
+ const uint16_t& aMessageType) override;
+
+ bool RecvWebSocketClosed(const uint32_t& aWebSocketSerialID,
+ const bool& aWasClean,
+ const uint16_t& aCode,
+ const nsString& aReason) override;
+
+ bool RecvFrameReceived(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData) override;
+
+ bool RecvFrameSent(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData) override;
+
+ 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..1814ecd7d6
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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)
+{
+ mService->AddListener(mInnerWindowID, this);
+}
+
+WebSocketEventListenerParent::~WebSocketEventListenerParent()
+{
+ MOZ_ASSERT(!mService);
+}
+
+bool
+WebSocketEventListenerParent::RecvClose()
+{
+ if (mService) {
+ UnregisterListener();
+ Unused << Send__delete__(this);
+ }
+
+ return true;
+}
+
+void
+WebSocketEventListenerParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ UnregisterListener();
+}
+
+void
+WebSocketEventListenerParent::UnregisterListener()
+{
+ if (mService) {
+ mService->RemoveListener(mInnerWindowID, this);
+ 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)
+{
+ Unused << SendWebSocketOpened(aWebSocketSerialID, nsString(aEffectiveURI),
+ nsCString(aProtocols), nsCString(aExtensions));
+ 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..5b5b10d734
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.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_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
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETEVENTLISTENER
+
+ explicit WebSocketEventListenerParent(uint64_t aInnerWindowID);
+
+private:
+ ~WebSocketEventListenerParent();
+
+ virtual bool RecvClose() override;
+
+ 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..2f9486a706
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.cpp
@@ -0,0 +1,578 @@
+/* -*- 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"
+
+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)
+ : 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()
+ {}
+
+ 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(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)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID)
+ , mEffectiveURI(aEffectiveURI)
+ , mProtocols(aProtocols)
+ , mExtensions(aExtensions)
+ {}
+
+private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override
+ {
+ DebugOnly<nsresult> rv = aListener->WebSocketOpened(mWebSocketSerialID,
+ mEffectiveURI,
+ mProtocols,
+ mExtensions);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketOpened failed");
+ }
+
+ const nsString mEffectiveURI;
+ const nsCString mProtocols;
+ const nsCString mExtensions;
+};
+
+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::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)
+{
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketCreatedRunnable> runnable =
+ new WebSocketCreatedRunnable(aWebSocketSerialID, aInnerWindowID,
+ aURI, aProtocols);
+ DebugOnly<nsresult> rv = 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)
+{
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketOpenedRunnable> runnable =
+ new WebSocketOpenedRunnable(aWebSocketSerialID, aInnerWindowID,
+ aEffectiveURI, aProtocols, aExtensions);
+ DebugOnly<nsresult> rv = 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)
+{
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketMessageAvailableRunnable> runnable =
+ new WebSocketMessageAvailableRunnable(aWebSocketSerialID, aInnerWindowID,
+ aData, aMessageType);
+ DebugOnly<nsresult> rv = 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)
+{
+ // 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 = 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)
+{
+ RefPtr<WebSocketFrame> frame(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 = 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)
+{
+ RefPtr<WebSocketFrame> frame(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 = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+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::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);
+}
+
+} // net namespace
+} // mozilla namespace
diff --git a/netwerk/protocol/websocket/WebSocketEventService.h b/netwerk/protocol/websocket/WebSocketEventService.h
new file mode 100644
index 0000000000..f232679998
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.h
@@ -0,0 +1,124 @@
+/* -*- 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 "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+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> GetOrCreate();
+
+ void WebSocketCreated(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aURI,
+ const nsACString& aProtocols);
+
+ void WebSocketOpened(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions);
+
+ void WebSocketMessageAvailable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsACString& aData,
+ uint16_t aMessageType);
+
+ void WebSocketClosed(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ bool aWasClean,
+ uint16_t aCode,
+ const nsAString& aReason);
+
+ void FrameReceived(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame);
+
+ void FrameSent(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame);
+
+ 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;
+
+ 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;
+};
+
+} // net namespace
+} // mozilla namespace
+
+/**
+ * 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..b93729b3f6
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.cpp
@@ -0,0 +1,169 @@
+/* -*- 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 "WebSocketChannel.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h" // for NS_IsMainThread
+#include "ipc/IPCMessageUtils.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(PR_GetCurrentThread() == gSocketThread);
+ mData.mTimeStamp = PR_Now();
+}
+
+WebSocketFrame::~WebSocketFrame()
+{}
+
+#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, mOpCode);
+ WriteParam(aMessage, mMaskBit);
+ 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);
+}
+
+} // net namespace
+} // mozilla namespace
diff --git a/netwerk/protocol/websocket/WebSocketFrame.h b/netwerk/protocol/websocket/WebSocketFrame.h
new file mode 100644
index 0000000000..28c98466ea
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.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_WebSocketFrame_h
+#define mozilla_net_WebSocketFrame_h
+
+#include "nsAutoPtr.h"
+#include "nsIWebSocketEventService.h"
+#include "nsString.h"
+
+namespace IPC {
+class Message;
+}
+
+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();
+
+ WebSocketFrameData mData;
+};
+
+} // net namespace
+} // mozilla namespace
+
+#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..6bfe911c44
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketLog.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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;
+}
+}
+
+#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..49320fe31b
--- /dev/null
+++ b/netwerk/protocol/websocket/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 += [
+ 'nsITransportProvider.idl',
+ 'nsIWebSocketChannel.idl',
+ 'nsIWebSocketEventService.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['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/protocol/websocket/nsITransportProvider.idl b/netwerk/protocol/websocket/nsITransportProvider.idl
new file mode 100644
index 0000000000..e1723da3b4
--- /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.
+ 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] PTransportProviderChild getIPCChild();
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketChannel.idl b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
new file mode 100644
index 0000000000..0ffd3f60b2
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsIURI;
+interface nsIInterfaceRequestor;
+interface nsILoadGroup;
+interface nsIWebSocketListener;
+interface nsIInputStream;
+interface nsILoadInfo;
+interface nsIDOMNode;
+interface nsIPrincipal;
+interface nsITransportProvider;
+
+#include "nsISupports.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, 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.
+ */
+ readonly attribute nsIURI originalURI;
+
+ /**
+ * The readonly URI corresponding to the protocol connection after any
+ * redirections are completed.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The notification callbacks for authorization, etc..
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any)
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * The load group of of the websocket
+ */
+ attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load info of the websocket
+ */
+ attribute nsILoadInfo loadInfo;
+
+ /**
+ * Sec-Websocket-Protocol value
+ */
+ attribute ACString protocol;
+
+ /**
+ * Sec-Websocket-Extensions response header value
+ */
+ readonly attribute ACString extensions;
+
+ /**
+ * Init the WebSocketChannel with LoadInfo arguments.
+ * @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
+ *
+ * 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.
+ */
+ void initLoadInfo(in nsIDOMNode aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in unsigned long 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
+ */
+ 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
+ */
+ 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
+ */
+ void sendMsg(in AUTF8String aMsg);
+
+ /**
+ * Use to send binary message down the connection to WebSocket peer.
+ *
+ * @param aMsg the data to send
+ */
+ void sendBinaryMsg(in ACString aMsg);
+
+ /**
+ * Use to send a binary stream (Blob) to Websocket peer.
+ *
+ * @param aStream The input stream to be sent.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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;
+ }
+%}
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketEventService.idl b/netwerk/protocol/websocket/nsIWebSocketEventService.idl
new file mode 100644
index 0000000000..c2986dc2f0
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketEventService.idl
@@ -0,0 +1,79 @@
+/* -*- 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"
+
+[scriptable, builtinclass, uuid(6714a6be-2265-4f73-a988-d78a12416037)]
+interface nsIWebSocketFrame : nsISupports
+{
+ readonly attribute DOMHighResTimeStamp timeStamp;
+
+ readonly attribute boolean finBit;
+
+ readonly attribute boolean rsvBit1;
+ readonly attribute boolean rsvBit2;
+ readonly attribute boolean rsvBit3;
+
+ readonly attribute unsigned short opCode;
+
+ readonly attribute boolean maskBit;
+
+ readonly attribute unsigned long mask;
+
+ 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
+{
+ void webSocketCreated(in unsigned long aWebSocketSerialID,
+ in AString aURI,
+ in ACString aProtocols);
+
+ void webSocketOpened(in unsigned long aWebSocketSerialID,
+ in AString aEffectiveURI,
+ in ACString aProtocols,
+ in ACString aExtensions);
+
+ const unsigned short TYPE_STRING = 0x0;
+ const unsigned short TYPE_BLOB = 0x1;
+ const unsigned short TYPE_ARRAYBUFFER = 0x2;
+
+ void webSocketMessageAvailable(in unsigned long aWebSocketSerialID,
+ in ACString aMessage,
+ in unsigned short aType);
+
+ void webSocketClosed(in unsigned long aWebSocketSerialID,
+ in boolean aWasClean,
+ in unsigned short aCode,
+ in AString aReason);
+
+ void frameReceived(in unsigned long aWebSocketSerialID,
+ in nsIWebSocketFrame aFrame);
+
+ void frameSent(in unsigned long aWebSocketSerialID,
+ in nsIWebSocketFrame aFrame);
+};
+
+[scriptable, builtinclass, uuid(b89d1b90-2cf3-4d8f-ac21-5aedfb25c760)]
+interface nsIWebSocketEventService : nsISupports
+{
+ void addListener(in unsigned long long aInnerWindowID,
+ in nsIWebSocketEventListener aListener);
+
+ void removeListener(in unsigned long long aInnerWindowID,
+ in nsIWebSocketEventListener aListener);
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketListener.idl b/netwerk/protocol/websocket/nsIWebSocketListener.idl
new file mode 100644
index 0000000000..ac2d42f761
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketListener.idl
@@ -0,0 +1,90 @@
+/* -*- 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
+ */
+ 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)
+ */
+ void onStop(in nsISupports aContext,
+ in nsresult aStatusCode);
+
+ /**
+ * Called to deliver text message.
+ *
+ * @param aContext user defined context
+ * @param aMsg the message data
+ */
+ void onMessageAvailable(in nsISupports aContext,
+ in AUTF8String aMsg);
+
+ /**
+ * Called to deliver binary message.
+ *
+ * @param aContext user defined context
+ * @param aMsg the message data
+ */
+ 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
+ */
+ 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
+
+ */
+ void onServerClose(in nsISupports aContext, in unsigned short aCode,
+ in AUTF8String aReason);
+
+};
+
+
diff --git a/netwerk/protocol/wyciwyg/PWyciwygChannel.ipdl b/netwerk/protocol/wyciwyg/PWyciwygChannel.ipdl
new file mode 100644
index 0000000000..16f5848fb4
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/PWyciwygChannel.ipdl
@@ -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 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 URIParams;
+include PBackgroundSharedTypes;
+include PBrowserOrId;
+
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+protocol PWyciwygChannel
+{
+ manager PNecko;
+
+parent:
+ async __delete__();
+
+ async Init(URIParams uri,
+ PrincipalInfo requestingPrincipalInfo,
+ PrincipalInfo triggeringPrincipalInfo,
+ PrincipalInfo principalToInheritInfo,
+ uint32_t securityFlags,
+ uint32_t contentPolicyType);
+ async AsyncOpen(URIParams originalURI,
+ uint32_t loadFlags,
+ SerializedLoadContext loadContext,
+ PBrowserOrId browser);
+ async AppData(SerializedLoadContext loadContext, PBrowserOrId browser);
+
+ // methods corresponding to those of nsIWyciwygChannel
+ async WriteToCacheEntry(nsString data);
+ async CloseCacheEntry(nsresult reason);
+ async SetCharsetAndSource(int32_t source, nsCString charset);
+ async SetSecurityInfo(nsCString securityInfo);
+ async Cancel(nsresult status);
+
+child:
+ async OnStartRequest(nsresult statusCode,
+ int64_t contentLength,
+ int32_t source,
+ nsCString charset,
+ nsCString securityInfo);
+
+ async OnDataAvailable(nsCString data,
+ uint64_t offset);
+
+ async OnStopRequest(nsresult statusCode);
+
+ async CancelEarly(nsresult statusCode);
+};
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
new file mode 100644
index 0000000000..2a794df3b4
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
@@ -0,0 +1,764 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWyciwyg.h"
+
+#include "base/compiler_specific.h"
+
+#include "mozilla/net/ChannelEventQueue.h"
+#include "WyciwygChannelChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/ContentChild.h"
+
+#include "nsCharsetSource.h"
+#include "nsContentUtils.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+#include "nsIProgressEventSink.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+
+using namespace mozilla::ipc;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(WyciwygChannelChild,
+ nsIRequest,
+ nsIChannel,
+ nsIWyciwygChannel,
+ nsIPrivateBrowsingChannel)
+
+
+WyciwygChannelChild::WyciwygChannelChild()
+ : mStatus(NS_OK)
+ , mIsPending(false)
+ , mCanceled(false)
+ , mLoadFlags(LOAD_NORMAL)
+ , mContentLength(-1)
+ , mCharsetSource(kCharsetUninitialized)
+ , mState(WCC_NEW)
+ , mIPCOpen(false)
+ , mSentAppData(false)
+{
+ LOG(("Creating WyciwygChannelChild @%x\n", this));
+ mEventQ = new ChannelEventQueue(NS_ISUPPORTS_CAST(nsIWyciwygChannel*, this));
+}
+
+WyciwygChannelChild::~WyciwygChannelChild()
+{
+ LOG(("Destroying WyciwygChannelChild @%x\n", this));
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+ }
+}
+
+void
+WyciwygChannelChild::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void
+WyciwygChannelChild::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+nsresult
+WyciwygChannelChild::Init(nsIURI* uri)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+
+ mState = WCC_INIT;
+
+ mURI = uri;
+ mOriginalURI = uri;
+
+ URIParams serializedUri;
+ SerializeURI(uri, serializedUri);
+
+ // propagate loadInfo
+ mozilla::ipc::PrincipalInfo requestingPrincipalInfo;
+ mozilla::ipc::PrincipalInfo triggeringPrincipalInfo;
+ mozilla::ipc::PrincipalInfo principalToInheritInfo;
+ uint32_t securityFlags;
+ uint32_t policyType;
+ if (mLoadInfo) {
+ mozilla::ipc::PrincipalToPrincipalInfo(mLoadInfo->LoadingPrincipal(),
+ &requestingPrincipalInfo);
+ mozilla::ipc::PrincipalToPrincipalInfo(mLoadInfo->TriggeringPrincipal(),
+ &triggeringPrincipalInfo);
+ mozilla::ipc::PrincipalToPrincipalInfo(mLoadInfo->PrincipalToInherit(),
+ &principalToInheritInfo);
+ securityFlags = mLoadInfo->GetSecurityFlags();
+ policyType = mLoadInfo->InternalContentPolicyType();
+ }
+ else {
+ // use default values if no loadInfo is provided
+ mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(),
+ &requestingPrincipalInfo);
+ mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(),
+ &triggeringPrincipalInfo);
+ mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(),
+ &principalToInheritInfo);
+ securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
+ policyType = nsIContentPolicy::TYPE_OTHER;
+ }
+
+ SendInit(serializedUri,
+ requestingPrincipalInfo,
+ triggeringPrincipalInfo,
+ principalToInheritInfo,
+ securityFlags,
+ policyType);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelChild::PWyciwygChannelChild
+//-----------------------------------------------------------------------------
+
+class WyciwygStartRequestEvent : public ChannelEvent
+{
+public:
+ WyciwygStartRequestEvent(WyciwygChannelChild* child,
+ const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo)
+ : mChild(child), mStatusCode(statusCode), mContentLength(contentLength),
+ mSource(source), mCharset(charset), mSecurityInfo(securityInfo) {}
+ void Run() { mChild->OnStartRequest(mStatusCode, mContentLength, mSource,
+ mCharset, mSecurityInfo); }
+private:
+ WyciwygChannelChild* mChild;
+ nsresult mStatusCode;
+ int64_t mContentLength;
+ int32_t mSource;
+ nsCString mCharset;
+ nsCString mSecurityInfo;
+};
+
+bool
+WyciwygChannelChild::RecvOnStartRequest(const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo)
+{
+ mEventQ->RunOrEnqueue(new WyciwygStartRequestEvent(this, statusCode,
+ contentLength, source,
+ charset, securityInfo));
+ return true;
+}
+
+void
+WyciwygChannelChild::OnStartRequest(const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo)
+{
+ LOG(("WyciwygChannelChild::RecvOnStartRequest [this=%p]\n", this));
+
+ mState = WCC_ONSTART;
+
+ mStatus = statusCode;
+ mContentLength = contentLength;
+ mCharsetSource = source;
+ mCharset = charset;
+
+ if (!securityInfo.IsEmpty()) {
+ NS_DeserializeObject(securityInfo, getter_AddRefs(mSecurityInfo));
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ nsresult rv = mListener->OnStartRequest(this, mListenerContext);
+ if (NS_FAILED(rv))
+ Cancel(rv);
+}
+
+class WyciwygDataAvailableEvent : public ChannelEvent
+{
+public:
+ WyciwygDataAvailableEvent(WyciwygChannelChild* child,
+ const nsCString& data,
+ const uint64_t& offset)
+ : mChild(child), mData(data), mOffset(offset) {}
+ void Run() { mChild->OnDataAvailable(mData, mOffset); }
+private:
+ WyciwygChannelChild* mChild;
+ nsCString mData;
+ uint64_t mOffset;
+};
+
+bool
+WyciwygChannelChild::RecvOnDataAvailable(const nsCString& data,
+ const uint64_t& offset)
+{
+ mEventQ->RunOrEnqueue(new WyciwygDataAvailableEvent(this, data, offset));
+ return true;
+}
+
+void
+WyciwygChannelChild::OnDataAvailable(const nsCString& data,
+ const uint64_t& offset)
+{
+ LOG(("WyciwygChannelChild::RecvOnDataAvailable [this=%p]\n", this));
+
+ if (mCanceled)
+ return;
+
+ mState = WCC_ONDATA;
+
+ // 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),
+ data.get(),
+ data.Length(),
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ rv = mListener->OnDataAvailable(this, mListenerContext,
+ stringStream, offset, data.Length());
+ if (NS_FAILED(rv))
+ Cancel(rv);
+
+ if (mProgressSink && NS_SUCCEEDED(rv)) {
+ mProgressSink->OnProgress(this, nullptr, offset + data.Length(),
+ mContentLength);
+ }
+}
+
+class WyciwygStopRequestEvent : public ChannelEvent
+{
+public:
+ WyciwygStopRequestEvent(WyciwygChannelChild* child,
+ const nsresult& statusCode)
+ : mChild(child), mStatusCode(statusCode) {}
+ void Run() { mChild->OnStopRequest(mStatusCode); }
+private:
+ WyciwygChannelChild* mChild;
+ nsresult mStatusCode;
+};
+
+bool
+WyciwygChannelChild::RecvOnStopRequest(const nsresult& statusCode)
+{
+ mEventQ->RunOrEnqueue(new WyciwygStopRequestEvent(this, statusCode));
+ return true;
+}
+
+void
+WyciwygChannelChild::OnStopRequest(const nsresult& statusCode)
+{
+ LOG(("WyciwygChannelChild::RecvOnStopRequest [this=%p status=%u]\n",
+ this, statusCode));
+
+ { // We need to ensure that all IPDL message dispatching occurs
+ // before we delete the protocol below
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mState = WCC_ONSTOP;
+
+ mIsPending = false;
+
+ if (!mCanceled)
+ mStatus = statusCode;
+
+ mListener->OnStopRequest(this, mListenerContext, statusCode);
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+ }
+
+ if (mIPCOpen)
+ PWyciwygChannelChild::Send__delete__(this);
+}
+
+class WyciwygCancelEvent : public ChannelEvent
+{
+ public:
+ WyciwygCancelEvent(WyciwygChannelChild* child, const nsresult& status)
+ : mChild(child)
+ , mStatus(status) {}
+
+ void Run() { mChild->CancelEarly(mStatus); }
+ private:
+ WyciwygChannelChild* mChild;
+ nsresult mStatus;
+};
+
+bool
+WyciwygChannelChild::RecvCancelEarly(const nsresult& statusCode)
+{
+ mEventQ->RunOrEnqueue(new WyciwygCancelEvent(this, statusCode));
+ return true;
+}
+
+void WyciwygChannelChild::CancelEarly(const nsresult& statusCode)
+{
+ LOG(("WyciwygChannelChild::CancelEarly [this=%p]\n", this));
+
+ if (mCanceled)
+ return;
+
+ mCanceled = true;
+ mStatus = statusCode;
+
+ mIsPending = false;
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ if (mListener) {
+ mListener->OnStartRequest(this, mListenerContext);
+ mListener->OnStopRequest(this, mListenerContext, mStatus);
+ }
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mIPCOpen)
+ PWyciwygChannelChild::Send__delete__(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetName(nsACString & aName)
+{
+ return mURI->GetSpec(aName);
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::IsPending(bool *aIsPending)
+{
+ *aIsPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetStatus(nsresult *aStatus)
+{
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Cancel(nsresult aStatus)
+{
+ if (mCanceled)
+ return NS_OK;
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (mIPCOpen)
+ SendCancel(aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Suspend()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Resume()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ *aLoadGroup = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIProgressEventSink),
+ getter_AddRefs(mProgressSink));
+
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetOriginalURI(nsIURI * *aOriginalURI)
+{
+ *aOriginalURI = mOriginalURI;
+ NS_ADDREF(*aOriginalURI);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetOriginalURI(nsIURI * aOriginalURI)
+{
+ NS_ENSURE_TRUE(mState == WCC_INIT, NS_ERROR_UNEXPECTED);
+
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetURI(nsIURI * *aURI)
+{
+ *aURI = mURI;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetOwner(nsISupports * *aOwner)
+{
+ NS_IF_ADDREF(*aOwner = mOwner);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetOwner(nsISupports * aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetNotificationCallbacks(nsIInterfaceRequestor * *aCallbacks)
+{
+ *aCallbacks = mCallbacks;
+ NS_IF_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetNotificationCallbacks(nsIInterfaceRequestor * aCallbacks)
+{
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIProgressEventSink),
+ getter_AddRefs(mProgressSink));
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentType(nsACString & aContentType)
+{
+ aContentType.AssignLiteral(WYCIWYG_TYPE);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentType(const nsACString & aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentCharset(nsACString & aContentCharset)
+{
+ aContentCharset.AssignLiteral("UTF-16");
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentCharset(const nsACString & aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentLength(int64_t *aContentLength)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentLength(int64_t aContentLength)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Open(nsIInputStream **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+static mozilla::dom::TabChild*
+GetTabChild(nsIChannel* aChannel)
+{
+ nsCOMPtr<nsITabChild> iTabChild;
+ NS_QueryNotificationCallbacks(aChannel, iTabChild);
+ return iTabChild ? static_cast<mozilla::dom::TabChild*>(iTabChild.get()) : nullptr;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("WyciwygChannelChild::AsyncOpen [this=%p]\n", this));
+
+ // The only places creating wyciwyg: channels should be
+ // HTMLDocument::OpenCommon and session history. Both should be setting an
+ // owner or loadinfo.
+ NS_PRECONDITION(mOwner || mLoadInfo, "Must have a principal");
+ NS_ENSURE_STATE(mOwner || mLoadInfo);
+
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+
+ mListener = aListener;
+ mListenerContext = aContext;
+ mIsPending = true;
+
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ URIParams originalURI;
+ SerializeURI(mOriginalURI, originalURI);
+
+ mozilla::dom::TabChild* tabChild = GetTabChild(this);
+ if (MissingRequiredTabChild(tabChild, "wyciwyg")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PBrowserOrId browser = static_cast<ContentChild*>(Manager()->Manager())
+ ->GetBrowserOrId(tabChild);
+
+ SendAsyncOpen(originalURI, mLoadFlags, IPC::SerializedLoadContext(this), browser);
+
+ mSentAppData = true;
+ mState = WCC_OPENED;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+//-----------------------------------------------------------------------------
+// nsIWyciwygChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelChild::WriteToCacheEntry(const nsAString & aData)
+{
+ NS_ENSURE_TRUE((mState == WCC_INIT) ||
+ (mState == WCC_ONWRITE), NS_ERROR_UNEXPECTED);
+
+ if (!mSentAppData) {
+ mozilla::dom::TabChild* tabChild = GetTabChild(this);
+
+ PBrowserOrId browser = static_cast<ContentChild*>(Manager()->Manager())
+ ->GetBrowserOrId(tabChild);
+
+ SendAppData(IPC::SerializedLoadContext(this), browser);
+ mSentAppData = true;
+ }
+
+ SendWriteToCacheEntry(PromiseFlatString(aData));
+ mState = WCC_ONWRITE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::CloseCacheEntry(nsresult reason)
+{
+ NS_ENSURE_TRUE(mState == WCC_ONWRITE, NS_ERROR_UNEXPECTED);
+
+ SendCloseCacheEntry(reason);
+ mState = WCC_ONCLOSED;
+
+ if (mIPCOpen)
+ PWyciwygChannelChild::Send__delete__(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetSecurityInfo(nsISupports *aSecurityInfo)
+{
+ mSecurityInfo = aSecurityInfo;
+
+ if (mSecurityInfo) {
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(mSecurityInfo);
+ if (serializable) {
+ nsCString secInfoStr;
+ NS_SerializeToString(serializable, secInfoStr);
+ SendSetSecurityInfo(secInfoStr);
+ }
+ else {
+ NS_WARNING("Can't serialize security info");
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetCharsetAndSource(int32_t aSource, const nsACString & aCharset)
+{
+ // mState == WCC_ONSTART when reading from the channel
+ // mState == WCC_INIT when writing to the cache
+ NS_ENSURE_TRUE((mState == WCC_ONSTART) ||
+ (mState == WCC_INIT), NS_ERROR_UNEXPECTED);
+
+ mCharsetSource = aSource;
+ mCharset = aCharset;
+
+ // TODO ensure that nsWyciwygChannel in the parent has still the cache entry
+ SendSetCharsetAndSource(mCharsetSource, mCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetCharsetAndSource(int32_t *aSource, nsACString & _retval)
+{
+ NS_ENSURE_TRUE((mState == WCC_ONSTART) ||
+ (mState == WCC_ONDATA) ||
+ (mState == WCC_ONSTOP), NS_ERROR_NOT_AVAILABLE);
+
+ if (mCharsetSource == kCharsetUninitialized)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aSource = mCharsetSource;
+ _retval = mCharset;
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelChild.h b/netwerk/protocol/wyciwyg/WyciwygChannelChild.h
new file mode 100644
index 0000000000..a712c46921
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.h
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WyciwygChannelChild_h
+#define mozilla_net_WyciwygChannelChild_h
+
+#include "mozilla/net/PWyciwygChannelChild.h"
+#include "nsIWyciwygChannel.h"
+#include "nsIChannel.h"
+#include "nsILoadInfo.h"
+#include "PrivateBrowsingChannel.h"
+
+class nsIProgressEventSink;
+
+namespace mozilla {
+namespace net {
+
+class ChannelEventQueue;
+
+// TODO: replace with IPDL states
+enum WyciwygChannelChildState {
+ WCC_NEW,
+ WCC_INIT,
+
+ // States when reading from the channel
+ WCC_OPENED,
+ WCC_ONSTART,
+ WCC_ONDATA,
+ WCC_ONSTOP,
+
+ // States when writing to the cache
+ WCC_ONWRITE,
+ WCC_ONCLOSED
+};
+
+
+// Header file contents
+class WyciwygChannelChild final : public PWyciwygChannelChild
+ , public nsIWyciwygChannel
+ , public PrivateBrowsingChannel<WyciwygChannelChild>
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIWYCIWYGCHANNEL
+
+ WyciwygChannelChild();
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ nsresult Init(nsIURI *uri);
+
+ bool IsSuspended();
+
+protected:
+ virtual ~WyciwygChannelChild();
+
+ bool RecvOnStartRequest(const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo) override;
+ bool RecvOnDataAvailable(const nsCString& data,
+ const uint64_t& offset) override;
+ bool RecvOnStopRequest(const nsresult& statusCode) override;
+ bool RecvCancelEarly(const nsresult& statusCode) override;
+
+ void OnStartRequest(const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo);
+ void OnDataAvailable(const nsCString& data,
+ const uint64_t& offset);
+ void OnStopRequest(const nsresult& statusCode);
+ void CancelEarly(const nsresult& statusCode);
+
+ friend class PrivateBrowsingChannel<WyciwygChannelChild>;
+
+private:
+ nsresult mStatus;
+ bool mIsPending;
+ bool mCanceled;
+ uint32_t mLoadFlags;
+ int64_t mContentLength;
+ int32_t mCharsetSource;
+ nsCString mCharset;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+
+ // FIXME: replace with IPDL states (bug 536319)
+ enum WyciwygChannelChildState mState;
+
+ bool mIPCOpen;
+ bool mSentAppData;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ friend class WyciwygStartRequestEvent;
+ friend class WyciwygDataAvailableEvent;
+ friend class WyciwygStopRequestEvent;
+ friend class WyciwygCancelEvent;
+};
+
+inline bool
+WyciwygChannelChild::IsSuspended()
+{
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WyciwygChannelChild_h
diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp b/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
new file mode 100644
index 0000000000..be00c2d869
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
@@ -0,0 +1,373 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWyciwyg.h"
+
+#include "mozilla/net/WyciwygChannelParent.h"
+#include "nsWyciwygChannel.h"
+#include "nsNetUtil.h"
+#include "nsCharsetSource.h"
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoParent.h"
+#include "SerializedLoadContext.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+WyciwygChannelParent::WyciwygChannelParent()
+ : mIPCClosed(false)
+ , mReceivedAppData(false)
+{
+}
+
+WyciwygChannelParent::~WyciwygChannelParent()
+{
+}
+
+void
+WyciwygChannelParent::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;
+
+ // We need to force the cycle to break here
+ if (mChannel) {
+ mChannel->SetNotificationCallbacks(nullptr);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(WyciwygChannelParent,
+ nsIStreamListener,
+ nsIInterfaceRequestor,
+ nsIRequestObserver)
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::PWyciwygChannelParent
+//-----------------------------------------------------------------------------
+
+bool
+WyciwygChannelParent::RecvInit(const URIParams& aURI,
+ const ipc::PrincipalInfo& aRequestingPrincipalInfo,
+ const ipc::PrincipalInfo& aTriggeringPrincipalInfo,
+ const ipc::PrincipalInfo& aPrincipalToInheritInfo,
+ const uint32_t& aSecurityFlags,
+ const uint32_t& aContentPolicyType)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri)
+ return false;
+
+ LOG(("WyciwygChannelParent RecvInit [this=%p uri=%s]\n",
+ this, uri->GetSpecOrDefault().get()));
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ nsCOMPtr<nsIPrincipal> requestingPrincipal =
+ mozilla::ipc::PrincipalInfoToPrincipal(aRequestingPrincipalInfo, &rv);
+ if (NS_FAILED(rv)) {
+ return SendCancelEarly(rv);
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ mozilla::ipc::PrincipalInfoToPrincipal(aTriggeringPrincipalInfo, &rv);
+ if (NS_FAILED(rv)) {
+ return SendCancelEarly(rv);
+ }
+
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalToInheritInfo, &rv);
+ if (NS_FAILED(rv)) {
+ return SendCancelEarly(rv);
+ }
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(chan),
+ uri,
+ requestingPrincipal,
+ triggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ios);
+
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
+ rv = loadInfo->SetPrincipalToInherit(principalToInherit);
+ if (NS_FAILED(rv)) {
+ return SendCancelEarly(rv);
+ }
+
+ mChannel = do_QueryInterface(chan, &rv);
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvAppData(const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &parent)
+{
+ LOG(("WyciwygChannelParent RecvAppData [this=%p]\n", this));
+
+ if (!SetupAppData(loadContext, parent))
+ return false;
+
+ mChannel->SetNotificationCallbacks(this);
+ return true;
+}
+
+bool
+WyciwygChannelParent::SetupAppData(const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &aParent)
+{
+ if (!mChannel)
+ return true;
+
+ const char* error = NeckoParent::CreateChannelLoadContext(aParent,
+ Manager()->Manager(),
+ loadContext,
+ nullptr,
+ mLoadContext);
+ if (error) {
+ printf_stderr("WyciwygChannelParent::SetupAppData: FATAL ERROR: %s\n",
+ error);
+ return false;
+ }
+
+ if (!mLoadContext && loadContext.IsPrivateBitValid()) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(mChannel);
+ if (pbChannel)
+ pbChannel->SetPrivate(loadContext.mOriginAttributes.mPrivateBrowsingId > 0);
+ }
+
+ mReceivedAppData = true;
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvAsyncOpen(const URIParams& aOriginal,
+ const uint32_t& aLoadFlags,
+ const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &aParent)
+{
+ nsCOMPtr<nsIURI> original = DeserializeURI(aOriginal);
+ if (!original)
+ return false;
+
+ LOG(("WyciwygChannelParent RecvAsyncOpen [this=%p]\n", this));
+
+ if (!mChannel)
+ return true;
+
+ nsresult rv;
+
+ rv = mChannel->SetOriginalURI(original);
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ rv = mChannel->SetLoadFlags(aLoadFlags);
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ if (!mReceivedAppData && !SetupAppData(loadContext, aParent)) {
+ return false;
+ }
+
+ rv = mChannel->SetNotificationCallbacks(this);
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ rv = mChannel->AsyncOpen2(this);
+ }
+ else {
+ rv = mChannel->AsyncOpen(this, nullptr);
+ }
+
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvWriteToCacheEntry(const nsString& data)
+{
+ if (!mReceivedAppData) {
+ printf_stderr("WyciwygChannelParent::RecvWriteToCacheEntry: FATAL ERROR: didn't receive app data\n");
+ return false;
+ }
+
+ if (mChannel)
+ mChannel->WriteToCacheEntry(data);
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvCloseCacheEntry(const nsresult& reason)
+{
+ if (mChannel) {
+ mChannel->CloseCacheEntry(reason);
+ }
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvSetCharsetAndSource(const int32_t& aCharsetSource,
+ const nsCString& aCharset)
+{
+ if (mChannel)
+ mChannel->SetCharsetAndSource(aCharsetSource, aCharset);
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvSetSecurityInfo(const nsCString& aSecurityInfo)
+{
+ if (mChannel) {
+ nsCOMPtr<nsISupports> securityInfo;
+ NS_DeserializeObject(aSecurityInfo, getter_AddRefs(securityInfo));
+ mChannel->SetSecurityInfo(securityInfo);
+ }
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvCancel(const nsresult& aStatusCode)
+{
+ if (mChannel)
+ mChannel->Cancel(aStatusCode);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ LOG(("WyciwygChannelParent::OnStartRequest [this=%p]\n", this));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIWyciwygChannel> chan = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult status;
+ chan->GetStatus(&status);
+
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+
+ int32_t charsetSource = kCharsetUninitialized;
+ nsAutoCString charset;
+ chan->GetCharsetAndSource(&charsetSource, charset);
+
+ nsCOMPtr<nsISupports> securityInfo;
+ chan->GetSecurityInfo(getter_AddRefs(securityInfo));
+ nsCString secInfoStr;
+ if (securityInfo) {
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(securityInfo);
+ if (serializable)
+ NS_SerializeToString(serializable, secInfoStr);
+ else {
+ NS_ERROR("Can't serialize security info");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (mIPCClosed ||
+ !SendOnStartRequest(status, contentLength, charsetSource, charset, secInfoStr)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelParent::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ LOG(("WyciwygChannelParent::OnStopRequest: [this=%p status=%ul]\n",
+ this, aStatusCode));
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelParent::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ LOG(("WyciwygChannelParent::OnDataAvailable [this=%p]\n", this));
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mIPCClosed || !SendOnDataAvailable(data, aOffset)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelParent::GetInterface(const nsIID& uuid, void** result)
+{
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(uuid, result);
+}
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelParent.h b/netwerk/protocol/wyciwyg/WyciwygChannelParent.h
new file mode 100644
index 0000000000..be009487b1
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelParent.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_WyciwygChannelParent_h
+#define mozilla_net_WyciwygChannelParent_h
+
+#include "mozilla/net/PWyciwygChannelParent.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "nsIStreamListener.h"
+
+#include "nsIWyciwygChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+
+namespace mozilla {
+namespace dom {
+ class PBrowserParent;
+} // namespace dom
+
+namespace net {
+
+class WyciwygChannelParent : public PWyciwygChannelParent
+ , public nsIStreamListener
+ , public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ WyciwygChannelParent();
+
+protected:
+ virtual ~WyciwygChannelParent();
+
+ virtual bool RecvInit(const URIParams& uri,
+ const ipc::PrincipalInfo& aRequestingPrincipalInfo,
+ const ipc::PrincipalInfo& aTriggeringPrincipalInfo,
+ const ipc::PrincipalInfo& aPrincipalToInheritInfo,
+ const uint32_t& aSecurityFlags,
+ const uint32_t& aContentPolicyType) override;
+ virtual bool RecvAsyncOpen(const URIParams& original,
+ const uint32_t& loadFlags,
+ const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &parent) override;
+ virtual bool RecvWriteToCacheEntry(const nsString& data) override;
+ virtual bool RecvCloseCacheEntry(const nsresult& reason) override;
+ virtual bool RecvSetCharsetAndSource(const int32_t& source,
+ const nsCString& charset) override;
+ virtual bool RecvSetSecurityInfo(const nsCString& securityInfo) override;
+ virtual bool RecvCancel(const nsresult& statusCode) override;
+ virtual bool RecvAppData(const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &parent) override;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ bool SetupAppData(const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &aParent);
+
+ nsCOMPtr<nsIWyciwygChannel> mChannel;
+ bool mIPCClosed;
+ bool mReceivedAppData;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WyciwygChannelParent_h
diff --git a/netwerk/protocol/wyciwyg/moz.build b/netwerk/protocol/wyciwyg/moz.build
new file mode 100644
index 0000000000..b043137f75
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIWyciwygChannel.idl',
+]
+
+XPIDL_MODULE = 'necko_wyciwyg'
+
+EXPORTS.mozilla.net += [
+ 'WyciwygChannelChild.h',
+ 'WyciwygChannelParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsWyciwyg.cpp',
+ 'nsWyciwygChannel.cpp',
+ 'nsWyciwygProtocolHandler.cpp',
+ 'WyciwygChannelChild.cpp',
+ 'WyciwygChannelParent.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PWyciwygChannel.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
diff --git a/netwerk/protocol/wyciwyg/nsIWyciwygChannel.idl b/netwerk/protocol/wyciwyg/nsIWyciwygChannel.idl
new file mode 100644
index 0000000000..29bcc4d771
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsIWyciwygChannel.idl
@@ -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/. */
+
+#include "nsIChannel.idl"
+
+/**
+ * A channel to manage all cache-related interactions for layout
+ * when it is dealing with dynamic pages created through
+ * document.write(). This interface provides methods that will
+ * help layout save dynamic pages in cache for future retrievals.
+ */
+
+[scriptable, uuid (8b8f3341-46da-40f5-a16f-41a91f5d25dd)]
+interface nsIWyciwygChannel : nsIChannel
+{
+ /**
+ * Append data to the cache entry; opens the cache entry if necessary.
+ */
+ void writeToCacheEntry(in AString aData);
+
+ /**
+ * Close the cache entry; subsequent writes have undefined behavior.
+ */
+ void closeCacheEntry(in nsresult reason);
+
+ /**
+ * Set the wyciwyg channels security info
+ */
+ void setSecurityInfo(in nsISupports aSecurityInfo);
+
+ /**
+ * Store and read a charset and charset source on the wyciwyg channel. These
+ * are opaque values to the channel; consumers who set them should know what
+ * they mean.
+ */
+ void setCharsetAndSource(in long aSource, in ACString aCharset);
+ /**
+ * The return value is the charset. Throws if either the charset or the
+ * source cannot be retrieved. This is guaranteed to return a nonzero source
+ * and a nonempty charset if it does not throw.
+ */
+ ACString getCharsetAndSource(out long aSource);
+};
diff --git a/netwerk/protocol/wyciwyg/nsWyciwyg.cpp b/netwerk/protocol/wyciwyg/nsWyciwyg.cpp
new file mode 100644
index 0000000000..edc716afdf
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwyg.cpp
@@ -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/. */
+
+#include "nsWyciwyg.h"
+#include "nscore.h"
+
+mozilla::LazyLogModule gWyciwygLog("nsWyciwygChannel");
+
+
diff --git a/netwerk/protocol/wyciwyg/nsWyciwyg.h b/netwerk/protocol/wyciwyg/nsWyciwyg.h
new file mode 100644
index 0000000000..48199a9b9f
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwyg.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 nsWyciwyg_h__
+#define nsWyciwyg_h__
+
+#include "mozilla/net/NeckoChild.h"
+
+// Get rid of chromium's LOG.
+#undef LOG
+
+#include "mozilla/Logging.h"
+
+//
+// Log module for HTTP Protocol logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=nsWyciwyg:5
+// set MOZ_LOG_FILE=wyciwyg.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file wyciwyg.log.
+//
+extern mozilla::LazyLogModule gWyciwygLog;
+
+// http logging
+#define LOG1(args) MOZ_LOG(gWyciwygLog, mozilla::LogLevel::Error, args)
+#define LOG2(args) MOZ_LOG(gWyciwygLog, mozilla::LogLevel::Warning, args)
+#define LOG3(args) MOZ_LOG(gWyciwygLog, mozilla::LogLevel::Info, args)
+#define LOG4(args) MOZ_LOG(gWyciwygLog, mozilla::LogLevel::Debug, args)
+#define LOG(args) LOG4(args)
+
+#define LOG1_ENABLED() MOZ_LOG_TEST(gWyciwygLog, mozilla::LogLevel::Error)
+#define LOG2_ENABLED() MOZ_LOG_TEST(gWyciwygLog, mozilla::LogLevel::Warning)
+#define LOG3_ENABLED() MOZ_LOG_TEST(gWyciwygLog, mozilla::LogLevel::Info)
+#define LOG4_ENABLED() MOZ_LOG_TEST(gWyciwygLog, mozilla::LogLevel::Debug)
+#define LOG_ENABLED() LOG4_ENABLED()
+
+#define WYCIWYG_TYPE "text/html"
+
+#endif // nsWyciwyg_h__
diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
new file mode 100644
index 0000000000..52949b7996
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
@@ -0,0 +1,808 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWyciwyg.h"
+#include "nsWyciwygChannel.h"
+#include "nsILoadGroup.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "LoadContextInfo.h"
+#include "nsICacheService.h" // only to initialize
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntry.h"
+#include "nsCharsetSource.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsIInputStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsIOutputStream.h"
+#include "nsIProgressEventSink.h"
+#include "nsIURI.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Unused.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+
+typedef mozilla::net::LoadContextInfo LoadContextInfo;
+
+// nsWyciwygChannel methods
+nsWyciwygChannel::nsWyciwygChannel()
+ : mMode(NONE),
+ mStatus(NS_OK),
+ mIsPending(false),
+ mNeedToWriteCharset(false),
+ mCharsetSource(kCharsetUninitialized),
+ mContentLength(-1),
+ mLoadFlags(LOAD_NORMAL),
+ mNeedToSetSecurityInfo(false)
+{
+}
+
+nsWyciwygChannel::~nsWyciwygChannel()
+{
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread(mLoadInfo.forget(), false);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsWyciwygChannel,
+ nsIChannel,
+ nsIRequest,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsICacheEntryOpenCallback,
+ nsIWyciwygChannel,
+ nsIPrivateBrowsingChannel)
+
+nsresult
+nsWyciwygChannel::Init(nsIURI* uri)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+
+ mURI = uri;
+ mOriginalURI = uri;
+
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetName(nsACString &aName)
+{
+ return mURI->GetSpec(aName);
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::IsPending(bool *aIsPending)
+{
+ *aIsPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetStatus(nsresult *aStatus)
+{
+ if (NS_SUCCEEDED(mStatus) && mPump)
+ mPump->GetStatus(aStatus);
+ else
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Cancel(nsresult status)
+{
+ mStatus = status;
+ if (mPump)
+ mPump->Cancel(status);
+ // else we're waiting for OnCacheEntryAvailable
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Suspend()
+{
+ if (mPump)
+ mPump->Suspend();
+ // XXX else, we'll ignore this ... and that's probably bad!
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Resume()
+{
+ if (mPump)
+ mPump->Resume();
+ // XXX else, we'll ignore this ... and that's probably bad!
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
+{
+ *aLoadGroup = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIProgressEventSink),
+ getter_AddRefs(mProgressSink));
+ UpdatePrivateBrowsing();
+ NS_GetOriginAttributes(this, mOriginAttributes);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetLoadFlags(uint32_t aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetLoadFlags(uint32_t * aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannel methods:
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetOriginalURI(nsIURI* *aURI)
+{
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetOriginalURI(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetURI(nsIURI* *aURI)
+{
+ *aURI = mURI;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetOwner(nsISupports **aOwner)
+{
+ NS_IF_ADDREF(*aOwner = mOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetOwner(nsISupports* aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks)
+{
+ *aCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ if (!CanSetCallbacks(aNotificationCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aNotificationCallbacks;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIProgressEventSink),
+ getter_AddRefs(mProgressSink));
+
+ UpdatePrivateBrowsing();
+ NS_GetOriginAttributes(this, mOriginAttributes);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentType(nsACString &aContentType)
+{
+ aContentType.AssignLiteral(WYCIWYG_TYPE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentType(const nsACString &aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ aContentCharset.AssignLiteral("UTF-16");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentLength(int64_t *aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Open(nsIInputStream ** aReturn)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("nsWyciwygChannel::AsyncOpen [this=%p]\n", this));
+ MOZ_ASSERT(mMode == NONE, "nsWyciwygChannel already open");
+
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(mMode == NONE, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_ARG_POINTER(listener);
+
+ mMode = READING;
+
+ // open a cache entry for this channel...
+ // mIsPending set to true since OnCacheEntryAvailable may be called
+ // synchronously and fails when mIsPending found false.
+ mIsPending = true;
+ nsresult rv = OpenCacheEntryForReading(mURI);
+ if (NS_FAILED(rv)) {
+ LOG(("nsWyciwygChannel::OpenCacheEntryForReading failed [rv=%x]\n", rv));
+ mIsPending = false;
+ return rv;
+ }
+
+ // There is no code path that would invoke the listener sooner than
+ // we get to this line in case OnCacheEntryAvailable is invoked
+ // synchronously.
+ mListener = listener;
+ mListenerContext = ctx;
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsIWyciwygChannel
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::WriteToCacheEntry(const nsAString &aData)
+{
+ LOG(("nsWyciwygChannel::WriteToCacheEntry [this=%p]", this));
+
+ nsresult rv;
+
+ if (mMode == READING) {
+ LOG(("nsWyciwygChannel::WriteToCacheEntry already open for reading"));
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mMode = WRITING;
+
+ if (!mCacheEntry) {
+ nsresult rv = OpenCacheEntryForWriting(mURI);
+ if (NS_FAILED(rv) || !mCacheEntry) {
+ LOG((" could not synchronously open cache entry for write!"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
+ rv = mCacheEntry->SetMetaDataElement("inhibit-persistent-caching", "1");
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (mNeedToSetSecurityInfo) {
+ mCacheEntry->SetSecurityInfo(mSecurityInfo);
+ mNeedToSetSecurityInfo = false;
+ }
+
+ if (mNeedToWriteCharset) {
+ WriteCharsetAndSourceToCache(mCharsetSource, mCharset);
+ mNeedToWriteCharset = false;
+ }
+
+ uint32_t out;
+ if (!mCacheOutputStream) {
+ // Get the outputstream from the cache entry.
+ rv = mCacheEntry->OpenOutputStream(0, getter_AddRefs(mCacheOutputStream));
+ if (NS_FAILED(rv)) return rv;
+
+ // Write out a Byte Order Mark, so that we'll know if the data is
+ // BE or LE when we go to read it.
+ char16_t bom = 0xFEFF;
+ rv = mCacheOutputStream->Write((char *)&bom, sizeof(bom), &out);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return mCacheOutputStream->Write((const char *)PromiseFlatString(aData).get(),
+ aData.Length() * sizeof(char16_t), &out);
+}
+
+
+NS_IMETHODIMP
+nsWyciwygChannel::CloseCacheEntry(nsresult reason)
+{
+ if (mCacheEntry) {
+ LOG(("nsWyciwygChannel::CloseCacheEntry [this=%p ]", this));
+ mCacheOutputStream = nullptr;
+ mCacheInputStream = nullptr;
+
+ if (NS_FAILED(reason)) {
+ mCacheEntry->AsyncDoom(nullptr);
+ }
+
+ mCacheEntry = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetSecurityInfo(nsISupports *aSecurityInfo)
+{
+ if (mMode == READING) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mSecurityInfo = aSecurityInfo;
+
+ if (mCacheEntry) {
+ return mCacheEntry->SetSecurityInfo(mSecurityInfo);
+ }
+
+ mNeedToSetSecurityInfo = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetCharsetAndSource(int32_t aSource,
+ const nsACString& aCharset)
+{
+ NS_ENSURE_ARG(!aCharset.IsEmpty());
+
+ if (mCacheEntry) {
+ WriteCharsetAndSourceToCache(mCharsetSource, mCharset);
+ } else {
+ MOZ_ASSERT(mMode != WRITING, "We must have an entry!");
+ if (mMode == READING) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mNeedToWriteCharset = true;
+ mCharsetSource = aSource;
+ mCharset = aCharset;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetCharsetAndSource(int32_t* aSource, nsACString& aCharset)
+{
+ MOZ_ASSERT(mMode == READING);
+
+ if (!mCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsXPIDLCString data;
+ mCacheEntry->GetMetaDataElement("charset", getter_Copies(data));
+
+ if (data.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsXPIDLCString sourceStr;
+ mCacheEntry->GetMetaDataElement("charset-source", getter_Copies(sourceStr));
+
+ int32_t source;
+ nsresult err;
+ source = sourceStr.ToInteger(&err);
+ if (NS_FAILED(err) || source == 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aSource = source;
+ aCharset = data;
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsICacheEntryOpenCallback
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
+ uint32_t* aResult)
+{
+ *aResult = ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnCacheEntryAvailable(nsICacheEntry *aCacheEntry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult aStatus)
+{
+ LOG(("nsWyciwygChannel::OnCacheEntryAvailable [this=%p entry=%p "
+ "new=%d status=%x]\n", this, aCacheEntry, aNew, aStatus));
+
+ MOZ_RELEASE_ASSERT(!aNew, "New entry must not be returned when flag "
+ "OPEN_READONLY is used!");
+
+ // if the channel's already fired onStopRequest,
+ // then we should ignore this event.
+ if (!mIsPending)
+ return NS_OK;
+
+ if (NS_SUCCEEDED(mStatus)) {
+ if (NS_SUCCEEDED(aStatus)) {
+ MOZ_ASSERT(aCacheEntry);
+ mCacheEntry = aCacheEntry;
+ nsresult rv = ReadFromCache();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ }
+ } else {
+ mStatus = aStatus;
+ }
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
+ // Since OnCacheEntryAvailable can be called directly from AsyncOpen
+ // we must dispatch.
+ NS_DispatchToCurrentThread(mozilla::NewRunnableMethod(
+ this, &nsWyciwygChannel::NotifyListener));
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsWyciwygChannel::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
+ nsIInputStream *input,
+ uint64_t offset, uint32_t count)
+{
+ LOG(("nsWyciwygChannel::OnDataAvailable [this=%p request=%x offset=%llu count=%u]\n",
+ this, request, offset, count));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ nsCOMPtr<nsISupports> listenerContext = mListenerContext;
+
+ if (listener) {
+ rv = listener->OnDataAvailable(this, listenerContext, input, offset, count);
+ } else {
+ MOZ_ASSERT(false, "We must have a listener!");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ // XXX handle 64-bit stuff for real
+ if (mProgressSink && NS_SUCCEEDED(rv)) {
+ mProgressSink->OnProgress(this, nullptr, offset + count, mContentLength);
+ }
+
+ return rv; // let the pump cancel on failure
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsIRequestObserver
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnStartRequest(nsIRequest *request, nsISupports *ctx)
+{
+ LOG(("nsWyciwygChannel::OnStartRequest [this=%p request=%x\n",
+ this, request));
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ nsCOMPtr<nsISupports> listenerContext = mListenerContext;
+
+ if (listener) {
+ return listener->OnStartRequest(this, listenerContext);
+ }
+
+ MOZ_ASSERT(false, "We must have a listener!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnStopRequest(nsIRequest *request, nsISupports *ctx, nsresult status)
+{
+ LOG(("nsWyciwygChannel::OnStopRequest [this=%p request=%x status=%d\n",
+ this, request, status));
+
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = status;
+
+ mIsPending = false;
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsISupports> listenerContext;
+ listener.swap(mListener);
+ listenerContext.swap(mListenerContext);
+
+ if (listener) {
+ listener->OnStopRequest(this, listenerContext, mStatus);
+ } else {
+ MOZ_ASSERT(false, "We must have a listener!");
+ }
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ CloseCacheEntry(mStatus);
+ mPump = nullptr;
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Helper functions
+//////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsWyciwygChannel::GetCacheStorage(nsICacheStorage **_retval)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorageService> cacheService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool anonymous = mLoadFlags & LOAD_ANONYMOUS;
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(mPrivateBrowsing);
+ RefPtr<LoadContextInfo> loadInfo = mozilla::net::GetLoadContextInfo(anonymous, mOriginAttributes);
+
+ if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
+ return cacheService->MemoryCacheStorage(loadInfo, _retval);
+ }
+
+ return cacheService->DiskCacheStorage(loadInfo, false, _retval);
+}
+
+nsresult
+nsWyciwygChannel::OpenCacheEntryForReading(nsIURI *aURI)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return cacheStorage->AsyncOpenURI(aURI, EmptyCString(),
+ nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::CHECK_MULTITHREADED,
+ this);
+}
+
+nsresult
+nsWyciwygChannel::OpenCacheEntryForWriting(nsIURI *aURI)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return cacheStorage->OpenTruncate(aURI, EmptyCString(),
+ getter_AddRefs(mCacheEntry));
+}
+
+nsresult
+nsWyciwygChannel::ReadFromCache()
+{
+ LOG(("nsWyciwygChannel::ReadFromCache [this=%p] ", this));
+
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
+ nsresult rv;
+
+ // Get the stored security info
+ mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+
+ nsAutoCString tmpStr;
+ rv = mCacheEntry->GetMetaDataElement("inhibit-persistent-caching",
+ getter_Copies(tmpStr));
+ if (NS_SUCCEEDED(rv) && tmpStr.EqualsLiteral("1"))
+ mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+
+ // Get a transport to the cached data...
+ rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(mCacheInputStream));
+ if (NS_FAILED(rv))
+ return rv;
+ NS_ENSURE_TRUE(mCacheInputStream, NS_ERROR_UNEXPECTED);
+
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), mCacheInputStream);
+ if (NS_FAILED(rv)) return rv;
+
+ // Pump the cache data downstream
+ return mPump->AsyncRead(this, nullptr);
+}
+
+void
+nsWyciwygChannel::WriteCharsetAndSourceToCache(int32_t aSource,
+ const nsCString& aCharset)
+{
+ NS_PRECONDITION(mCacheEntry, "Better have cache entry!");
+
+ mCacheEntry->SetMetaDataElement("charset", aCharset.get());
+
+ nsAutoCString source;
+ source.AppendInt(aSource);
+ mCacheEntry->SetMetaDataElement("charset-source", source.get());
+}
+
+void
+nsWyciwygChannel::NotifyListener()
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsISupports> listenerContext;
+
+ listener.swap(mListener);
+ listenerContext.swap(mListenerContext);
+
+ if (listener) {
+ listener->OnStartRequest(this, listenerContext);
+ mIsPending = false;
+ listener->OnStopRequest(this, listenerContext, mStatus);
+ } else {
+ MOZ_ASSERT(false, "We must have the listener!");
+ mIsPending = false;
+ }
+
+ CloseCacheEntry(mStatus);
+
+ // Remove ourselves from the load group.
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+}
+
+// vim: ts=2 sw=2
diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.h b/netwerk/protocol/wyciwyg/nsWyciwygChannel.h
new file mode 100644
index 0000000000..26326e2a49
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.h
@@ -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/. */
+
+#ifndef nsWyciwygChannel_h___
+#define nsWyciwygChannel_h___
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+#include "nsILoadInfo.h"
+#include "nsIWyciwygChannel.h"
+#include "nsIStreamListener.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "PrivateBrowsingChannel.h"
+#include "mozilla/BasePrincipal.h"
+
+class nsICacheEntry;
+class nsICacheStorage;
+class nsIInputStream;
+class nsIInputStreamPump;
+class nsILoadGroup;
+class nsIOutputStream;
+class nsIProgressEventSink;
+class nsIURI;
+
+extern mozilla::LazyLogModule gWyciwygLog;
+
+//-----------------------------------------------------------------------------
+
+class nsWyciwygChannel final: public nsIWyciwygChannel,
+ public nsIStreamListener,
+ public nsICacheEntryOpenCallback,
+ public mozilla::net::PrivateBrowsingChannel<nsWyciwygChannel>
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIWYCIWYGCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+
+ // nsWyciwygChannel methods:
+ nsWyciwygChannel();
+
+ nsresult Init(nsIURI *uri);
+
+protected:
+ virtual ~nsWyciwygChannel();
+
+ nsresult ReadFromCache();
+ nsresult EnsureWriteCacheEntry();
+ nsresult GetCacheStorage(nsICacheStorage **_retval);
+ nsresult OpenCacheEntryForReading(nsIURI *aURI);
+ nsresult OpenCacheEntryForWriting(nsIURI *aURI);
+
+ void WriteCharsetAndSourceToCache(int32_t aSource,
+ const nsCString& aCharset);
+
+ void NotifyListener();
+
+ friend class mozilla::net::PrivateBrowsingChannel<nsWyciwygChannel>;
+
+ enum EMode {
+ NONE,
+ WRITING,
+ READING
+ };
+
+ EMode mMode;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mNeedToWriteCharset;
+ int32_t mCharsetSource;
+ nsCString mCharset;
+ int64_t mContentLength;
+ uint32_t mLoadFlags;
+ mozilla::NeckoOriginAttributes mOriginAttributes;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+
+ // reuse as much of this channel implementation as we can
+ nsCOMPtr<nsIInputStreamPump> mPump;
+
+ // Cache related stuff
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+ nsCOMPtr<nsIOutputStream> mCacheOutputStream;
+ nsCOMPtr<nsIInputStream> mCacheInputStream;
+
+ bool mNeedToSetSecurityInfo;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+};
+
+/**
+ * Casting nsWyciwygChannel to nsISupports is ambiguous.
+ * This method handles that.
+ */
+inline nsISupports*
+ToSupports(nsWyciwygChannel* p)
+{
+ return NS_ISUPPORTS_CAST(nsIStreamListener*, p);
+}
+
+#endif /* nsWyciwygChannel_h___ */
diff --git a/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp
new file mode 100644
index 0000000000..4e1f7c22e6
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp
@@ -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 "nsWyciwyg.h"
+#include "nsWyciwygChannel.h"
+#include "nsWyciwygProtocolHandler.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "plstr.h"
+#include "nsIObserverService.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "nsIURI.h"
+
+#include "mozilla/net/NeckoChild.h"
+
+using namespace mozilla::net;
+#include "mozilla/net/WyciwygChannelChild.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsWyciwygProtocolHandler::nsWyciwygProtocolHandler()
+{
+ LOG(("Creating nsWyciwygProtocolHandler [this=%p].\n", this));
+}
+
+nsWyciwygProtocolHandler::~nsWyciwygProtocolHandler()
+{
+ LOG(("Deleting nsWyciwygProtocolHandler [this=%p]\n", this));
+}
+
+NS_IMPL_ISUPPORTS(nsWyciwygProtocolHandler,
+ nsIProtocolHandler)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::GetScheme(nsACString &result)
+{
+ result = "wyciwyg";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignored
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> url = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ url.forget(result);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::NewChannel2(nsIURI* url,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ if (mozilla::net::IsNeckoChild())
+ mozilla::net::NeckoChild::InitNeckoChild();
+
+ NS_ENSURE_ARG_POINTER(url);
+ nsresult rv;
+
+ nsCOMPtr<nsIWyciwygChannel> channel;
+ if (IsNeckoChild()) {
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+
+ WyciwygChannelChild *wcc = static_cast<WyciwygChannelChild *>(
+ gNeckoChild->SendPWyciwygChannelConstructor());
+ if (!wcc)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ channel = wcc;
+ rv = wcc->Init(url);
+ if (NS_FAILED(rv))
+ PWyciwygChannelChild::Send__delete__(wcc);
+ } else
+ {
+ // If original channel used https, make sure PSM is initialized
+ // (this may be first channel to load during a session restore)
+ nsAutoCString path;
+ rv = url->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t slashIndex = path.FindChar('/', 2);
+ if (slashIndex == kNotFound)
+ return NS_ERROR_FAILURE;
+ if (path.Length() < (uint32_t)slashIndex + 1 + 5)
+ return NS_ERROR_FAILURE;
+ if (!PL_strncasecmp(path.get() + slashIndex + 1, "https", 5))
+ net_EnsurePSMInit();
+
+ nsWyciwygChannel *wc = new nsWyciwygChannel();
+ channel = wc;
+ rv = wc->Init(url);
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ channel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::NewChannel(nsIURI* url, nsIChannel* *result)
+{
+ return NewChannel2(url, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ // Should this be an an nsINestedURI? We don't really want random webpages
+ // loading these URIs...
+
+ // Note that using URI_INHERITS_SECURITY_CONTEXT here is OK -- untrusted code
+ // is not allowed to link to wyciwyg URIs and users shouldn't be able to get
+ // at them, and nsDocShell::InternalLoad forbids non-history loads of these
+ // URIs. And when loading from history we end up using the principal from
+ // the history entry, which we put there ourselves, so all is ok.
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+ URI_INHERITS_SECURITY_CONTEXT;
+ return NS_OK;
+}
diff --git a/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.h b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.h
new file mode 100644
index 0000000000..d3dbd5eadc
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.h
@@ -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/. */
+
+#ifndef nsWyciwygProtocolHandler_h___
+#define nsWyciwygProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+
+class nsWyciwygProtocolHandler : public nsIProtocolHandler
+{
+ virtual ~nsWyciwygProtocolHandler();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ nsWyciwygProtocolHandler();
+};
+
+#endif /* nsWyciwygProtocolHandler_h___ */
diff --git a/netwerk/sctp/datachannel/DataChannel.cpp b/netwerk/sctp/datachannel/DataChannel.cpp
new file mode 100644
index 0000000000..bf7790f51c
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -0,0 +1,2661 @@
+/* -*- 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 "DataChannelLog.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "mozilla/Services.h"
+#include "mozilla/Sprintf.h"
+#include "nsProxyRelease.h"
+#include "nsThread.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#ifdef MOZ_PEERCONNECTION
+#include "mtransport/runnable_utils.h"
+#endif
+
+#define DATACHANNEL_LOG(args) LOG(args)
+#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
+
+static bool sctp_initialized;
+
+namespace mozilla {
+
+LazyLogModule gDataChannelLog("DataChannel");
+static LazyLogModule gSCTPLog("SCTP");
+
+class DataChannelShutdown : public nsIObserver
+{
+public:
+ // This needs to be tied to some form object that is guaranteed to be
+ // around (singleton likely) unless we want to shutdown sctp whenever
+ // we're not using it (and in which case we'd keep a refcnt'd object
+ // ref'd by each DataChannelConnection to release the SCTP usrlib via
+ // sctp_finish). Right now, the single instance of this class is
+ // owned by the observer service.
+
+ NS_DECL_ISUPPORTS
+
+ DataChannelShutdown() {}
+
+ void Init()
+ {
+ 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;
+ }
+
+private:
+ // The only instance of DataChannelShutdown is owned by the observer
+ // service, so there is no need to call RemoveObserver here.
+ virtual ~DataChannelShutdown() = default;
+
+public:
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (strcmp(aTopic, "xpcom-will-shutdown") == 0) {
+ LOG(("Shutting down SCTP"));
+ if (sctp_initialized) {
+ usrsctp_finish();
+ sctp_initialized = false;
+ }
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = observerService->RemoveObserver(this,
+ "xpcom-will-shutdown");
+ MOZ_ASSERT(rv == NS_OK);
+ (void) rv;
+ }
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(DataChannelShutdown, nsIObserver);
+
+BufferedMsg::BufferedMsg(struct sctp_sendv_spa &spa, const char *data,
+ size_t length) : mLength(length)
+{
+ mSpa = new sctp_sendv_spa;
+ *mSpa = spa;
+ auto *tmp = new char[length]; // infallible malloc!
+ memcpy(tmp, data, length);
+ mData = tmp;
+}
+
+BufferedMsg::~BufferedMsg()
+{
+ delete mSpa;
+ 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)
+{
+ DataChannelConnection *connection = static_cast<DataChannelConnection*>(ulp_info);
+ return connection->ReceiveCallback(sock, data, datalen, rcv, flags);
+}
+
+static
+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]);
+ DataChannelConnection *connection =
+ reinterpret_cast<DataChannelConnection *>(sconn->sconn_addr);
+ 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)
+{
+ DataChannelConnection *connection = GetConnectionFromSocket(sock);
+ if (connection) {
+ LOG(("SendDeferred()"));
+ connection->SendDeferredMessages();
+ } else {
+ LOG(("Can't find connection for socket %p", sock));
+ }
+ return 0;
+}
+
+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
+ PR_LogPrint("%s", buffer);
+ }
+ va_end(ap);
+ }
+}
+
+DataChannelConnection::DataChannelConnection(DataConnectionListener *listener) :
+ mLock("netwerk::sctp::DataChannelConnection")
+{
+ mState = CLOSED;
+ mSocket = nullptr;
+ mMasterSocket = nullptr;
+ mListener = listener;
+ mLocalPort = 0;
+ mRemotePort = 0;
+ LOG(("Constructor DataChannelConnection=%p, listener=%p", this, mListener.get()));
+ mInternalIOThread = nullptr;
+}
+
+DataChannelConnection::~DataChannelConnection()
+{
+ LOG(("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);
+
+ // Already disconnected from sigslot/mTransportFlow
+ // TransportFlows must be released from the STS thread
+ if (!IsSTSThread()) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ if (mTransportFlow) {
+ ASSERT_WEBRTC(mSTS);
+ NS_ProxyRelease(mSTS, mTransportFlow.forget());
+ }
+
+ if (mInternalIOThread) {
+ // Avoid spinning the event thread from here (which if we're mainthread
+ // is in the event loop already)
+ NS_DispatchToMainThread(WrapRunnable(nsCOMPtr<nsIThread>(mInternalIOThread),
+ &nsIThread::Shutdown),
+ NS_DISPATCH_NORMAL);
+ }
+ } 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.
+ LOG(("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());
+ // Must do this in Destroy() since we may then delete this object.
+ // Do this before dispatching to create a consistent ordering of calls to
+ // the SCTP stack.
+ if (mUsingDtls) {
+ usrsctp_deregister_address(static_cast<void *>(this));
+ LOG(("Deregistered %p from the SCTP stack.", static_cast<void *>(this)));
+ }
+
+ // 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);
+
+ disconnect_all();
+}
+
+bool
+DataChannelConnection::Init(unsigned short aPort, uint16_t aNumStreams, bool aUsingDtls)
+{
+ struct sctp_initmsg initmsg;
+ struct sctp_udpencaps encaps;
+ 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_SEND_FAILED_EVENT,
+ SCTP_STREAM_RESET_EVENT,
+ SCTP_STREAM_CHANGE_EVENT};
+ {
+ ASSERT_WEBRTC(NS_IsMainThread());
+
+ // MutexAutoLock lock(mLock); Not needed since we're on mainthread always
+ if (!sctp_initialized) {
+ if (aUsingDtls) {
+ LOG(("sctp_init(DTLS)"));
+#ifdef MOZ_PEERCONNECTION
+ usrsctp_init(0,
+ DataChannelConnection::SctpDtlsOutput,
+ debug_printf
+ );
+#else
+ NS_ASSERTION(!aUsingDtls, "Trying to use SCTP/DTLS without mtransport");
+#endif
+ } else {
+ LOG(("sctp_init(%u)", aPort));
+ usrsctp_init(aPort,
+ nullptr,
+ debug_printf
+ );
+ }
+
+ // Set logging to SCTP:LogLevel::Debug to get SCTP debugs
+ if (MOZ_LOG_TEST(gSCTPLog, LogLevel::Debug)) {
+ usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+ }
+
+ usrsctp_sysctl_set_sctp_blackhole(2);
+ // ECN is currently not supported by the Firefox code
+ usrsctp_sysctl_set_sctp_ecn_enable(0);
+ sctp_initialized = true;
+
+ RefPtr<DataChannelShutdown> shutdown = new DataChannelShutdown();
+ shutdown->Init();
+ }
+ }
+
+ // 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(
+ aUsingDtls ? AF_CONN : AF_INET,
+ SOCK_STREAM, IPPROTO_SCTP, receive_cb, threshold_event,
+ usrsctp_sysctl_get_sctp_sendspace() / 2, this)) == nullptr) {
+ return false;
+ }
+
+ // 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) {
+ LOG(("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) {
+ LOG(("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.
+ {
+ uint32_t on = 1;
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_REUSE_PORT,
+ (const void *)&on, (socklen_t)sizeof(on)) < 0) {
+ LOG(("Couldn't set SCTP_REUSE_PORT on SCTP socket"));
+ }
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_NODELAY,
+ (const void *)&on, (socklen_t)sizeof(on)) < 0) {
+ LOG(("Couldn't set SCTP_NODELAY on SCTP socket"));
+ }
+ }
+
+ if (!aUsingDtls) {
+ memset(&encaps, 0, sizeof(encaps));
+ encaps.sue_address.ss_family = AF_INET;
+ encaps.sue_port = htons(aPort);
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT,
+ (const void*)&encaps,
+ (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+ LOG(("*** failed encaps errno %d", errno));
+ goto error_cleanup;
+ }
+ LOG(("SCTP encapsulation local port %d", aPort));
+ }
+
+ 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) {
+ LOG(("*** 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) {
+ LOG(("*** failed setsockopt SCTP_EVENT errno %d", errno));
+ goto error_cleanup;
+ }
+ }
+
+ // Update number of streams
+ mStreams.AppendElements(aNumStreams);
+ for (uint32_t i = 0; i < aNumStreams; ++i) {
+ mStreams[i] = nullptr;
+ }
+ memset(&initmsg, 0, sizeof(initmsg));
+ len = sizeof(initmsg);
+ if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, &len) < 0) {
+ LOG(("*** failed getsockopt SCTP_INITMSG"));
+ goto error_cleanup;
+ }
+ LOG(("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) {
+ LOG(("*** failed setsockopt SCTP_INITMSG, errno %d", errno));
+ goto error_cleanup;
+ }
+
+ mSocket = nullptr;
+ if (aUsingDtls) {
+ mUsingDtls = true;
+ usrsctp_register_address(static_cast<void *>(this));
+ LOG(("Registered %p within the SCTP stack.", static_cast<void *>(this)));
+ } else {
+ mUsingDtls = false;
+ }
+ return true;
+
+error_cleanup:
+ usrsctp_close(mMasterSocket);
+ mMasterSocket = nullptr;
+ mUsingDtls = false;
+ return false;
+}
+
+#ifdef MOZ_PEERCONNECTION
+void
+DataChannelConnection::SetEvenOdd()
+{
+ ASSERT_WEBRTC(IsSTSThread());
+
+ TransportLayerDtls *dtls = static_cast<TransportLayerDtls *>(
+ mTransportFlow->GetLayer(TransportLayerDtls::ID()));
+ MOZ_ASSERT(dtls); // DTLS is mandatory
+ mAllocateEven = (dtls->role() == TransportLayerDtls::CLIENT);
+}
+
+bool
+DataChannelConnection::ConnectViaTransportFlow(TransportFlow *aFlow, uint16_t localport, uint16_t remoteport)
+{
+ LOG(("Connect DTLS local %u, remote %u", localport, remoteport));
+
+ NS_PRECONDITION(mMasterSocket, "SCTP wasn't initialized before ConnectViaTransportFlow!");
+ NS_ENSURE_TRUE(aFlow, false);
+
+ mTransportFlow = aFlow;
+ mLocalPort = localport;
+ mRemotePort = remoteport;
+ mState = CONNECTING;
+
+ RUN_ON_THREAD(mSTS, WrapRunnable(RefPtr<DataChannelConnection>(this),
+ &DataChannelConnection::SetSignals),
+ NS_DISPATCH_NORMAL);
+ return true;
+}
+
+void
+DataChannelConnection::SetSignals()
+{
+ ASSERT_WEBRTC(IsSTSThread());
+ ASSERT_WEBRTC(mTransportFlow);
+ LOG(("Setting transport signals, state: %d", mTransportFlow->state()));
+ mTransportFlow->SignalPacketReceived.connect(this, &DataChannelConnection::SctpDtlsInput);
+ // SignalStateChange() doesn't call you with the initial state
+ mTransportFlow->SignalStateChange.connect(this, &DataChannelConnection::CompleteConnect);
+ CompleteConnect(mTransportFlow, mTransportFlow->state());
+}
+
+void
+DataChannelConnection::CompleteConnect(TransportFlow *flow, TransportLayer::State state)
+{
+ LOG(("Data transport state: %d", state));
+ MutexAutoLock lock(mLock);
+ ASSERT_WEBRTC(IsSTSThread());
+ // We should abort connection on TS_ERROR.
+ // Note however that the association will also fail (perhaps with a delay) and
+ // notify us in that way
+ if (state != TransportLayer::TS_OPEN || !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 = static_cast<void *>(this);
+
+ LOG(("Calling usrsctp_bind"));
+ int r = usrsctp_bind(mMasterSocket, reinterpret_cast<struct sockaddr *>(&addr),
+ sizeof(addr));
+ if (r < 0) {
+ LOG(("usrsctp_bind failed: %d", r));
+ } else {
+ // This is the remote addr
+ addr.sconn_port = htons(mRemotePort);
+ LOG(("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) {
+ LOG(("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) {
+ LOG(("usrsctp_getsockopt failed: %d", r));
+ } else {
+ LOG(("usrsctp: PMTUD disabled, MTU set to %u", paddrparams.spp_pathmtu));
+ }
+ }
+ }
+ if (r < 0) {
+ if (errno == EINPROGRESS) {
+ // non-blocking
+ return;
+ } else {
+ LOG(("usrsctp_connect failed: %d", errno));
+ mState = CLOSED;
+ }
+ } else {
+ // We set Even/Odd and fire ON_CONNECTION via SCTP_COMM_UP when we get that
+ // This also avoids issues with calling TransportFlow stuff on Mainthread
+ return;
+ }
+ }
+ // Note: currently this doesn't actually notify the application
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CONNECTION,
+ this)));
+ return;
+}
+
+// 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
+ nsDeque temp;
+ DataChannel *temp_channel; // really already_AddRefed<>
+ while (nullptr != (temp_channel = static_cast<DataChannel *>(mPending.PopFront()))) {
+ temp.Push(static_cast<void *>(temp_channel));
+ }
+
+ RefPtr<DataChannel> channel;
+ // All these entries have an AddRef(); make that explicit now via the dont_AddRef()
+ while (nullptr != (channel = dont_AddRef(static_cast<DataChannel *>(temp.PopFront())))) {
+ if (channel->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) {
+ LOG(("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(TransportFlow *flow,
+ const unsigned char *data, size_t len)
+{
+ if (MOZ_LOG_TEST(gSCTPLog, LogLevel::Debug)) {
+ char *buf;
+
+ if ((buf = usrsctp_dumppacket((void *)data, len, SCTP_DUMP_INBOUND)) != nullptr) {
+ PR_LogPrint("%s", buf);
+ usrsctp_freedumpbuffer(buf);
+ }
+ }
+ // Pass the data to SCTP
+ usrsctp_conninput(static_cast<void *>(this), data, len, 0);
+}
+
+int
+DataChannelConnection::SendPacket(unsigned char data[], size_t len, bool release)
+{
+ //LOG(("%p: SCTP/DTLS sent %ld bytes", this, len));
+ int res = mTransportFlow->SendPacket(data, len) < 0 ? 1 : 0;
+ if (release)
+ delete [] data;
+ return res;
+}
+
+/* static */
+int
+DataChannelConnection::SctpDtlsOutput(void *addr, void *buffer, size_t length,
+ uint8_t tos, uint8_t set_df)
+{
+ DataChannelConnection *peer = static_cast<DataChannelConnection *>(addr);
+ int res;
+
+ if (MOZ_LOG_TEST(gSCTPLog, LogLevel::Debug)) {
+ char *buf;
+
+ if ((buf = usrsctp_dumppacket(buffer, length, SCTP_DUMP_OUTBOUND)) != nullptr) {
+ PR_LogPrint("%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.
+ if ((false /*peer->IsSTSThread()*/)) {
+ res = peer->SendPacket(static_cast<unsigned char *>(buffer), length, false);
+ } else {
+ auto *data = new unsigned char[length];
+ memcpy(data, buffer, length);
+ // Commented out since we have to Dispatch SendPacket to avoid deadlock"
+ // res = -1;
+
+ // XXX It might be worthwhile to add an assertion against the thread
+ // somehow getting into the DataChannel/SCTP code again, as
+ // DISPATCH_SYNC is not fully blocking. This may be tricky, as it
+ // needs to be a per-thread check, not a global.
+ peer->mSTS->Dispatch(WrapRunnable(
+ RefPtr<DataChannelConnection>(peer),
+ &DataChannelConnection::SendPacket, data, length, true),
+ NS_DISPATCH_NORMAL);
+ res = 0; // cheat! Packets can always be dropped later anyways
+ }
+ return res;
+}
+#endif
+
+#ifdef ALLOW_DIRECT_SCTP_LISTEN_CONNECT
+// listen for incoming associations
+// Blocks! - Don't call this from main thread!
+
+#error This code will not work as-is since SetEvenOdd() runs on Mainthread
+
+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);
+ LOG(("Waiting for connections on port %u", ntohs(addr.sin_port)));
+ mState = CONNECTING;
+ if (usrsctp_bind(mMasterSocket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(struct sockaddr_in)) < 0) {
+ LOG(("***Failed userspace_bind"));
+ return false;
+ }
+ if (usrsctp_listen(mMasterSocket, 1) < 0) {
+ LOG(("***Failed userspace_listen"));
+ return false;
+ }
+
+ LOG(("Accepting connection"));
+ addr_len = 0;
+ if ((mSocket = usrsctp_accept(mMasterSocket, nullptr, &addr_len)) == nullptr) {
+ LOG(("***Failed accept"));
+ return false;
+ }
+ mState = 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) {
+ LOG(("Couldn't set SO_LINGER on SCTP socket"));
+ }
+
+ SetEvenOdd();
+
+ // Notify Connection open
+ // XXX We need to make sure connection sticks around until the message is delivered
+ LOG(("%s: sending ON_CONNECTION for %p", __FUNCTION__, this));
+ NS_DispatchToMainThread(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 */
+ LOG(("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);
+ mState = 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) {
+ LOG(("*** 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) {
+ LOG(("*** Failed userspace_connect"));
+ return false;
+ }
+ } else {
+ LOG(("*** 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) {
+ LOG(("*** 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) {
+ LOG(("*** Failed userspace_connect"));
+ return false;
+ }
+ } else {
+ LOG(("*** Illegal destination address."));
+ }
+ }
+#endif
+
+ mSocket = mMasterSocket;
+
+ LOG(("connect() succeeded! Entering connected mode"));
+ mState = OPEN;
+
+ SetEvenOdd();
+
+ // Notify Connection open
+ // XXX We need to make sure connection sticks around until the message is delivered
+ LOG(("%s: sending ON_CONNECTION for %p", __FUNCTION__, this));
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CONNECTION,
+ this, (DataChannel *) nullptr)));
+ return true;
+}
+#endif
+
+DataChannel *
+DataChannelConnection::FindChannelByStream(uint16_t stream)
+{
+ return mStreams.SafeElementAt(stream);
+}
+
+uint16_t
+DataChannelConnection::FindFreeStream()
+{
+ uint32_t i, j, limit;
+
+ limit = mStreams.Length();
+ if (limit > MAX_NUM_STREAMS)
+ limit = MAX_NUM_STREAMS;
+
+ for (i = (mAllocateEven ? 0 : 1); i < limit; i += 2) {
+ if (!mStreams[i]) {
+ // Verify it's not still in the process of closing
+ for (j = 0; j < mStreamsResetting.Length(); ++j) {
+ if (mStreamsResetting[j] == i) {
+ break;
+ }
+ }
+ if (j == mStreamsResetting.Length())
+ break;
+ }
+ }
+ if (i >= limit) {
+ return INVALID_STREAM;
+ }
+ return i;
+}
+
+bool
+DataChannelConnection::RequestMoreStreams(int32_t aNeeded)
+{
+ struct sctp_status status;
+ struct sctp_add_streams sas;
+ uint32_t outStreamsNeeded;
+ socklen_t len;
+
+ if (aNeeded + mStreams.Length() > MAX_NUM_STREAMS) {
+ aNeeded = MAX_NUM_STREAMS - mStreams.Length();
+ }
+ if (aNeeded <= 0) {
+ return false;
+ }
+
+ len = (socklen_t)sizeof(struct sctp_status);
+ if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_STATUS, &status, &len) < 0) {
+ LOG(("***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) {
+ LOG(("Already have %u output streams", outStreamsNeeded));
+ return true;
+ }
+
+ LOG(("***failed: setsockopt ADD errno=%d", errno));
+ return false;
+ }
+ LOG(("Requested %u more streams", outStreamsNeeded));
+ // We add to mStreams when we get a SCTP_STREAM_CHANGE_EVENT and the
+ // values are larger than mStreams.Length()
+ return true;
+}
+
+int32_t
+DataChannelConnection::SendControlMessage(void *msg, uint32_t len, uint16_t stream)
+{
+ struct sctp_sndinfo sndinfo;
+
+ // Note: Main-thread IO, but doesn't block
+ memset(&sndinfo, 0, sizeof(struct sctp_sndinfo));
+ sndinfo.snd_sid = stream;
+ sndinfo.snd_ppid = htonl(DATA_CHANNEL_PPID_CONTROL);
+ if (usrsctp_sendv(mSocket, msg, len, nullptr, 0,
+ &sndinfo, (socklen_t)sizeof(struct sctp_sndinfo),
+ SCTP_SENDV_SNDINFO, 0) < 0) {
+ //LOG(("***failed: sctp_sendv")); don't log because errno is a return!
+ return (0);
+ }
+ return (1);
+}
+
+int32_t
+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(&ack, sizeof(ack), stream);
+}
+
+int32_t
+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;
+ struct rtcweb_datachannel_open_request *req =
+ (struct rtcweb_datachannel_open_request*) moz_xmalloc(req_size);
+
+ memset(req, 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:
+ // FIX! need to set errno! Or make all these SendXxxx() funcs return 0 or errno!
+ free(req);
+ return (0);
+ }
+ 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);
+
+ int32_t result = SendControlMessage(req, req_size, stream);
+
+ free(req);
+ return result;
+}
+
+// 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 or not
+bool
+DataChannelConnection::SendDeferredMessages()
+{
+ uint32_t i;
+ RefPtr<DataChannel> channel; // we may null out the refs to this
+ bool still_blocked = false;
+
+ // This may block while something is modifying channels, but should not block for IO
+ MutexAutoLock lock(mLock);
+
+ // XXX For total fairness, on a still_blocked we'd start next time at the
+ // same index. Sorry, not going to bother for now.
+ for (i = 0; i < mStreams.Length(); ++i) {
+ channel = mStreams[i];
+ if (!channel)
+ continue;
+
+ // Only one of these should be set....
+ if (channel->mFlags & DATA_CHANNEL_FLAGS_SEND_REQ) {
+ if (SendOpenRequestMessage(channel->mLabel, channel->mProtocol,
+ channel->mStream,
+ channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED,
+ channel->mPrPolicy, channel->mPrValue)) {
+ channel->mFlags &= ~DATA_CHANNEL_FLAGS_SEND_REQ;
+
+ channel->mState = OPEN;
+ channel->mReady = true;
+ LOG(("%s: sending ON_CHANNEL_OPEN for %p", __FUNCTION__, channel.get()));
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_OPEN, this,
+ channel)));
+ } else {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ still_blocked = true;
+ } else {
+ // Close the channel, inform the user
+ mStreams[channel->mStream] = nullptr;
+ channel->mState = CLOSED;
+ // Don't need to reset; we didn't open it
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_CLOSED, this,
+ channel)));
+ }
+ }
+ }
+ if (still_blocked)
+ break;
+
+ if (channel->mFlags & DATA_CHANNEL_FLAGS_SEND_ACK) {
+ if (SendOpenAckMessage(channel->mStream)) {
+ channel->mFlags &= ~DATA_CHANNEL_FLAGS_SEND_ACK;
+ } else {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ still_blocked = true;
+ } else {
+ // Close the channel, inform the user
+ CloseInt(channel);
+ // XXX send error via DataChannelOnMessageAvailable (bug 843625)
+ }
+ }
+ }
+ if (still_blocked)
+ break;
+
+ if (channel->mFlags & DATA_CHANNEL_FLAGS_SEND_DATA) {
+ bool failed_send = false;
+ int32_t result;
+
+ if (channel->mState == CLOSED || channel->mState == CLOSING) {
+ channel->mBufferedData.Clear();
+ }
+
+ uint32_t buffered_amount = channel->GetBufferedAmountLocked();
+ uint32_t threshold = channel->GetBufferedAmountLowThreshold();
+ bool was_over_threshold = buffered_amount >= threshold;
+
+ while (!channel->mBufferedData.IsEmpty() &&
+ !failed_send) {
+ struct sctp_sendv_spa *spa = channel->mBufferedData[0]->mSpa;
+ const char *data = channel->mBufferedData[0]->mData;
+ size_t len = channel->mBufferedData[0]->mLength;
+
+ // SCTP will return EMSGSIZE if the message is bigger than the buffer
+ // size (or EAGAIN if there isn't space)
+ if ((result = usrsctp_sendv(mSocket, data, len,
+ nullptr, 0,
+ (void *)spa, (socklen_t)sizeof(struct sctp_sendv_spa),
+ SCTP_SENDV_SPA,
+ 0)) < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ // leave queued for resend
+ failed_send = true;
+ LOG(("queue full again when resending %d bytes (%d)", len, result));
+ } else {
+ LOG(("error %d re-sending string", errno));
+ failed_send = true;
+ }
+ } else {
+ LOG(("Resent buffer of %d bytes (%d)", len, result));
+ // In theory this could underflow if >4GB was buffered and re
+ // truncated in GetBufferedAmount(), but this won't cause any problems.
+ buffered_amount -= channel->mBufferedData[0]->mLength;
+ channel->mBufferedData.RemoveElementAt(0);
+ // can never fire with default threshold of 0
+ if (was_over_threshold && buffered_amount < threshold) {
+ LOG(("%s: sending BUFFER_LOW_THRESHOLD for %s/%s: %u", __FUNCTION__,
+ channel->mLabel.get(), channel->mProtocol.get(), channel->mStream));
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::BUFFER_LOW_THRESHOLD,
+ this, channel)));
+ was_over_threshold = false;
+ }
+ if (buffered_amount == 0) {
+ // buffered-to-not-buffered transition; tell the DOM code in case this makes it
+ // available for GC
+ LOG(("%s: sending NO_LONGER_BUFFERED for %s/%s: %u", __FUNCTION__,
+ channel->mLabel.get(), channel->mProtocol.get(), channel->mStream));
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::NO_LONGER_BUFFERED,
+ this, channel)));
+ }
+ }
+ }
+ if (channel->mBufferedData.IsEmpty())
+ channel->mFlags &= ~DATA_CHANNEL_FLAGS_SEND_DATA;
+ else
+ still_blocked = true;
+ }
+ if (still_blocked)
+ break;
+ }
+
+ return still_blocked;
+}
+
+void
+DataChannelConnection::HandleOpenRequestMessage(const struct rtcweb_datachannel_open_request *req,
+ size_t length,
+ uint16_t stream)
+{
+ RefPtr<DataChannel> channel;
+ uint32_t prValue;
+ uint16_t prPolicy;
+ uint32_t flags;
+
+ mLock.AssertCurrentThreadOwns();
+
+ if (length != (sizeof(*req) - 1) + ntohs(req->label_length) + ntohs(req->protocol_length)) {
+ LOG(("%s: Inconsistent length: %u, should be %u", __FUNCTION__, length,
+ (sizeof(*req) - 1) + ntohs(req->label_length) + ntohs(req->protocol_length)));
+ if (length < (sizeof(*req) - 1) + ntohs(req->label_length) + ntohs(req->protocol_length))
+ return;
+ }
+
+ LOG(("%s: length %u, sizeof(*req) = %u", __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:
+ LOG(("Unknown channel type", req->channel_type));
+ /* XXX error handling */
+ return;
+ }
+ prValue = ntohl(req->reliability_param);
+ flags = (req->channel_type & 0x80) ? DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED : 0;
+
+ if ((channel = FindChannelByStream(stream))) {
+ if (!(channel->mFlags & DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED)) {
+ LOG(("ERROR: HandleOpenRequestMessage: channel for stream %u is in state %d instead of CLOSED.",
+ stream, channel->mState));
+ /* XXX: some error handling */
+ } else {
+ LOG(("Open for externally negotiated channel %u", stream));
+ // XXX should also check protocol, maybe label
+ if (prPolicy != channel->mPrPolicy ||
+ prValue != channel->mPrValue ||
+ flags != (channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED))
+ {
+ LOG(("WARNING: external negotiation mismatch with OpenRequest:"
+ "channel %u, policy %u/%u, value %u/%u, flags %x/%x",
+ stream, prPolicy, channel->mPrPolicy,
+ prValue, channel->mPrValue, flags, channel->mFlags));
+ }
+ }
+ return;
+ }
+ if (stream >= mStreams.Length()) {
+ LOG(("%s: stream %u out of bounds (%u)", __FUNCTION__, stream, mStreams.Length()));
+ 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::CONNECTING,
+ label,
+ protocol,
+ prPolicy, prValue,
+ flags,
+ nullptr, nullptr);
+ mStreams[stream] = channel;
+
+ channel->mState = DataChannel::WAITING_TO_OPEN;
+
+ LOG(("%s: sending ON_CHANNEL_CREATED for %s/%s: %u (state %u)", __FUNCTION__,
+ channel->mLabel.get(), channel->mProtocol.get(), stream, channel->mState));
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_CREATED,
+ this, channel)));
+
+ LOG(("%s: deferring sending ON_CHANNEL_OPEN for %p", __FUNCTION__, channel.get()));
+
+ if (!SendOpenAckMessage(stream)) {
+ // XXX Only on EAGAIN!? And if not, then close the channel??
+ channel->mFlags |= DATA_CHANNEL_FLAGS_SEND_ACK;
+ // Note: we're locked, so there's no danger of a race with the
+ // buffer-threshold callback
+ }
+
+ // Now process any queued data messages for the channel (which will
+ // themselves likely get queued until we leave WAITING_TO_OPEN, plus any
+ // more that come in before that happens)
+ DeliverQueuedData(stream);
+}
+
+// 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();
+
+ uint32_t i = 0;
+ while (i < mQueuedData.Length()) {
+ // Careful! we may modify the array length from within the loop!
+ if (mQueuedData[i]->mStream == stream) {
+ LOG(("Delivering queued data for stream %u, length %u",
+ stream, (unsigned int) mQueuedData[i]->mLength));
+ // Deliver the queued data
+ HandleDataMessage(mQueuedData[i]->mPpid,
+ mQueuedData[i]->mData, mQueuedData[i]->mLength,
+ mQueuedData[i]->mStream);
+ mQueuedData.RemoveElementAt(i);
+ continue; // don't bump index since we removed the element
+ }
+ i++;
+ }
+}
+
+void
+DataChannelConnection::HandleOpenAckMessage(const struct rtcweb_datachannel_ack *ack,
+ size_t length, uint16_t stream)
+{
+ DataChannel *channel;
+
+ mLock.AssertCurrentThreadOwns();
+
+ channel = FindChannelByStream(stream);
+ NS_ENSURE_TRUE_VOID(channel);
+
+ LOG(("OpenAck received for stream %u, waiting=%d", stream,
+ (channel->mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK) ? 1 : 0));
+
+ channel->mFlags &= ~DATA_CHANNEL_FLAGS_WAITING_ACK;
+}
+
+void
+DataChannelConnection::HandleUnknownMessage(uint32_t ppid, size_t length, uint16_t stream)
+{
+ /* XXX: Send an error message? */
+ LOG(("unknown DataChannel message received: %u, len %ld on stream %lu", ppid, length, stream));
+ // XXX Log to JS error console if possible
+}
+
+void
+DataChannelConnection::HandleDataMessage(uint32_t ppid,
+ const void *data, size_t length,
+ uint16_t stream)
+{
+ DataChannel *channel;
+ const char *buffer = (const char *) data;
+
+ mLock.AssertCurrentThreadOwns();
+
+ channel = FindChannelByStream(stream);
+
+ // 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.
+ LOG(("Queuing data for stream %u, length %u", stream, length));
+ // Copies data
+ mQueuedData.AppendElement(new QueuedDataMessage(stream, ppid, data, length));
+ return;
+ }
+
+ // XXX should this be a simple if, no warnings/debugbreaks?
+ NS_ENSURE_TRUE_VOID(channel->mState != CLOSED);
+
+ {
+ nsAutoCString recvData(buffer, length); // copies (<64) or allocates
+ bool is_binary = true;
+
+ if (ppid == DATA_CHANNEL_PPID_DOMSTRING ||
+ ppid == DATA_CHANNEL_PPID_DOMSTRING_LAST) {
+ is_binary = false;
+ }
+ if (is_binary != channel->mIsRecvBinary && !channel->mRecvBuffer.IsEmpty()) {
+ NS_WARNING("DataChannel message aborted by fragment type change!");
+ channel->mRecvBuffer.Truncate(0);
+ }
+ channel->mIsRecvBinary = is_binary;
+
+ switch (ppid) {
+ case DATA_CHANNEL_PPID_DOMSTRING:
+ case DATA_CHANNEL_PPID_BINARY:
+ channel->mRecvBuffer += recvData;
+ LOG(("DataChannel: Partial %s message of length %lu (total %u) on channel id %u",
+ is_binary ? "binary" : "string", length, channel->mRecvBuffer.Length(),
+ channel->mStream));
+ return; // Not ready to notify application
+
+ case DATA_CHANNEL_PPID_DOMSTRING_LAST:
+ LOG(("DataChannel: String message received of length %lu on channel %u",
+ length, channel->mStream));
+ if (!channel->mRecvBuffer.IsEmpty()) {
+ channel->mRecvBuffer += recvData;
+ LOG(("%s: sending ON_DATA (string fragmented) for %p", __FUNCTION__, channel));
+ channel->SendOrQueue(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_DATA, this,
+ channel, channel->mRecvBuffer, -1));
+ channel->mRecvBuffer.Truncate(0);
+ return;
+ }
+ // else send using recvData normally
+ length = -1; // Flag for DOMString
+
+ // WebSockets checks IsUTF8() here; we can try to deliver it
+ break;
+
+ case DATA_CHANNEL_PPID_BINARY_LAST:
+ LOG(("DataChannel: Received binary message of length %lu on channel id %u",
+ length, channel->mStream));
+ if (!channel->mRecvBuffer.IsEmpty()) {
+ channel->mRecvBuffer += recvData;
+ LOG(("%s: sending ON_DATA (binary fragmented) for %p", __FUNCTION__, channel));
+ channel->SendOrQueue(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_DATA, this,
+ channel, channel->mRecvBuffer,
+ channel->mRecvBuffer.Length()));
+ channel->mRecvBuffer.Truncate(0);
+ return;
+ }
+ // else send using recvData normally
+ break;
+
+ default:
+ NS_ERROR("Unknown data PPID");
+ return;
+ }
+ /* Notify onmessage */
+ LOG(("%s: sending ON_DATA for %p", __FUNCTION__, channel));
+ channel->SendOrQueue(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_DATA, this,
+ channel, recvData, length));
+ }
+}
+
+// Called with mLock locked!
+void
+DataChannelConnection::HandleMessage(const void *buffer, size_t length, uint32_t ppid, uint16_t stream)
+{
+ const struct rtcweb_datachannel_open_request *req;
+ const struct rtcweb_datachannel_ack *ack;
+
+ mLock.AssertCurrentThreadOwns();
+
+ switch (ppid) {
+ case DATA_CHANNEL_PPID_CONTROL:
+ req = static_cast<const struct rtcweb_datachannel_open_request *>(buffer);
+
+ NS_ENSURE_TRUE_VOID(length >= sizeof(*ack)); // smallest message
+ switch (req->msg_type) {
+ case DATA_CHANNEL_OPEN_REQUEST:
+ // structure includes a possibly-unused char label[1] (in a packed structure)
+ NS_ENSURE_TRUE_VOID(length >= sizeof(*req) - 1);
+
+ HandleOpenRequestMessage(req, length, stream);
+ break;
+ case DATA_CHANNEL_ACK:
+ // >= sizeof(*ack) checked above
+
+ ack = static_cast<const struct rtcweb_datachannel_ack *>(buffer);
+ HandleOpenAckMessage(ack, length, stream);
+ break;
+ default:
+ HandleUnknownMessage(ppid, length, stream);
+ break;
+ }
+ break;
+ case DATA_CHANNEL_PPID_DOMSTRING:
+ case DATA_CHANNEL_PPID_DOMSTRING_LAST:
+ case DATA_CHANNEL_PPID_BINARY:
+ case DATA_CHANNEL_PPID_BINARY_LAST:
+ HandleDataMessage(ppid, buffer, length, stream);
+ break;
+ default:
+ LOG(("Message of length %lu, PPID %u on stream %u received.",
+ length, ppid, stream));
+ break;
+ }
+}
+
+void
+DataChannelConnection::HandleAssociationChangeEvent(const struct sctp_assoc_change *sac)
+{
+ uint32_t i, n;
+
+ switch (sac->sac_state) {
+ case SCTP_COMM_UP:
+ LOG(("Association change: SCTP_COMM_UP"));
+ if (mState == CONNECTING) {
+ mSocket = mMasterSocket;
+ mState = OPEN;
+
+ SetEvenOdd();
+
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CONNECTION,
+ this)));
+ LOG(("DTLS connect() succeeded! Entering connected mode"));
+
+ // Open any streams pending...
+ ProcessQueuedOpens();
+
+ } else if (mState == OPEN) {
+ LOG(("DataConnection Already OPEN"));
+ } else {
+ LOG(("Unexpected state: %d", mState));
+ }
+ break;
+ case SCTP_COMM_LOST:
+ LOG(("Association change: SCTP_COMM_LOST"));
+ // This association is toast, so also close all the channels -- from mainthread!
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_DISCONNECTED,
+ this)));
+ break;
+ case SCTP_RESTART:
+ LOG(("Association change: SCTP_RESTART"));
+ break;
+ case SCTP_SHUTDOWN_COMP:
+ LOG(("Association change: SCTP_SHUTDOWN_COMP"));
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_DISCONNECTED,
+ this)));
+ break;
+ case SCTP_CANT_STR_ASSOC:
+ LOG(("Association change: SCTP_CANT_STR_ASSOC"));
+ break;
+ default:
+ LOG(("Association change: UNKNOWN"));
+ break;
+ }
+ LOG(("Association change: streams (in/out) = (%u/%u)",
+ sac->sac_inbound_streams, sac->sac_outbound_streams));
+
+ NS_ENSURE_TRUE_VOID(sac);
+ n = sac->sac_length - sizeof(*sac);
+ if (((sac->sac_state == SCTP_COMM_UP) ||
+ (sac->sac_state == SCTP_RESTART)) && (n > 0)) {
+ for (i = 0; i < n; ++i) {
+ switch (sac->sac_info[i]) {
+ case SCTP_ASSOC_SUPPORTS_PR:
+ LOG(("Supports: PR"));
+ break;
+ case SCTP_ASSOC_SUPPORTS_AUTH:
+ LOG(("Supports: AUTH"));
+ break;
+ case SCTP_ASSOC_SUPPORTS_ASCONF:
+ LOG(("Supports: ASCONF"));
+ break;
+ case SCTP_ASSOC_SUPPORTS_MULTIBUF:
+ LOG(("Supports: MULTIBUF"));
+ break;
+ case SCTP_ASSOC_SUPPORTS_RE_CONFIG:
+ LOG(("Supports: RE-CONFIG"));
+ break;
+ default:
+ LOG(("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)) {
+ LOG(("Association: ABORT ="));
+ for (i = 0; i < n; ++i) {
+ LOG((" 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;
+ }
+ LOG(("Peer address %s is now ", addr));
+ switch (spc->spc_state) {
+ case SCTP_ADDR_AVAILABLE:
+ LOG(("SCTP_ADDR_AVAILABLE"));
+ break;
+ case SCTP_ADDR_UNREACHABLE:
+ LOG(("SCTP_ADDR_UNREACHABLE"));
+ break;
+ case SCTP_ADDR_REMOVED:
+ LOG(("SCTP_ADDR_REMOVED"));
+ break;
+ case SCTP_ADDR_ADDED:
+ LOG(("SCTP_ADDR_ADDED"));
+ break;
+ case SCTP_ADDR_MADE_PRIM:
+ LOG(("SCTP_ADDR_MADE_PRIM"));
+ break;
+ case SCTP_ADDR_CONFIRMED:
+ LOG(("SCTP_ADDR_CONFIRMED"));
+ break;
+ default:
+ LOG(("UNKNOWN"));
+ break;
+ }
+ LOG((" (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);
+ LOG(("Remote Error (error = 0x%04x): ", sre->sre_error));
+ for (i = 0; i < n; ++i) {
+ LOG((" 0x%02x", sre-> sre_data[i]));
+ }
+}
+
+void
+DataChannelConnection::HandleShutdownEvent(const struct sctp_shutdown_event *sse)
+{
+ LOG(("Shutdown event."));
+ /* XXX: notify all channels. */
+ // Attempts to actually send anything will fail
+}
+
+void
+DataChannelConnection::HandleAdaptationIndication(const struct sctp_adaptation_event *sai)
+{
+ LOG(("Adaptation indication: %x.", sai-> sai_adaptation_ind));
+}
+
+void
+DataChannelConnection::HandleSendFailedEvent(const struct sctp_send_failed_event *ssfe)
+{
+ size_t i, n;
+
+ if (ssfe->ssfe_flags & SCTP_DATA_UNSENT) {
+ LOG(("Unsent "));
+ }
+ if (ssfe->ssfe_flags & SCTP_DATA_SENT) {
+ LOG(("Sent "));
+ }
+ if (ssfe->ssfe_flags & ~(SCTP_DATA_SENT | SCTP_DATA_UNSENT)) {
+ LOG(("(flags = %x) ", ssfe->ssfe_flags));
+ }
+ LOG(("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) {
+ LOG((" 0x%02x", ssfe->ssfe_data[i]));
+ }
+}
+
+void
+DataChannelConnection::ClearResets()
+{
+ // Clear all pending resets
+ if (!mStreamsResetting.IsEmpty()) {
+ LOG(("Clearing resets for %d streams", mStreamsResetting.Length()));
+ }
+
+ for (uint32_t i = 0; i < mStreamsResetting.Length(); ++i) {
+ RefPtr<DataChannel> channel;
+ channel = FindChannelByStream(mStreamsResetting[i]);
+ if (channel) {
+ LOG(("Forgetting channel %u (%p) with pending reset",channel->mStream, channel.get()));
+ mStreams[channel->mStream] = nullptr;
+ }
+ }
+ mStreamsResetting.Clear();
+}
+
+void
+DataChannelConnection::ResetOutgoingStream(uint16_t stream)
+{
+ uint32_t i;
+
+ mLock.AssertCurrentThreadOwns();
+ LOG(("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;
+
+ LOG(("Connection %p: Sending outgoing stream reset for %d streams",
+ (void *) this, mStreamsResetting.Length()));
+ mLock.AssertCurrentThreadOwns();
+ if (mStreamsResetting.IsEmpty()) {
+ LOG(("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) {
+ LOG(("***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.
+
+ LOG(("Incoming: Channel %u closed, state %d",
+ channel->mStream, channel->mState));
+ ASSERT_WEBRTC(channel->mState == DataChannel::OPEN ||
+ channel->mState == DataChannel::CLOSING ||
+ channel->mState == DataChannel::CONNECTING ||
+ channel->mState == DataChannel::WAITING_TO_OPEN);
+ if (channel->mState == DataChannel::OPEN ||
+ channel->mState == DataChannel::WAITING_TO_OPEN) {
+ // Mark the stream for reset (the reset is sent below)
+ ResetOutgoingStream(channel->mStream);
+ }
+ mStreams[channel->mStream] = nullptr;
+
+ LOG(("Disconnected DataChannel %p from connection %p",
+ (void *) channel.get(), (void *) channel->mConnection.get()));
+ // This sends ON_CHANNEL_CLOSED to mainthread
+ channel->StreamClosedLocked();
+ } else {
+ LOG(("Can't find incoming channel %d",i));
+ }
+ }
+ }
+ }
+
+ // Process any pending resets now:
+ if (!mStreamsResetting.IsEmpty()) {
+ LOG(("Sending %d pending resets", mStreamsResetting.Length()));
+ SendOutgoingStreamReset();
+ }
+}
+
+void
+DataChannelConnection::HandleStreamChangeEvent(const struct sctp_stream_change_event *strchg)
+{
+ uint16_t stream;
+ RefPtr<DataChannel> channel;
+
+ if (strchg->strchange_flags == SCTP_STREAM_CHANGE_DENIED) {
+ LOG(("*** Failed increasing number of streams from %u (%u/%u)",
+ mStreams.Length(),
+ strchg->strchange_instrms,
+ strchg->strchange_outstrms));
+ // XXX FIX! notify pending opens of failure
+ return;
+ } else {
+ if (strchg->strchange_instrms > mStreams.Length()) {
+ LOG(("Other side increased streams from %u to %u",
+ mStreams.Length(), strchg->strchange_instrms));
+ }
+ if (strchg->strchange_outstrms > mStreams.Length() ||
+ strchg->strchange_instrms > mStreams.Length()) {
+ uint16_t old_len = mStreams.Length();
+ uint16_t new_len = std::max(strchg->strchange_outstrms,
+ strchg->strchange_instrms);
+ LOG(("Increasing number of streams from %u to %u - adding %u (in: %u)",
+ old_len, new_len, new_len - old_len,
+ strchg->strchange_instrms));
+ // make sure both are the same length
+ mStreams.AppendElements(new_len - old_len);
+ LOG(("New length = %d (was %d)", mStreams.Length(), old_len));
+ for (size_t i = old_len; i < mStreams.Length(); ++i) {
+ mStreams[i] = nullptr;
+ }
+ // 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
+ size_t num_needed = mPending.GetSize();
+ LOG(("%d of %d new streams already needed", num_needed,
+ new_len - old_len));
+ num_needed -= (new_len - old_len); // number we added
+ if (num_needed > 0) {
+ if (num_needed < 16)
+ num_needed = 16;
+ LOG(("Not enough new streams, asking for %d more", num_needed));
+ RequestMoreStreams(num_needed);
+ } else if (strchg->strchange_outstrms < strchg->strchange_instrms) {
+ LOG(("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
+ }
+
+ for (uint32_t i = 0; i < mStreams.Length(); ++i) {
+ channel = mStreams[i];
+ if (!channel)
+ continue;
+
+ if ((channel->mState == CONNECTING) &&
+ (channel->mStream == INVALID_STREAM)) {
+ if ((strchg->strchange_flags & SCTP_STREAM_CHANGE_DENIED) ||
+ (strchg->strchange_flags & SCTP_STREAM_CHANGE_FAILED)) {
+ /* XXX: Signal to the other end. */
+ channel->mState = CLOSED;
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_CLOSED, this,
+ channel)));
+ // maybe fire onError (bug 843625)
+ } else {
+ stream = FindFreeStream();
+ if (stream != INVALID_STREAM) {
+ channel->mStream = stream;
+ mStreams[stream] = channel;
+ channel->mFlags |= DATA_CHANNEL_FLAGS_SEND_REQ;
+ // Note: we're locked, so there's no danger of a race with the
+ // buffer-threshold callback
+ } else {
+ /* We will not find more ... */
+ break;
+ }
+ }
+ }
+ }
+}
+
+// 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_PARTIAL_DELIVERY_EVENT:
+ LOG(("SCTP_PARTIAL_DELIVERY_EVENT"));
+ break;
+ case SCTP_AUTHENTICATION_EVENT:
+ LOG(("SCTP_AUTHENTICATION_EVENT"));
+ break;
+ case SCTP_SENDER_DRY_EVENT:
+ //LOG(("SCTP_SENDER_DRY_EVENT"));
+ break;
+ case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+ LOG(("SCTP_NOTIFICATIONS_STOPPED_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:
+ LOG(("SCTP_ASSOC_RESET_EVENT"));
+ break;
+ case SCTP_STREAM_CHANGE_EVENT:
+ HandleStreamChangeEvent(&(notif->sn_strchange_event));
+ break;
+ default:
+ LOG(("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, int32_t flags)
+{
+ ASSERT_WEBRTC(!NS_IsMainThread());
+
+ if (!data) {
+ usrsctp_close(sock); // SCTP has finished shutting down
+ } else {
+ MutexAutoLock lock(mLock);
+ if (flags & MSG_NOTIFICATION) {
+ HandleNotification(static_cast<union sctp_notification *>(data), datalen);
+ } else {
+ HandleMessage(data, datalen, ntohl(rcv.rcv_ppid), rcv.rcv_sid);
+ }
+ }
+ // 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)
+{
+ // aStream == INVALID_STREAM to have the protocol allocate
+ uint16_t prPolicy = SCTP_PR_SCTP_NONE;
+ uint32_t flags;
+
+ LOG(("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;
+ }
+ if ((prPolicy == SCTP_PR_SCTP_NONE) && (prValue != 0)) {
+ return nullptr;
+ }
+
+ // Don't look past currently-negotiated streams
+ if (aStream != INVALID_STREAM && aStream < mStreams.Length() && mStreams[aStream]) {
+ LOG(("ERROR: external negotiation of already-open channel %u", aStream));
+ // XXX How do we indicate this up to the application? Probably the
+ // caller's job, but we may need to return an error code.
+ return nullptr;
+ }
+
+ flags = !inOrder ? DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED : 0;
+ RefPtr<DataChannel> channel(new DataChannel(this,
+ aStream,
+ DataChannel::CONNECTING,
+ label, protocol,
+ type, prValue,
+ flags,
+ aListener, aContext));
+ if (aExternalNegotiated) {
+ channel->mFlags |= DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED;
+ }
+
+ 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
+ uint16_t stream = channel->mStream;
+ bool queue = false;
+
+ 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.
+
+ if (mState == OPEN) {
+ if (stream == INVALID_STREAM) {
+ stream = FindFreeStream(); // may be INVALID_STREAM if we need more
+ }
+ if (stream == INVALID_STREAM || stream >= mStreams.Length()) {
+ // 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 == INVALID_STREAM) ? 16 :
+ (stream-((int32_t)mStreams.Length())) + 16;
+ if (!RequestMoreStreams(more_needed)) {
+ // Something bad happened... we're done
+ goto request_error_cleanup;
+ }
+ queue = true;
+ }
+ } else {
+ // not OPEN
+ if (stream != INVALID_STREAM && stream >= mStreams.Length() &&
+ mState == CLOSED) {
+ // Update number of streams for init message
+ struct sctp_initmsg initmsg;
+ socklen_t len = sizeof(initmsg);
+ int32_t total_needed = stream+16;
+
+ memset(&initmsg, 0, sizeof(initmsg));
+ if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, &len) < 0) {
+ LOG(("*** failed getsockopt SCTP_INITMSG"));
+ goto request_error_cleanup;
+ }
+ LOG(("Setting number of SCTP streams to %u, was %u/%u", total_needed,
+ initmsg.sinit_num_ostreams, initmsg.sinit_max_instreams));
+ initmsg.sinit_num_ostreams = total_needed;
+ initmsg.sinit_max_instreams = MAX_NUM_STREAMS;
+ if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg,
+ (socklen_t)sizeof(initmsg)) < 0) {
+ LOG(("*** failed setsockopt SCTP_INITMSG, errno %d", errno));
+ goto request_error_cleanup;
+ }
+
+ int32_t old_len = mStreams.Length();
+ mStreams.AppendElements(total_needed - old_len);
+ for (int32_t i = old_len; i < total_needed; ++i) {
+ mStreams[i] = nullptr;
+ }
+ }
+ // else if state is CONNECTING, we'll just re-negotiate when OpenFinish
+ // is called, if needed
+ queue = true;
+ }
+ if (queue) {
+ LOG(("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;
+ // we need a ref for the nsDeQue and one to return
+ DataChannel* rawChannel = channel;
+ rawChannel->AddRef();
+ mPending.Push(rawChannel);
+ return channel.forget();
+ }
+
+ MOZ_ASSERT(stream != INVALID_STREAM);
+ // just allocated (& OPEN), or externally negotiated
+ mStreams[stream] = channel; // holds a reference
+ channel->mStream = stream;
+
+#ifdef TEST_QUEUED_DATA
+ // It's painful to write a test for this...
+ channel->mState = OPEN;
+ channel->mReady = true;
+ SendMsgInternal(channel, "Help me!", 8, DATA_CHANNEL_PPID_DOMSTRING_LAST);
+#endif
+
+ if (channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) {
+ // Don't send unordered until this gets cleared
+ channel->mFlags |= DATA_CHANNEL_FLAGS_WAITING_ACK;
+ }
+
+ if (!(channel->mFlags & DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED)) {
+ if (!SendOpenRequestMessage(channel->mLabel, channel->mProtocol,
+ stream,
+ !!(channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED),
+ channel->mPrPolicy, channel->mPrValue)) {
+ LOG(("SendOpenRequest failed, errno = %d", errno));
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ channel->mFlags |= DATA_CHANNEL_FLAGS_SEND_REQ;
+ // Note: we're locked, so there's no danger of a race with the
+ // buffer-threshold callback
+ return channel.forget();
+ } else {
+ if (channel->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) {
+ // We already returned the channel to the app.
+ NS_ERROR("Failed to send open request");
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_CLOSED, this,
+ channel)));
+ }
+ // If we haven't returned the channel yet, it will get destroyed when we exit
+ // this function.
+ mStreams[stream] = nullptr;
+ channel->mStream = INVALID_STREAM;
+ // we'll be destroying the channel
+ channel->mState = CLOSED;
+ return nullptr;
+ }
+ /* NOTREACHED */
+ }
+ }
+ // Either externally negotiated or we sent Open
+ channel->mState = OPEN;
+ channel->mReady = true;
+ // FIX? Move into DOMDataChannel? I don't think we can send it yet here
+ LOG(("%s: sending ON_CHANNEL_OPEN for %p", __FUNCTION__, channel.get()));
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_OPEN, this,
+ channel)));
+
+ return channel.forget();
+
+request_error_cleanup:
+ channel->mState = CLOSED;
+ if (channel->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) {
+ // We already returned the channel to the app.
+ NS_ERROR("Failed to request more streams");
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_CLOSED, this,
+ channel)));
+ 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;
+}
+
+int32_t
+DataChannelConnection::SendMsgInternal(DataChannel *channel, const char *data,
+ size_t length, uint32_t ppid)
+{
+ uint16_t flags;
+ struct sctp_sendv_spa spa;
+ int32_t result;
+
+ NS_ENSURE_TRUE(channel->mState == OPEN || channel->mState == CONNECTING, 0);
+ NS_WARNING_ASSERTION(length > 0, "Length is 0?!");
+
+ // 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->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) &&
+ !(channel->mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK)) {
+ flags = SCTP_UNORDERED;
+ } else {
+ flags = 0;
+ }
+
+ spa.sendv_sndinfo.snd_ppid = htonl(ppid);
+ spa.sendv_sndinfo.snd_sid = channel->mStream;
+ spa.sendv_sndinfo.snd_flags = flags;
+ spa.sendv_sndinfo.snd_context = 0;
+ spa.sendv_sndinfo.snd_assoc_id = 0;
+ spa.sendv_flags = SCTP_SEND_SNDINFO_VALID;
+
+ if (channel->mPrPolicy != SCTP_PR_SCTP_NONE) {
+ spa.sendv_prinfo.pr_policy = channel->mPrPolicy;
+ spa.sendv_prinfo.pr_value = channel->mPrValue;
+ spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+ }
+
+ // 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
+
+ // SCTP will return EMSGSIZE if the message is bigger than the buffer
+ // size (or EAGAIN if there isn't space)
+
+ // 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!
+ MutexAutoLock lock(mLock);
+ if (channel->mBufferedData.IsEmpty()) {
+ result = usrsctp_sendv(mSocket, data, length,
+ nullptr, 0,
+ (void *)&spa, (socklen_t)sizeof(struct sctp_sendv_spa),
+ SCTP_SENDV_SPA, 0);
+ LOG(("Sent buffer (len=%u), result=%d", length, result));
+ } else {
+ // Fake EAGAIN if we're already buffering data
+ result = -1;
+ errno = EAGAIN;
+ }
+ if (result < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+
+ // queue data for resend! And queue any further data for the stream until it is...
+ auto *buffered = new BufferedMsg(spa, data, length); // infallible malloc
+ channel->mBufferedData.AppendElement(buffered); // owned by mBufferedData array
+ channel->mFlags |= DATA_CHANNEL_FLAGS_SEND_DATA;
+ LOG(("Queued %u buffers (len=%u)", channel->mBufferedData.Length(), length));
+ return 0;
+ }
+ LOG(("error %d sending string", errno));
+ }
+ return result;
+}
+
+// Handles fragmenting binary messages
+int32_t
+DataChannelConnection::SendBinary(DataChannel *channel, const char *data,
+ size_t len,
+ uint32_t ppid_partial, uint32_t ppid_final)
+{
+ // Since there's a limit on network buffer size and no limits on message
+ // size, and we don't want to use EOR mode (multiple writes for a
+ // message, but all other streams are blocked until you finish sending
+ // this message), we need to add application-level fragmentation of large
+ // messages. On a reliable channel, these can be simply rebuilt into a
+ // large message. On an unreliable channel, we can't and don't know how
+ // long to wait, and there are no retransmissions, and no easy way to
+ // tell the user "this part is missing", so on unreliable channels we
+ // need to return an error if sending more bytes than the network buffers
+ // can hold, and perhaps a lower number.
+
+ // We *really* don't want to do this from main thread! - and SendMsgInternal
+ // avoids blocking.
+ // This MUST be reliable and in-order for the reassembly to work
+ if (len > DATA_CHANNEL_MAX_BINARY_FRAGMENT &&
+ channel->mPrPolicy == DATA_CHANNEL_RELIABLE &&
+ !(channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED)) {
+ int32_t sent=0;
+ uint32_t origlen = len;
+ LOG(("Sending binary message length %u in chunks", len));
+ // XXX check flags for out-of-order, or force in-order for large binary messages
+ while (len > 0) {
+ size_t sendlen = std::min<size_t>(len, DATA_CHANNEL_MAX_BINARY_FRAGMENT);
+ uint32_t ppid;
+ len -= sendlen;
+ ppid = len > 0 ? ppid_partial : ppid_final;
+ LOG(("Send chunk of %u bytes, ppid %u", sendlen, ppid));
+ // Note that these might end up being deferred and queued.
+ sent += SendMsgInternal(channel, data, sendlen, ppid);
+ data += sendlen;
+ }
+ LOG(("Sent %d buffers for %u bytes, %d sent immediately, %d buffers queued",
+ (origlen+DATA_CHANNEL_MAX_BINARY_FRAGMENT-1)/DATA_CHANNEL_MAX_BINARY_FRAGMENT,
+ origlen, sent,
+ channel->mBufferedData.Length()));
+ return sent;
+ }
+ NS_WARNING_ASSERTION(len <= DATA_CHANNEL_MAX_BINARY_FRAGMENT,
+ "Sending too-large data on unreliable channel!");
+
+ // This will fail if the message is too large (default 256K)
+ return SendMsgInternal(channel, data, len, ppid_final);
+}
+
+class ReadBlobRunnable : public Runnable {
+public:
+ ReadBlobRunnable(DataChannelConnection* aConnection, uint16_t aStream,
+ nsIInputStream* aBlob) :
+ 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;
+};
+
+int32_t
+DataChannelConnection::SendBlob(uint16_t stream, nsIInputStream *aBlob)
+{
+ DataChannel *channel = mStreams[stream];
+ NS_ENSURE_TRUE(channel, 0);
+ // Spawn a thread to send the data
+ if (!mInternalIOThread) {
+ nsresult res = NS_NewThread(getter_AddRefs(mInternalIOThread));
+ if (NS_FAILED(res)) {
+ return -1;
+ }
+ }
+
+ 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)
+ : mConnection(aConnection)
+ , mStream(aStream) {}
+
+ ~DataChannelBlobSendRunnable() override
+ {
+ if (!NS_IsMainThread() && mConnection) {
+ MOZ_ASSERT(false);
+ // explicitly leak the connection if destroyed off mainthread
+ Unused << mConnection.forget().take();
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ ASSERT_WEBRTC(NS_IsMainThread());
+
+ mConnection->SendBinaryMsg(mStream, mData);
+ mConnection = nullptr;
+ return NS_OK;
+ }
+
+ // explicitly public so we can avoid allocating twice and copying
+ nsCString mData;
+
+private:
+ // Note: we can be destroyed off the target thread, so be careful not to let this
+ // get Released()ed on the temp thread!
+ RefPtr<DataChannelConnection> mConnection;
+ uint16_t mStream;
+};
+
+void
+DataChannelConnection::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.
+
+ // For now as a hack, send as a single blast of queued packets which may
+ // be deferred until buffer space is available.
+ uint64_t len;
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+
+ // 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(aBlob->Available(&len)) ||
+ NS_FAILED(NS_ReadInputStreamToString(aBlob, runnable->mData, len))) {
+ // 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_ProxyRelease(mainThread, runnable.forget());
+ return;
+ }
+ aBlob->Close();
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+}
+
+void
+DataChannelConnection::GetStreamIds(std::vector<uint16_t>* aStreamList)
+{
+ ASSERT_WEBRTC(NS_IsMainThread());
+ for (uint32_t i = 0; i < mStreams.Length(); ++i) {
+ if (mStreams[i]) {
+ aStreamList->push_back(mStreams[i]->mStream);
+ }
+ }
+}
+
+int32_t
+DataChannelConnection::SendMsgCommon(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
+ // mStreams, and issues with the association closing (access to mSocket).
+
+ const char *data = aMsg.BeginReading();
+ uint32_t len = aMsg.Length();
+ DataChannel *channel;
+
+ LOG(("Sending %sto stream %u: %u bytes", isBinary ? "binary " : "", stream, len));
+ // XXX if we want more efficiency, translate flags once at open time
+ channel = mStreams[stream];
+ NS_ENSURE_TRUE(channel, 0);
+
+ if (isBinary)
+ return SendBinary(channel, data, len,
+ DATA_CHANNEL_PPID_BINARY, DATA_CHANNEL_PPID_BINARY_LAST);
+ return SendBinary(channel, data, len,
+ DATA_CHANNEL_PPID_DOMSTRING, DATA_CHANNEL_PPID_DOMSTRING_LAST);
+}
+
+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();
+ LOG(("Connection %p/Channel %p: Closing stream %u",
+ channel->mConnection.get(), channel.get(), channel->mStream));
+ // re-test since it may have closed before the lock was grabbed
+ if (aChannel->mState == CLOSED || aChannel->mState == CLOSING) {
+ LOG(("Channel already closing/closed (%u)", aChannel->mState));
+ if (mState == CLOSED && channel->mStream != INVALID_STREAM) {
+ // called from CloseAll()
+ // we're not going to hang around waiting any more
+ mStreams[channel->mStream] = nullptr;
+ }
+ return;
+ }
+ aChannel->mBufferedData.Clear();
+ if (channel->mStream != INVALID_STREAM) {
+ ResetOutgoingStream(channel->mStream);
+ if (mState == CLOSED) { // called from CloseAll()
+ // Let resets accumulate then send all at once in CloseAll()
+ // we're not going to hang around waiting
+ mStreams[channel->mStream] = nullptr;
+ } else {
+ SendOutgoingStreamReset();
+ }
+ }
+ aChannel->mState = CLOSING;
+ if (mState == 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()
+{
+ LOG(("Closing all channels (connection %p)", (void*) this));
+ // Don't need to lock here
+
+ // Make sure no more channels will be opened
+ {
+ MutexAutoLock lock(mLock);
+ mState = 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)
+ bool closed_some = false;
+ for (uint32_t i = 0; i < mStreams.Length(); ++i) {
+ if (mStreams[i]) {
+ mStreams[i]->Close();
+ closed_some = true;
+ }
+ }
+
+ // Clean up any pending opens for channels
+ RefPtr<DataChannel> channel;
+ while (nullptr != (channel = dont_AddRef(static_cast<DataChannel *>(mPending.PopFront())))) {
+ LOG(("closing pending channel %p, stream %u", channel.get(), channel->mStream));
+ channel->Close(); // also releases the ref on each iteration
+ closed_some = true;
+ }
+ // It's more efficient to let the Resets queue in shutdown and then
+ // SendOutgoingStreamReset() here.
+ if (closed_some) {
+ MutexAutoLock lock(mLock);
+ SendOutgoingStreamReset();
+ }
+}
+
+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(mState == CLOSED || mState == 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;
+
+ LOG(("Destroying Data channel %u", mStream));
+ MOZ_ASSERT_IF(mStream != INVALID_STREAM,
+ !mConnection->FindChannelByStream(mStream));
+ mStream = INVALID_STREAM;
+ mState = CLOSED;
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_CLOSED,
+ mConnection, this)));
+ // 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)
+{
+ MutexAutoLock mLock(mListenerLock);
+ mContext = aContext;
+ mListener = aListener;
+}
+
+// May be called from another (i.e. Main) thread!
+void
+DataChannel::AppReady()
+{
+ ENSURE_DATACONNECTION;
+
+ MutexAutoLock lock(mConnection->mLock);
+
+ mReady = true;
+ if (mState == WAITING_TO_OPEN) {
+ mState = OPEN;
+ NS_DispatchToMainThread(do_AddRef(new DataChannelOnMessageAvailable(
+ DataChannelOnMessageAvailable::ON_CHANNEL_OPEN, mConnection,
+ this)));
+ for (uint32_t i = 0; i < mQueuedMessages.Length(); ++i) {
+ nsCOMPtr<nsIRunnable> runnable = mQueuedMessages[i];
+ MOZ_ASSERT(runnable);
+ NS_DispatchToMainThread(runnable);
+ }
+ } else {
+ NS_ASSERTION(mQueuedMessages.IsEmpty(), "Shouldn't have queued messages if not WAITING_TO_OPEN");
+ }
+ mQueuedMessages.Clear();
+ mQueuedMessages.Compact();
+ // We never use it again... We could even allocate the array in the odd
+ // cases we need it.
+}
+
+uint32_t
+DataChannel::GetBufferedAmountLocked() const
+{
+ size_t buffered = 0;
+
+ for (auto& buffer : mBufferedData) {
+ buffered += buffer->mLength;
+ }
+ // XXX Note: per Michael Tuexen, there's no way to currently get the buffered
+ // amount from the SCTP stack for a single stream. It is on their to-do
+ // list, and once we import a stack with support for that, we'll need to
+ // add it to what we buffer. Also we'll need to ask for notification of a per-
+ // stream buffer-low event and merge that into the handling of buffer-low
+ // (the equivalent to TCP_NOTSENT_LOWAT on TCP sockets)
+
+ if (buffered > UINT32_MAX) { // paranoia - >4GB buffered is very very unlikely
+ buffered = UINT32_MAX;
+ }
+ return buffered;
+}
+
+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)
+{
+ if (!mReady &&
+ (mState == CONNECTING || mState == WAITING_TO_OPEN)) {
+ mQueuedMessages.AppendElement(aMessage);
+ } else {
+ NS_DispatchToMainThread(aMessage);
+ }
+}
+
+} // namespace mozilla
diff --git a/netwerk/sctp/datachannel/DataChannel.h b/netwerk/sctp/datachannel/DataChannel.h
new file mode 100644
index 0000000000..84ab422fcf
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannel.h
@@ -0,0 +1,583 @@
+/* -*- 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 <string>
+#include <errno.h>
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsTArray.h"
+#include "nsDeque.h"
+#include "nsIInputStream.h"
+#include "mozilla/Mutex.h"
+#include "DataChannelProtocol.h"
+#include "DataChannelListener.h"
+#ifdef SCTP_DTLS_SUPPORTED
+#include "mtransport/sigslot.h"
+#include "mtransport/transportflow.h"
+#include "mtransport/transportlayer.h"
+#include "mtransport/transportlayerdtls.h"
+#include "mtransport/transportlayerprsock.h"
+#endif
+
+#ifndef DATACHANNEL_LOG
+#define DATACHANNEL_LOG(args)
+#endif
+
+#ifndef EALREADY
+#define EALREADY WSAEALREADY
+#endif
+
+extern "C" {
+ struct socket;
+ struct sctp_rcvinfo;
+}
+
+namespace mozilla {
+
+class DataChannelConnection;
+class DataChannel;
+class DataChannelOnMessageAvailable;
+
+// For queuing outgoing messages
+class BufferedMsg
+{
+public:
+ BufferedMsg(struct sctp_sendv_spa &spa,const char *data,
+ size_t length);
+ ~BufferedMsg();
+
+ struct sctp_sendv_spa *mSpa;
+ const char *mData;
+ size_t mLength;
+};
+
+// 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,
+ const void *data, size_t length)
+ : mStream(stream)
+ , mPpid(ppid)
+ , mLength(length)
+ {
+ mData = static_cast<char *>(moz_xmalloc(length)); // infallible
+ memcpy(mData, data, length);
+ }
+
+ ~QueuedDataMessage()
+ {
+ free(mData);
+ }
+
+ uint16_t mStream;
+ uint32_t mPpid;
+ size_t mLength;
+ char *mData;
+};
+
+// One per PeerConnection
+class DataChannelConnection
+#ifdef SCTP_DTLS_SUPPORTED
+ : public sigslot::has_slots<>
+#endif
+{
+ virtual ~DataChannelConnection();
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannelConnection)
+
+ class DataConnectionListener : public SupportsWeakPtr<DataConnectionListener>
+ {
+ public:
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(DataChannelConnection::DataConnectionListener)
+ virtual ~DataConnectionListener() {}
+
+ // Called when a new DataChannel has been opened by the other side.
+ virtual void NotifyDataChannel(already_AddRefed<DataChannel> channel) = 0;
+ };
+
+ explicit DataChannelConnection(DataConnectionListener *listener);
+
+ bool Init(unsigned short aPort, uint16_t aNumStreams, bool aUsingDtls);
+ 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);
+
+#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
+ // Connect using a TransportFlow (DTLS) channel
+ void SetEvenOdd();
+ bool ConnectViaTransportFlow(TransportFlow *aFlow, uint16_t localport, uint16_t remoteport);
+ void CompleteConnect(TransportFlow *flow, TransportLayer::State state);
+ void SetSignals();
+#endif
+
+ typedef enum {
+ RELIABLE=0,
+ PARTIAL_RELIABLE_REXMIT = 1,
+ PARTIAL_RELIABLE_TIMED = 2
+ } Type;
+
+ MOZ_MUST_USE
+ 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 Close(DataChannel *aChannel);
+ // CloseInt() must be called with mLock held
+ void CloseInt(DataChannel *aChannel);
+ void CloseAll();
+
+ int32_t SendMsg(uint16_t stream, const nsACString &aMsg)
+ {
+ return SendMsgCommon(stream, aMsg, false);
+ }
+ int32_t SendBinaryMsg(uint16_t stream, const nsACString &aMsg)
+ {
+ return SendMsgCommon(stream, aMsg, true);
+ }
+ int32_t 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, int32_t flags);
+
+ // Find out state
+ enum {
+ CONNECTING = 0U,
+ OPEN = 1U,
+ CLOSING = 2U,
+ CLOSED = 3U
+ };
+ uint16_t GetReadyState() { MutexAutoLock lock(mLock); return mState; }
+
+ friend class DataChannel;
+ Mutex mLock;
+
+ void ReadBlob(already_AddRefed<DataChannelConnection> aThis, uint16_t aStream, nsIInputStream* aBlob);
+
+ void GetStreamIds(std::vector<uint16_t>* aStreamList);
+
+ bool SendDeferredMessages();
+
+protected:
+ friend class DataChannelOnMessageAvailable;
+ // Avoid cycles with PeerConnectionImpl
+ // Use from main thread only as WeakPtr is not threadsafe
+ WeakPtr<DataConnectionListener> mListener;
+
+private:
+ friend class DataChannelConnectRunnable;
+
+#ifdef SCTP_DTLS_SUPPORTED
+ static void DTLSConnectThread(void *data);
+ int SendPacket(unsigned char data[], size_t len, bool release);
+ void SctpDtlsInput(TransportFlow *flow, const unsigned char *data, size_t len);
+ static int SctpDtlsOutput(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df);
+#endif
+ DataChannel* FindChannelByStream(uint16_t stream);
+ uint16_t FindFreeStream();
+ bool RequestMoreStreams(int32_t aNeeded = 16);
+ int32_t SendControlMessage(void *msg, uint32_t len, uint16_t stream);
+ int32_t SendOpenRequestMessage(const nsACString& label, const nsACString& protocol,
+ uint16_t stream,
+ bool unordered, uint16_t prPolicy, uint32_t prValue);
+ int32_t SendOpenAckMessage(uint16_t stream);
+ int32_t SendMsgInternal(DataChannel *channel, const char *data,
+ size_t length, uint32_t ppid);
+ int32_t SendBinary(DataChannel *channel, const char *data,
+ size_t len, uint32_t ppid_partial, uint32_t ppid_final);
+ int32_t SendMsgCommon(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,
+ size_t length,
+ uint16_t stream);
+ void HandleOpenAckMessage(const struct rtcweb_datachannel_ack *ack,
+ size_t length, uint16_t stream);
+ void HandleUnknownMessage(uint32_t ppid, size_t length, uint16_t stream);
+ void HandleDataMessage(uint32_t ppid, const void *buffer, size_t length, uint16_t stream);
+ void HandleMessage(const void *buffer, size_t length, uint32_t ppid, uint16_t stream);
+ 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 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() {
+ bool on = false;
+ if (mSTS) {
+ mSTS->IsOnCurrentThread(&on);
+ }
+ return on;
+ }
+#endif
+
+ // Exists solely for proxying release of the TransportFlow to the STS thread
+ static void ReleaseTransportFlow(RefPtr<TransportFlow> aFlow) {}
+
+ // Data:
+ // NOTE: while this array will auto-expand, increases in the number of
+ // channels available from the stack must be negotiated!
+ bool mAllocateEven;
+ AutoTArray<RefPtr<DataChannel>,16> mStreams;
+ nsDeque mPending; // Holds addref'ed DataChannel's -- careful!
+ // holds data that's come in before a channel is open
+ nsTArray<nsAutoPtr<QueuedDataMessage>> mQueuedData;
+
+ // Streams pending reset
+ AutoTArray<uint16_t,4> mStreamsResetting;
+
+ struct socket *mMasterSocket; // accessed from STS thread
+ struct socket *mSocket; // cloned from mMasterSocket on successful Connect on STS thread
+ uint16_t mState; // Protected with mLock
+
+#ifdef SCTP_DTLS_SUPPORTED
+ RefPtr<TransportFlow> mTransportFlow;
+ nsCOMPtr<nsIEventTarget> mSTS;
+#endif
+ uint16_t mLocalPort; // Accessed from connect thread
+ uint16_t mRemotePort;
+ bool mUsingDtls;
+
+ nsCOMPtr<nsIThread> mInternalIOThread;
+};
+
+#define ENSURE_DATACONNECTION \
+ do { MOZ_ASSERT(mConnection); if (!mConnection) { return; } } while (0)
+
+#define ENSURE_DATACONNECTION_RET(x) \
+ do { MOZ_ASSERT(mConnection); if (!mConnection) { return (x); } } while (0)
+
+class DataChannel {
+public:
+ enum {
+ CONNECTING = 0U,
+ OPEN = 1U,
+ CLOSING = 2U,
+ CLOSED = 3U,
+ WAITING_TO_OPEN = 4U
+ };
+
+ DataChannel(DataChannelConnection *connection,
+ uint16_t stream,
+ uint16_t state,
+ const nsACString& label,
+ const nsACString& protocol,
+ uint16_t policy, uint32_t value,
+ uint32_t flags,
+ DataChannelListener *aListener,
+ nsISupports *aContext)
+ : mListenerLock("netwerk::sctp::DataChannel")
+ , mListener(aListener)
+ , mContext(aContext)
+ , mConnection(connection)
+ , mLabel(label)
+ , mProtocol(protocol)
+ , mState(state)
+ , mReady(false)
+ , mStream(stream)
+ , mPrPolicy(policy)
+ , mPrValue(value)
+ , mFlags(flags)
+ , mIsRecvBinary(false)
+ , mBufferedThreshold(0) // default from spec
+ {
+ 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);
+
+ // Send a string
+ bool SendMsg(const nsACString &aMsg)
+ {
+ ENSURE_DATACONNECTION_RET(false);
+
+ if (mStream != INVALID_STREAM)
+ return (mConnection->SendMsg(mStream, aMsg) >= 0);
+ else
+ return false;
+ }
+
+ // Send a binary message (TypedArray)
+ bool SendBinaryMsg(const nsACString &aMsg)
+ {
+ ENSURE_DATACONNECTION_RET(false);
+
+ if (mStream != INVALID_STREAM)
+ return (mConnection->SendBinaryMsg(mStream, aMsg) >= 0);
+ else
+ return false;
+ }
+
+ // Send a binary blob
+ bool SendBinaryStream(nsIInputStream *aBlob, uint32_t msgLen)
+ {
+ ENSURE_DATACONNECTION_RET(false);
+
+ if (mStream != INVALID_STREAM)
+ return (mConnection->SendBlob(mStream, aBlob) == 0);
+ else
+ return false;
+ }
+
+ uint16_t GetType() { return mPrPolicy; }
+
+ bool GetOrdered() { return !(mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED); }
+
+ // Amount of data buffered to send
+ uint32_t GetBufferedAmount()
+ {
+ if (!mConnection) {
+ return 0;
+ }
+
+ MutexAutoLock lock(mConnection->mLock);
+ return GetBufferedAmountLocked();
+ }
+
+
+ // Trigger amount for generating BufferedAmountLow events
+ uint32_t GetBufferedAmountLowThreshold();
+ void SetBufferedAmountLowThreshold(uint32_t aThreshold);
+
+ // Find out state
+ uint16_t GetReadyState()
+ {
+ if (mConnection) {
+ MutexAutoLock lock(mConnection->mLock);
+ if (mState == WAITING_TO_OPEN)
+ return CONNECTING;
+ return mState;
+ }
+ return CLOSED;
+ }
+
+ void GetLabel(nsAString& aLabel) { CopyUTF8toUTF16(mLabel, aLabel); }
+ void GetProtocol(nsAString& aProtocol) { CopyUTF8toUTF16(mProtocol, aProtocol); }
+ uint16_t GetStream() { return mStream; }
+
+ void AppReady();
+
+ void SendOrQueue(DataChannelOnMessageAvailable *aMessage);
+
+protected:
+ Mutex mListenerLock; // protects mListener and mContext
+ DataChannelListener *mListener;
+ nsCOMPtr<nsISupports> mContext;
+
+private:
+ friend class DataChannelOnMessageAvailable;
+ friend class DataChannelConnection;
+
+ nsresult AddDataToBinaryMsg(const char *data, uint32_t size);
+ uint32_t GetBufferedAmountLocked() const;
+
+ RefPtr<DataChannelConnection> mConnection;
+ nsCString mLabel;
+ nsCString mProtocol;
+ uint16_t mState;
+ bool mReady;
+ uint16_t mStream;
+ uint16_t mPrPolicy;
+ uint32_t mPrValue;
+ uint32_t mFlags;
+ uint32_t mId;
+ bool mIsRecvBinary;
+ size_t mBufferedThreshold;
+ nsCString mRecvBuffer;
+ nsTArray<nsAutoPtr<BufferedMsg>> mBufferedData; // GUARDED_BY(mConnection->mLock)
+ nsTArray<nsCOMPtr<nsIRunnable>> mQueuedMessages;
+};
+
+// 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_CHANNEL_OPEN,
+ ON_CHANNEL_CLOSED,
+ ON_DATA,
+ BUFFER_LOW_THRESHOLD,
+ NO_LONGER_BUFFERED,
+ }; /* types */
+
+ DataChannelOnMessageAvailable(int32_t aType,
+ DataChannelConnection *aConnection,
+ DataChannel *aChannel,
+ nsCString &aData, // XXX this causes inefficiency
+ int32_t aLen)
+ : mType(aType),
+ mChannel(aChannel),
+ mConnection(aConnection),
+ mData(aData),
+ mLen(aLen) {}
+
+ DataChannelOnMessageAvailable(int32_t aType,
+ DataChannel *aChannel)
+ : mType(aType),
+ mChannel(aChannel) {}
+ // XXX is it safe to leave mData/mLen 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)
+ : mType(aType),
+ mChannel(aChannel),
+ mConnection(aConnection) {}
+
+ // for ON_CONNECTION/ON_DISCONNECTED
+ DataChannelOnMessageAvailable(int32_t aType,
+ DataChannelConnection *aConnection)
+ : 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:
+ case ON_CHANNEL_OPEN:
+ case ON_CHANNEL_CLOSED:
+ case BUFFER_LOW_THRESHOLD:
+ case NO_LONGER_BUFFERED:
+ {
+ MutexAutoLock lock(mChannel->mListenerLock);
+ if (!mChannel->mListener) {
+ DATACHANNEL_LOG(("DataChannelOnMessageAvailable (%d) with null Listener!",mType));
+ return NS_OK;
+ }
+
+ switch (mType) {
+ case ON_DATA:
+ if (mLen < 0) {
+ mChannel->mListener->OnMessageAvailable(mChannel->mContext, mData);
+ } else {
+ mChannel->mListener->OnBinaryMessageAvailable(mChannel->mContext, mData);
+ }
+ break;
+ case ON_CHANNEL_OPEN:
+ mChannel->mListener->OnChannelConnected(mChannel->mContext);
+ break;
+ case ON_CHANNEL_CLOSED:
+ mChannel->mListener->OnChannelClosed(mChannel->mContext);
+ break;
+ case BUFFER_LOW_THRESHOLD:
+ mChannel->mListener->OnBufferLow(mChannel->mContext);
+ break;
+ case NO_LONGER_BUFFERED:
+ mChannel->mListener->NotBuffered(mChannel->mContext);
+ break;
+ }
+ break;
+ }
+ case ON_DISCONNECTED:
+ // If we've disconnected, make sure we close all the streams - from mainthread!
+ mConnection->CloseAll();
+ MOZ_FALLTHROUGH;
+ case ON_CHANNEL_CREATED:
+ case ON_CONNECTION:
+ // WeakPtr - only used/modified/nulled from MainThread so we can use a WeakPtr here
+ if (!mConnection->mListener) {
+ DATACHANNEL_LOG(("DataChannelOnMessageAvailable (%d) with null Listener",mType));
+ return NS_OK;
+ }
+ switch (mType) {
+ case ON_CHANNEL_CREATED:
+ // important to give it an already_AddRefed pointer!
+ mConnection->mListener->NotifyDataChannel(mChannel.forget());
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ return NS_OK;
+ }
+
+private:
+ ~DataChannelOnMessageAvailable() {}
+
+ int32_t mType;
+ // XXX should use union
+ RefPtr<DataChannel> mChannel;
+ RefPtr<DataChannelConnection> mConnection;
+ nsCString mData;
+ int32_t mLen;
+};
+
+}
+
+#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..f48aac10b3
--- /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() {}
+
+ // 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;
+};
+
+}
+
+#endif
diff --git a/netwerk/sctp/datachannel/DataChannelLog.h b/netwerk/sctp/datachannel/DataChannelLog.h
new file mode 100644
index 0000000000..07c8ad78bc
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannelLog.h
@@ -0,0 +1,20 @@
+/* -*- 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;
+}
+
+#undef LOG
+#define LOG(args) MOZ_LOG(mozilla::gDataChannelLog, mozilla::LogLevel::Debug, args)
+
+#endif
diff --git a/netwerk/sctp/datachannel/DataChannelProtocol.h b/netwerk/sctp/datachannel/DataChannelProtocol.h
new file mode 100644
index 0000000000..74fbe581c8
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannelProtocol.h
@@ -0,0 +1,80 @@
+/* -*- 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
+
+// Duplicated in fsm.def
+#define WEBRTC_DATACHANNEL_STREAMS_DEFAULT 256
+
+#define DATA_CHANNEL_PPID_CONTROL 50
+#define DATA_CHANNEL_PPID_BINARY 52
+#define DATA_CHANNEL_PPID_BINARY_LAST 53
+#define DATA_CHANNEL_PPID_DOMSTRING 54
+#define DATA_CHANNEL_PPID_DOMSTRING_LAST 51
+
+#define DATA_CHANNEL_MAX_BINARY_FRAGMENT 0x4000
+
+#define DATA_CHANNEL_FLAGS_SEND_REQ 0x00000001
+#define DATA_CHANNEL_FLAGS_SEND_RSP 0x00000002
+#define DATA_CHANNEL_FLAGS_SEND_ACK 0x00000004
+#define DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED 0x00000008
+#define DATA_CHANNEL_FLAGS_SEND_DATA 0x00000010
+#define DATA_CHANNEL_FLAGS_FINISH_OPEN 0x00000020
+#define DATA_CHANNEL_FLAGS_FINISH_RSP 0x00000040
+#define DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED 0x00000080
+#define DATA_CHANNEL_FLAGS_WAITING_ACK 0x00000100
+
+#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..fc59da229c
--- /dev/null
+++ b/netwerk/sctp/datachannel/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/.
+
+EXPORTS.mozilla.net += [
+ 'DataChannel.h',
+ 'DataChannelListener.h',
+ 'DataChannelProtocol.h'
+]
+
+SOURCES += [
+ 'DataChannel.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/media/mtransport',
+ '/netwerk/sctp/src',
+]
+
+DEFINES['INET'] = 1
+DEFINES['SCTP_DEBUG'] = 1
+
+if CONFIG['OS_TARGET'] != 'Android':
+ DEFINES['INET6'] = 1
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ DEFINES['__Userspace_os_Windows'] = 1
+else:
+ DEFINES['__Userspace_os_%s' % CONFIG['OS_TARGET']] = 1
+
+NO_PGO = True # Don't PGO
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/sctp/sctp_update.log b/netwerk/sctp/sctp_update.log
new file mode 100644
index 0000000000..229c7b9571
--- /dev/null
+++ b/netwerk/sctp/sctp_update.log
@@ -0,0 +1,18 @@
+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
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..f24702feef
--- /dev/null
+++ b/netwerk/sctp/src/moz.build
@@ -0,0 +1,97 @@
+# -*- 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',
+]
+
+if CONFIG['OS_TARGET'] == 'Android':
+ SOURCES += [
+ 'ifaddrs_android.cpp',
+ ]
+
+Library('nksctp_s')
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+# We allow warnings for third-party code that can be updated from upstream.
+ALLOW_COMPILER_WARNINGS = True
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/netwerk/base',
+]
+
+for var in ('SCTP_SIMPLE_ALLOCATOR',
+ 'SCTP_PROCESS_LEVEL_LOCKS', '__Userspace__', 'INET',
+ 'CALLBACK_API', 'SCTP_DEBUG'):
+ DEFINES[var] = 1
+
+# Android NDK r5c, used on the builders at the time of this writing, doesn't
+# have the headers we need for IPv6
+if CONFIG['OS_TARGET'] != 'Android':
+ DEFINES['INET6'] = 1
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ DEFINES['__Userspace_os_Windows'] = 1
+ DEFINES['_LIB'] = 1
+elif CONFIG['OS_TARGET'] == 'Android':
+ DEFINES['__Userspace_os_Linux'] = 1
+else:
+ DEFINES['__Userspace_os_%s' % CONFIG['OS_TARGET']] = 1
+
+if CONFIG['OS_TARGET'] == 'Darwin':
+ DEFINES['__APPLE_USE_RFC_2292'] = 1
+ DEFINES['__APPLE__'] = False
+
+if CONFIG['OS_TARGET'] in ('Linux', 'Android'):
+ # to make sure that in6_pktinfo gets defined on all distros
+ DEFINES['_GNU_SOURCE'] = True
+
+if CONFIG['OS_TARGET'] == 'FreeBSD':
+ DEFINES['__FreeBSD__'] = False
+
+if CONFIG['OS_TARGET'] == 'NetBSD':
+ DEFINES['__NetBSD__'] = False
+
+if CONFIG['OS_TARGET'] == 'OpenBSD':
+ DEFINES['__OpenBSD__'] = False
+
+if CONFIG['OS_TARGET'] == 'DragonFly':
+ DEFINES['__DragonFly__'] = False
+
+NO_PGO = True # Don't PGO
+
+if CONFIG['GNU_CC']:
+ CFLAGS += ['-std=gnu99']
diff --git a/netwerk/sctp/src/netinet/sctp.h b/netwerk/sctp/src/netinet/sctp.h
new file mode 100755
index 0000000000..a5ff4eb0bc
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp.h
@@ -0,0 +1,659 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp.h 279859 2015-03-10 19:49:25Z tuexen $");
+#endif
+
+#ifndef _NETINET_SCTP_H_
+#define _NETINET_SCTP_H_
+
+#if (defined(__APPLE__) || defined(__Userspace_os_Linux) || defined(__Userspace_os_Darwin))
+#include <stdint.h>
+#endif
+
+#include <sys/types.h>
+
+
+#if !defined(__Userspace_os_Windows)
+#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 */
+/* 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__)
+/* temporary workaround for Apple listen() issue, no args used */
+#define SCTP_LISTEN_FIX 0x0000800c
+#endif
+#if defined(__Windows__)
+/* 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_ERROR_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_ERROR_MISSING_PARAM */
+ uint32_t num_missing_params; /* number of missing parameters */
+ /* uint16_t param_type's follow */
+} SCTP_PACKED;
+
+struct sctp_error_stale_cookie {
+ struct sctp_error_cause cause; /* code=SCTP_ERROR_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_ERROR_OUT_OF_RESOURCES */
+} SCTP_PACKED;
+
+struct sctp_error_unresolv_addr {
+ struct sctp_error_cause cause; /* code=SCTP_ERROR_UNRESOLVABLE_ADDR */
+
+} SCTP_PACKED;
+
+struct sctp_error_unrecognized_chunk {
+ struct sctp_error_cause cause; /* code=SCTP_ERROR_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;
+
+/*
+ * 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 ***********/
+/************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
+
+
+/* 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_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
+#define SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE 0x0000000000000080
+/* 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
+
+
+#define SCTP_SMALLEST_PMTU 512 /* smallest pmtu allowed when disabling PMTU discovery */
+
+#if defined(__Userspace_os_Windows)
+#pragma pack()
+#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..438879885e
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_asconf.c
@@ -0,0 +1,3522 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_asconf.c 277347 2015-01-18 20:53:20Z 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
+ */
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 1
+#endif
+
+/*
+ * 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;
+ uint8_t *tlv;
+
+ m_reply = sctp_get_mbuf_for_msg((sizeof(struct sctp_asconf_paramhdr) +
+ tlv_length +
+ sizeof(struct sctp_error_cause)),
+ 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 *);
+ error = (struct sctp_error_cause *)(aph + 1);
+
+ aph->correlation_id = id;
+ aph->ph.param_type = htons(SCTP_ERROR_CAUSE_IND);
+ error->code = htons(cause);
+ error->length = tlv_length + sizeof(struct sctp_error_cause);
+ aph->ph.param_length = error->length +
+ sizeof(struct sctp_asconf_paramhdr);
+
+ if (aph->ph.param_length > MLEN) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_error_response: tlv_length (%xh) too big\n",
+ tlv_length);
+ sctp_m_freem(m_reply); /* discard */
+ return (NULL);
+ }
+ if (error_tlv != NULL) {
+ tlv = (uint8_t *) (error + 1);
+ memcpy(tlv, error_tlv, tlv_length);
+ }
+ SCTP_BUF_LEN(m_reply) = aph->ph.param_length;
+ error->length = htons(error->length);
+ aph->ph.param_length = htons(aph->ph.param_length);
+
+ 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);
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+#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;
+ bzero(sin, 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;
+ bzero(sin6, 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);
+ }
+ /* 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, 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 {
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_ADD_IP, stcb, 0, sa, SCTP_SO_NOT_LOCKED);
+ if (response_required) {
+ m_reply =
+ sctp_asconf_success_response(aph->correlation_id);
+ }
+ 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;
+
+ /* 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(net, &stcb->asoc.nets, sctp_next) {
+ if (net != src_net) {
+ /* delete this address */
+ sctp_remove_net(stcb, net);
+ 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);
+ }
+ }
+ 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);
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+#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;
+ bzero(sin, 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;
+ bzero(sin6, 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);
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+#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;
+ bzero(sin, 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;
+ bzero(sin6, 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_TIMER+SCTP_LOC_7);
+ 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,
+ stcb->asoc.deleted_primary);
+ }
+ } 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 */
+ return;
+ }
+ /* param_length is already validated in process_control... */
+ offset += ntohs(p_addr->ph.param_length); /* skip lookup addr */
+ /* 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);
+ }
+ /* 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;
+ /* update lengths, make sure it's aligned too */
+ SCTP_BUF_LEN(m_result) = SCTP_SIZE32(SCTP_BUF_LEN(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, struct sctp_nets *net)
+{
+ /*
+ * clear out any existing asconfs going out
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, stcb, net,
+ 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 */
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+ /* 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_TIMER+SCTP_LOC_8);
+ 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;
+
+ chk = TAILQ_FIRST(&stcb->asoc.sent_queue);
+ 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_TIMER+SCTP_LOC_5);
+ 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 (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;
+ }
+ /* 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 (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;
+ }
+ /* 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);
+ if (net->ro.ro_rt == NULL)
+ 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 /* __FreeBSD__ __APPLE__ __Userspace__ */
+
+/*
+ * 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 /* __FreeBSD__ __APPLE__ __Userspace__ */
+ /* 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;
+
+ /* 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 &&
+ (sctp_local_addr_count(stcb) < 2)) {
+ /* 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: queing 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)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf_ack: got unexpected next serial number! Aborting asoc!\n");
+ sctp_abort_an_association(stcb->sctp_ep, stcb, NULL, 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, net,
+ SCTP_FROM_SCTP_ASCONF+SCTP_LOC_3);
+ }
+
+ /* 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 */
+ ack_length -= SCTP_SIZE32(param_length);
+ if (ack_length <= 0) {
+ /* no more data in the mbuf chain */
+ 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__)
+ 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__)
+ 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;
+ struct in6pcb *inp6;
+
+ inp6 = (struct in6pcb *)&inp->ip_inp.inp;
+ /* invalid if we are a v6 only endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp6))
+ 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->asoc) == SCTP_STATE_OPEN) {
+#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 */
+ struct in6pcb *inp6;
+ inp6 = (struct in6pcb *)&inp->ip_inp.inp;
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp6)) {
+ 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__)
+ 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 in6pcb *inp6;
+ struct sockaddr_in *sin;
+
+ inp6 = (struct in6pcb *)&inp->ip_inp.inp;
+ /* invalid if we are a v6 only endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp6))
+ continue;
+
+ sin = &ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* we skip unspecifed addresses */
+ continue;
+ }
+#if defined(__FreeBSD__)
+ 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(inp6)) {
+ 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) {
+ sctp_rtentry_t *rt;
+
+ /* 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;
+ rt = net->ro.ro_rt;
+ if (rt) {
+ RTFREE(rt);
+ net->ro.ro_rt = NULL;
+ }
+ /*
+ * 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->asoc) == SCTP_STATE_OPEN) {
+ 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->asoc) == 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
+ }
+ } 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);
+}
+
+void
+sctp_set_primary_ip_address(struct sctp_ifa *ifa)
+{
+ struct sctp_inpcb *inp;
+
+ /* go through all our PCB's */
+ LIST_FOREACH(inp, &SCTP_BASE_INFO(listhead), sctp_list) {
+ struct sctp_tcb *stcb;
+
+ /* process for all associations for this endpoint */
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ /* 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: queued on stcb=%p, ",
+ (void *)stcb);
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, &ifa->address.sa);
+ if (SCTP_GET_STATE(&stcb->asoc) == 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
+ }
+ }
+ } /* for each stcb */
+ } /* for each inp */
+}
+
+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__)
+ 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__)
+ 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 *);
+ bzero(acp, 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)));
+ bzero(lookup->addr, 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->asoc) ==
+ 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;
+ }
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "check_addr_list_ep: laddr->ifa->ifa_addr 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__)
+ 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__)
+ 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 *sctp_ifap)
+{
+ 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 (sctp_ifap) {
+ ifa = sctp_ifap;
+ } else 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;
+
+ 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);
+ (void)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);
+ }
+ 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__)
+ 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__)
+ 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..a3622e4128
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_asconf.h
@@ -0,0 +1,97 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_asconf.h 237715 2012-06-28 16:01:08Z 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 *, struct sctp_nets *);
+
+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, struct sctp_ifa *);
+
+
+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_set_primary_ip_address(struct sctp_ifa *ifa);
+
+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..50432ad8a5
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_auth.c
@@ -0,0 +1,2336 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_auth.c 271673 2014-09-16 14:20:33Z 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)
+{
+ bzero(chklist, 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 */
+ bcopy(list, new_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);
+ }
+ bcopy(key, new_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)) {
+ bcopy(shared->key, key_ptr, shared->keylen);
+ key_ptr += shared->keylen;
+ }
+ if (sctp_get_keylen(key1)) {
+ bcopy(key1->key, key_ptr, key1->keylen);
+ key_ptr += key1->keylen;
+ }
+ if (sctp_get_keylen(key2)) {
+ bcopy(key2->key, key_ptr, key2->keylen);
+ }
+ } else {
+ /* key is shared + key2 + key1 */
+ if (sctp_get_keylen(shared)) {
+ bcopy(shared->key, key_ptr, shared->keylen);
+ key_ptr += shared->keylen;
+ }
+ if (sctp_get_keylen(key2)) {
+ bcopy(key2->key, key_ptr, key2->keylen);
+ key_ptr += key2->keylen;
+ }
+ if (sctp_get_keylen(key1)) {
+ bcopy(key1->key, key_ptr, 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 (0);
+}
+
+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",
+ __FUNCTION__, (void *)stcb, key_id, skey->refcount);
+ }
+}
+
+void
+sctp_auth_key_release(struct sctp_tcb *stcb, uint16_t key_id, int so_locked
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+ sctp_sharedkey_t *skey;
+
+ /* find the shared key */
+ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, key_id);
+
+ /* decrement the ref count */
+ if (skey) {
+ sctp_free_sharedkey(skey);
+ SCTPDBG(SCTP_DEBUG_AUTH2,
+ "%s: stcb %p key %u refcount release to %d\n",
+ __FUNCTION__, (void *)stcb, key_id, skey->refcount);
+
+ /* see if a notification should be generated */
+ if ((skey->refcount <= 1) && (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",
+ __FUNCTION__, (void *)stcb, key_id, skey->refcount);
+ }
+ }
+}
+
+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) {
+ (void)sctp_insert_sharedkey(dest, new_skey);
+ 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);
+ list = NULL;
+ }
+}
+
+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]);
+ bcopy(&hmac_id, ptr, 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);
+ }
+ bzero(new_authinfo, 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 */
+ bzero(ipad, blocklen);
+ bzero(opad, blocklen);
+ bcopy(key, ipad, keylen);
+ bcopy(key, opad, 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 */
+ bzero(ipad, blocklen);
+ bzero(opad, blocklen);
+ bcopy(key, ipad, keylen);
+ bcopy(key, opad, 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);
+}
+
+/*-
+ * verify the HMAC digest using the desired hash key, text, and HMAC
+ * algorithm.
+ * Returns -1 on error, 0 on success.
+ */
+int
+sctp_verify_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 len;
+ uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX];
+
+ /* 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 */
+ return (-1);
+ }
+ len = sctp_get_hmac_digest_len(hmac_algo);
+ if ((len == 0) || (digestlen != len))
+ return (-1);
+
+ /* compute the expected hash */
+ if (sctp_hmac(hmac_algo, key, keylen, text, textlen, temp) != len)
+ return (-1);
+
+ if (memcmp(digest, temp, digestlen) != 0)
+ return (-1);
+ else
+ return (0);
+}
+
+
+/*
+ * 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;
+ bcopy(temp, key->key, 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;
+ bcopy(temp, key->key, 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 == 0) || (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, min(plen, sizeof(random_store)));
+ 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, min(plen,sizeof(hmacs_store)));
+ 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, min(plen,sizeof(chunks_store)));
+ 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;
+ bcopy(p_random, new_key->key, keylen);
+ }
+ /* append in the AUTH chunks */
+ if (chunks != NULL) {
+ bcopy(chunks, new_key->key + keylen,
+ sizeof(*chunks) + num_chunks);
+ keylen += sizeof(*chunks) + num_chunks;
+ }
+ /* append in the HMACs */
+ if (hmacs != NULL) {
+ bcopy(hmacs, new_key->key + keylen,
+ 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);
+ bzero(auth->hmac, 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_bzero_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)) {
+ bzero(data, SCTP_BUF_LEN(m_tmp));
+ size -= SCTP_BUF_LEN(m_tmp);
+ } else {
+ bzero(data, 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);
+
+ /* is the indicated HMAC supported? */
+ if (!sctp_auth_is_supported_hmac(stcb->asoc.local_hmacs, hmac_id)) {
+ struct mbuf *m_err;
+ struct sctp_auth_invalid_hmac *err;
+
+ 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
+ */
+ m_err = sctp_get_mbuf_for_msg(sizeof(*err), 0, M_NOWAIT,
+ 1, MT_HEADER);
+ if (m_err != NULL) {
+ /* pre-reserve some space */
+ SCTP_BUF_RESV_UF(m_err, sizeof(struct sctp_chunkhdr));
+ /* fill in the error */
+ err = mtod(m_err, struct sctp_auth_invalid_hmac *);
+ bzero(err, sizeof(*err));
+ err->ph.param_type = htons(SCTP_CAUSE_UNSUPPORTED_HMACID);
+ err->ph.param_length = htons(sizeof(*err));
+ err->hmac_id = ntohs(hmac_id);
+ SCTP_BUF_LEN(m_err) = sizeof(*err);
+ /* queue it */
+ sctp_queue_op_err(stcb, m_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 */
+ bcopy(auth->hmac, digest, digestlen);
+ sctp_bzero_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 (memcmp(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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+ 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->spec_flags = M_NOTIFICATION;
+ control->length = SCTP_BUF_LEN(m_notify);
+ /* 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, parm_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, &parm_buf, sizeof(parm_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_PARAM_BUFFER_SIZE];
+ int num_ent, i;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&local_store, min(plen,sizeof(local_store)));
+ 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) {
+ got_random = 1;
+ /* 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);
+ }
+ } else if (ptype == SCTP_HMAC_LIST) {
+ uint8_t store[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_auth_hmac_algo *hmacs;
+ int num_hmacs;
+
+ if (plen > sizeof(store))
+ break;
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)store, min(plen,sizeof(store)));
+ 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) {
+ int i, num_chunks;
+ uint8_t chunks_store[SCTP_SMALL_CHUNK_STORE];
+ /* did the peer send a non-empty chunk list? */
+ struct sctp_auth_chunk_list *chunks = NULL;
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)chunks_store,
+ min(plen,sizeof(chunks_store)));
+ 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, &parm_buf,
+ sizeof(parm_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..997389363d
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_auth.h
@@ -0,0 +1,216 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_auth.h 271673 2014-09-16 14:20:33Z 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 int sctp_verify_hmac(uint16_t hmac_algo, uint8_t *key, uint32_t keylen,
+ uint8_t *text, uint32_t textlen, uint8_t *digest, uint32_t digestlen);
+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..292941b533
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_bsd_addr.c
@@ -0,0 +1,1116 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_bsd_addr.c 276914 2015-01-10 20:49:57Z 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(ANDROID)
+#include <unistd.h>
+#include <ifaddrs-android-ext.h>
+#else
+#if defined(__FreeBSD__)
+#include <sys/unistd.h>
+#endif
+#endif
+
+/* Declare all of our malloc named types */
+#ifndef __Panda__
+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");
+#endif
+
+/* Global NON-VNET structure that controls the iterator */
+struct iterator_control sctp_it_ctl;
+
+#if !defined(__FreeBSD__)
+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(__Userspace_os_Windows)
+ 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)
+{
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+ /* In FreeBSD this thread never terminates. */
+#if defined(__FreeBSD__)
+ 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__) || defined(__Userspace_os_Darwin)
+ sctp_it_ctl.ipi_iterator_wq_mtx,
+#endif
+ 0, "waiting_for_work", 0);
+#else
+#if defined(__Userspace_os_Windows)
+ 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__)
+ if (sctp_it_ctl.iterator_flags & SCTP_ITERATOR_MUST_EXIT) {
+ break;
+ }
+#endif
+ sctp_iterator_worker();
+ }
+#if !defined(__FreeBSD__)
+ /* 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(__FreeBSD__)
+#if __FreeBSD_version <= 701000
+ kthread_create(sctp_iterator_thread,
+#else
+ kproc_create(sctp_iterator_thread,
+#endif
+ (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);
+#elif defined(__Userspace__)
+#if defined(__Userspace_os_Windows)
+ if ((sctp_it_ctl.thread_proc = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&sctp_iterator_thread, NULL, 0, NULL)) == NULL) {
+#else
+ if (pthread_create(&sctp_it_ctl.thread_proc, NULL, &sctp_iterator_thread, NULL)) {
+#endif
+ SCTP_PRINTF("ERROR: Creating sctp_iterator_thread failed.\n");
+ }
+#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__)
+ 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__)
+ 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__)
+int
+sctp_is_vmware_interface(struct ifnet *ifn)
+{
+ return (strncmp(ifnet_name(ifn), "vmnet", 5) == 0);
+}
+#endif
+
+#if defined(__Userspace_os_Windows)
+static void
+sctp_init_ifns_for_vrf(int vrfid)
+{
+#if defined(INET) || defined(INET6)
+ struct ifaddrs *ifa;
+ 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);
+ 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;
+ }
+ ifa = (struct ifaddrs*)malloc(sizeof(struct ifaddrs));
+ ifa->ifa_name = _strdup(pAdapt->AdapterName);
+ ifa->ifa_flags = pAdapt->Flags;
+ ifa->ifa_addr = (struct sockaddr *)malloc(sizeof(struct sockaddr_in));
+ memcpy(ifa->ifa_addr, pUnicast->Address.lpSockaddr, sizeof(struct sockaddr_in));
+
+ sctp_ifa = sctp_add_addr_to_vrf(0,
+ ifa,
+ pAdapt->IfIndex,
+ (pAdapt->IfType == IF_TYPE_IEEE80211)?MIB_IF_TYPE_ETHERNET:pAdapt->IfType,
+ ifa->ifa_name,
+ (void *)ifa,
+ ifa->ifa_addr,
+ ifa->ifa_flags,
+ 0);
+ if (sctp_ifa) {
+ sctp_ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ }
+ }
+ }
+ if (pAdapterAddrs)
+ GlobalFree(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);
+ 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) {
+ ifa = (struct ifaddrs*)malloc(sizeof(struct ifaddrs));
+ ifa->ifa_name = _strdup(pAdapt->AdapterName);
+ ifa->ifa_flags = pAdapt->Flags;
+ ifa->ifa_addr = (struct sockaddr *)malloc(sizeof(struct sockaddr_in6));
+ memcpy(ifa->ifa_addr, pUnicast->Address.lpSockaddr, sizeof(struct sockaddr_in6));
+ sctp_ifa = sctp_add_addr_to_vrf(0,
+ ifa,
+ pAdapt->Ipv6IfIndex,
+ (pAdapt->IfType == IF_TYPE_IEEE80211)?MIB_IF_TYPE_ETHERNET:pAdapt->IfType,
+ ifa->ifa_name,
+ (void *)ifa,
+ ifa->ifa_addr,
+ ifa->ifa_flags,
+ 0);
+ if (sctp_ifa) {
+ sctp_ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ }
+ }
+ }
+ if (pAdapterAddrs)
+ GlobalFree(pAdapterAddrs);
+#endif
+}
+#elif defined(__Userspace__)
+static void
+sctp_init_ifns_for_vrf(int vrfid)
+{
+#if defined(INET) || defined(INET6)
+ int rc;
+ struct ifaddrs *ifa = NULL;
+ struct sctp_ifa *sctp_ifa;
+ uint32_t ifa_flags;
+
+ rc = getifaddrs(&g_interfaces);
+ if (rc != 0) {
+ return;
+ }
+ for (ifa = g_interfaces; 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,
+ ifa,
+ if_nametoindex(ifa->ifa_name),
+ 0,
+ ifa->ifa_name,
+ (void *)ifa,
+ ifa->ifa_addr,
+ ifa_flags,
+ 0);
+ if (sctp_ifa) {
+ sctp_ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ }
+#endif
+}
+#endif
+
+#if defined(__APPLE__)
+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;
+ }
+ snprintf(name, SCTP_IFNAMSIZ, "%s%d", ifnet_name(ifn), ifnet_unit(ifn));
+ sctp_ifa = sctp_add_addr_to_vrf(vrfid,
+ (void *)ifn,
+ ifnet_index(ifn),
+ ifnet_type(ifn),
+ name,
+ (void *)ifa,
+ 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__)
+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 *ifn;
+ struct ifaddr *ifa;
+ struct sctp_ifa *sctp_ifa;
+ uint32_t ifa_flags;
+#ifdef INET6
+ struct in6_ifaddr *ifa6;
+#endif
+
+ IFNET_RLOCK();
+ TAILQ_FOREACH(ifn, &MODULE_GLOBAL(ifnet), if_list) {
+ if (sctp_is_desired_interface_type(ifn) == 0) {
+ /* non desired type */
+ continue;
+ }
+#if (__FreeBSD_version >= 803000 && __FreeBSD_version < 900000) || __FreeBSD_version > 900000
+ IF_ADDR_RLOCK(ifn);
+#else
+ IF_ADDR_LOCK(ifn);
+#endif
+ TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) {
+ 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;
+ }
+ }
+#if (__FreeBSD_version >= 803000 && __FreeBSD_version < 900000) || __FreeBSD_version > 900000
+ IF_ADDR_RUNLOCK(ifn);
+#else
+ IF_ADDR_UNLOCK(ifn);
+#endif
+ }
+ 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;
+ /* 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 managment 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__)
+ 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__)
+ 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__)
+void
+sctp_add_or_del_interfaces(int (*pred)(struct ifnet *), int add)
+{
+ struct ifnet *ifn;
+ struct ifaddr *ifa;
+
+ IFNET_RLOCK();
+ TAILQ_FOREACH(ifn, &MODULE_GLOBAL(ifnet), if_list) {
+ if (!(*pred)(ifn)) {
+ continue;
+ }
+ TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) {
+ sctp_addr_change(ifa, add ? RTM_ADD : RTM_DELETE);
+ }
+ }
+ IFNET_RUNLOCK();
+}
+#endif
+#if defined(__APPLE__)
+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(__Userspace__)
+
+ /*
+ * __Userspace__
+ * Using m_clget, which creates and mbuf and a cluster and
+ * hooks those together.
+ * TODO: This does not yet have functionality for jumbo packets.
+ *
+ */
+
+ int mbuf_threshold;
+ if (want_header) {
+ MGETHDR(m, how, type);
+ } else {
+ MGET(m, how, type);
+ }
+ if (m == NULL) {
+ return (NULL);
+ }
+ if (allonebuf == 0)
+ mbuf_threshold = SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count);
+ else
+ mbuf_threshold = 1;
+
+
+ if ((int)space_needed > (((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);
+ }
+ }
+ SCTP_BUF_LEN(m) = 0;
+ SCTP_BUF_NEXT(m) = SCTP_BUF_NEXT_PKT(m) = NULL;
+
+ /* __Userspace__
+ * Check if anything need to be done to ensure logging works
+ */
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mb(m, SCTP_MBUF_IALLOC);
+ }
+#endif
+#elif defined(__FreeBSD__) && __FreeBSD_version > 1100052
+ m = m_getm2(NULL, space_needed, how, type, want_header ? M_PKTHDR : 0);
+ if (m == NULL) {
+ /* bad, no memory */
+ return (m);
+ }
+ if (allonebuf) {
+ if (SCTP_BUF_SIZE(m) < space_needed) {
+ m_freem(m);
+ return (NULL);
+ }
+ }
+ if (SCTP_BUF_NEXT(m)) {
+ sctp_m_freem(SCTP_BUF_NEXT(m));
+ SCTP_BUF_NEXT(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
+#elif defined(__FreeBSD__) && __FreeBSD_version > 602000
+ m = m_getm2(NULL, space_needed, how, type, want_header ? M_PKTHDR : 0);
+ if (m == NULL) {
+ /* bad, no memory */
+ return (m);
+ }
+ if (allonebuf) {
+ int siz;
+ if (SCTP_BUF_IS_EXTENDED(m)) {
+ siz = SCTP_BUF_EXTEND_SIZE(m);
+ } else {
+ if (want_header)
+ siz = MHLEN;
+ else
+ siz = MLEN;
+ }
+ if (siz < space_needed) {
+ m_freem(m);
+ return (NULL);
+ }
+ }
+ if (SCTP_BUF_NEXT(m)) {
+ sctp_m_freem(SCTP_BUF_NEXT(m));
+ SCTP_BUF_NEXT(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
+#else
+#if defined(__FreeBSD__) && __FreeBSD_version >= 601000
+ int aloc_size;
+ int index = 0;
+#endif
+ int mbuf_threshold;
+ if (want_header) {
+ MGETHDR(m, how, type);
+ } else {
+ MGET(m, how, type);
+ }
+ if (m == NULL) {
+ return (NULL);
+ }
+ if (allonebuf == 0)
+ mbuf_threshold = SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count);
+ else
+ mbuf_threshold = 1;
+
+
+ if (space_needed > (((mbuf_threshold - 1) * MLEN) + MHLEN)) {
+#if defined(__FreeBSD__) && __FreeBSD_version >= 601000
+ try_again:
+ index = 4;
+ if (space_needed <= MCLBYTES) {
+ aloc_size = MCLBYTES;
+ } else {
+ aloc_size = MJUMPAGESIZE;
+ index = 5;
+ }
+ m_cljget(m, how, aloc_size);
+ if (m == NULL) {
+ return (NULL);
+ }
+ if (SCTP_BUF_IS_EXTENDED(m) == 0) {
+ if ((aloc_size != MCLBYTES) &&
+ (allonebuf == 0)) {
+ aloc_size -= 10;
+ goto try_again;
+ }
+ sctp_m_freem(m);
+ return (NULL);
+ }
+#else
+ MCLGET(m, how);
+ if (m == NULL) {
+ return (NULL);
+ }
+ if (SCTP_BUF_IS_EXTENDED(m) == 0) {
+ sctp_m_freem(m);
+ return (NULL);
+ }
+#endif
+ }
+ 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..8373b3b834
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_bsd_addr.h
@@ -0,0 +1,69 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_bsd_addr.h 237540 2012-06-24 21:25:54Z 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
+
+#if !defined(__Panda__)
+void sctp_addr_change(struct ifaddr *ifa, int cmd);
+#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..3174e3fd4b
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_callout.c
@@ -0,0 +1,220 @@
+/*-
+ * 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 (__Userspace_os_Windows)
+#include <sys/wait.h>
+#include <unistd.h>
+#include <pthread.h>
+#endif
+#if defined(__Userspace_os_NaCl)
+#include <sys/select.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.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
+
+/*
+ * Callout/Timer routines for OS that doesn't have them
+ */
+#if defined(__APPLE__) || defined(__Userspace__)
+int ticks = 0;
+#else
+extern int ticks;
+#endif
+
+/*
+ * 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)
+{
+ bzero(c, sizeof(*c));
+}
+
+void
+sctp_os_timer_start(sctp_os_timer_t *c, int to_ticks, void (*ftn) (void *),
+ void *arg)
+{
+ /* paranoia */
+ if ((c == NULL) || (ftn == NULL))
+ return;
+
+ SCTP_TIMERQ_LOCK();
+ /* check to see if we're rescheduling a timer */
+ if (c->c_flags & 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);
+ /*
+ * 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();
+}
+
+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);
+}
+
+static void
+sctp_handle_tick(int delta)
+{
+ sctp_os_timer_t *c;
+ void (*c_func)(void *);
+ void *c_arg;
+
+ SCTP_TIMERQ_LOCK();
+ /* update our tick count */
+ ticks += delta;
+ c = TAILQ_FIRST(&SCTP_BASE_INFO(callqueue));
+ while (c) {
+ if (c->c_time <= ticks) {
+ 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__)
+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)
+{
+ for (;;) {
+#if defined (__Userspace_os_Windows)
+ Sleep(TIMEOUT_INTERVAL);
+#else
+ struct timeval timeout;
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1000 * TIMEOUT_INTERVAL;
+ select(0, NULL, NULL, NULL, &timeout);
+#endif
+ if (SCTP_BASE_VAR(timer_thread_should_exit)) {
+ break;
+ }
+ sctp_handle_tick(MSEC_TO_TICKS(TIMEOUT_INTERVAL));
+ }
+ return (NULL);
+}
+
+void
+sctp_start_timer(void)
+{
+ /*
+ * No need to do SCTP_TIMERQ_LOCK_INIT();
+ * here, it is being done in sctp_pcb_init()
+ */
+#if defined (__Userspace_os_Windows)
+ if ((SCTP_BASE_VAR(timer_thread) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)user_sctp_timer_iterate, NULL, 0, NULL)) == NULL) {
+ SCTP_PRINTF("ERROR; Creating ithread failed\n");
+ }
+#else
+ int rc;
+
+ rc = pthread_create(&SCTP_BASE_VAR(timer_thread), NULL, user_sctp_timer_iterate, NULL);
+ if (rc) {
+ SCTP_PRINTF("ERROR; return code from pthread_create() is %d\n", rc);
+ }
+#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..c53c5a4fc7
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_callout.h
@@ -0,0 +1,103 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#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(__Userspace_os_Windows)
+#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
+#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))
+#define SCTP_TIMERQ_LOCK_INIT() (void)pthread_mutex_init(&SCTP_BASE_VAR(timer_mtx), NULL)
+#define SCTP_TIMERQ_LOCK_DESTROY() (void)pthread_mutex_destroy(&SCTP_BASE_VAR(timer_mtx))
+#endif
+
+extern int ticks;
+#endif
+
+TAILQ_HEAD(calloutlist, sctp_callout);
+
+struct sctp_callout {
+ TAILQ_ENTRY(sctp_callout) tqe;
+ int 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);
+void sctp_os_timer_start(sctp_os_timer_t *, int, void (*)(void *), void *);
+int sctp_os_timer_stop(sctp_os_timer_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(void);
+#endif
+#if defined(__APPLE__)
+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..0e01c9860d
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_cc_functions.c
@@ -0,0 +1,2503 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_cc_functions.c 279859 2015-03-10 19:49:25Z 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__) && __FreeBSD_version >= 803000
+#include <netinet/sctp_dtrace_declare.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__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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_seq - 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_seq - 1;
+ }
+
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA+SCTP_LOC_32 );
+ 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 /* Loosing to other flows */
+#define SCTP_INST_NEUTRAL 2 /* Neutral, no indication */
+#define SCTP_INST_GAINING 3 /* Gaining, step down possible */
+
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 803000
+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__) && __FreeBSD_version >= 803000
+ uint64_t oth, probepoint;
+#endif
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ /* Probe point 5 */
+ probepoint |= ((5 << 16) | 1);
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ 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_PROBE(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__) && __FreeBSD_version >= 803000
+ /* Probe point 6 */
+ probepoint |= ((6 << 16) | 0);
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ 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_PROBE(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__) && __FreeBSD_version >= 803000
+ /* Probe point 7 */
+ probepoint |= ((7 << 16) | net->cc_mod.rtcc.ret_from_eq);
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+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__) && __FreeBSD_version >= 803000
+ uint64_t oth, probepoint;
+#endif
+
+ /* Bandwidth decreased.*/
+#if defined(__FreeBSD__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ /* PROBE POINT 1 */
+ probepoint |= ((1 << 16) | 1);
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ /* Probe point 2 */
+ probepoint |= ((2 << 16) | 0);
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ 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_PROBE(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__) && __FreeBSD_version >= 803000
+ /* Probe point 3 */
+ probepoint |= ((3 << 16) | 0);
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ 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_PROBE(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__) && __FreeBSD_version >= 803000
+ /* Probe point 4 */
+ probepoint |= ((4 << 16) | 0);
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ 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_PROBE(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__) && __FreeBSD_version >= 803000
+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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ /* PROBE POINT 0 */
+ probepoint = (((uint64_t)net->cwnd) << 32);
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ 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_PROBE(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 Algoritm 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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ probepoint |= ((0xb << 16) | inst_ind);
+#endif
+ } else {
+ inst_ind = net->cc_mod.rtcc.last_inst_ind;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ inst_bw = bytes_for_this_rtt;
+ /* Can't determine do not change */
+ probepoint |= ((0xd << 16) | inst_ind);
+#endif
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ 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_PROBE(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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ 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__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+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__) && __FreeBSD_version >= 803000
+ int old_cwnd;
+
+ old_cwnd = net->cwnd;
+#endif
+ net->cwnd = net->mtu;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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 orginal 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__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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__) && __FreeBSD_version >= 803000
+ SDT_PROBE(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 algoritm */
+ 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 algoritm */
+ 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 algoritms 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__) && __FreeBSD_version >= 803000
+ uint64_t vtag, probepoint;
+
+#endif
+ if (net->cc_mod.rtcc.lbw) {
+#if defined(__FreeBSD__) && __FreeBSD_version >= 803000
+ /* 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_PROBE(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__) && __FreeBSD_version >= 803000
+ uint64_t vtag, probepoint;
+
+#endif
+ sctp_set_initial_cc_param(stcb, net);
+ stcb->asoc.use_precise_time = 1;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 803000
+ 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_PROBE(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 algoritm */
+ 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;
+ int32_t increase;
+ int32_t drop_percent;
+};
+
+#define SCTP_HS_TABLE_SIZE 73
+
+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 = ((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) *
+ 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_seq - 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_seq - 1;
+ }
+
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA+SCTP_LOC_32);
+ 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 (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+MSEC_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 > (uint32_t)MSEC_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_seq - 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_seq - 1;
+ }
+
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND,
+ stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA+SCTP_LOC_32);
+ 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);
+ }
+ }
+}
+
+struct sctp_cc_functions sctp_cc_functions[] = {
+{
+#if defined(__Windows__) || defined(__Userspace_os_Windows)
+ 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(__Windows__) || defined(__Userspace_os_Windows)
+ 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(__Windows__) || defined(__Userspace_os_Windows)
+ 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(__Windows__) || defined(__Userspace_os_Windows)
+ 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..ff4874d9df
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_constants.h
@@ -0,0 +1,1094 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_constants.h 271204 2014-09-06 19:12:14Z tuexen $");
+#endif
+
+#ifndef _NETINET_SCTP_CONSTANTS_H_
+#define _NETINET_SCTP_CONSTANTS_H_
+
+#if defined(__Userspace_os_Windows)
+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)
+
+/* 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
+
+/* constants for rto calc */
+#define sctp_align_safe_nocopy 0
+#define sctp_align_unsafe_makecopy 1
+
+/* 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
+
+/*
+ * Theshold 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
+
+
+/* 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)
+#define IS_SCTP_DATA(a) ((a)->chunk_type == SCTP_DATA)
+
+
+/* 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
+
+/* draft-ietf-stewart-tsvwg-strreset-xxx */
+#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
+
+/* draft-ietf-tsvwg-auth-xxx */
+#define SCTP_RANDOM 0x8002
+#define SCTP_CHUNK_LIST 0x8003
+#define SCTP_HMAC_LIST 0x8004
+/*
+ * draft-ietf-tsvwg-addip-sctp-xx param=0x8008 len=0xNNNN Byte | Byte | Byte
+ * | Byte Byte | Byte ...
+ *
+ * Where each byte is a chunk type extension supported. For example, to support
+ * all chunks one would have (in hex):
+ *
+ * 80 01 00 09 C0 C1 80 81 82 00 00 00
+ *
+ * Has the parameter. C0 = PR-SCTP (RFC3758) C1, 80 = ASCONF (addip draft) 81
+ * = Packet Drop 82 = Stream Reset 83 = Authentication
+ */
+#define SCTP_SUPPORTED_CHUNK_EXT 0x8008
+
+/*************0xC000 series*************/
+#define SCTP_PRSCTP_SUPPORTED 0xc000
+/* draft-ietf-tsvwg-addip-sctp */
+#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 XXX (should match "user" values)
+ */
+#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(asoc) ((asoc)->state & SCTP_STATE_MASK)
+#define SCTP_SET_STATE(asoc, newstate) ((asoc)->state = ((asoc)->state & ~SCTP_STATE_MASK) | newstate)
+#define SCTP_CLEAR_SUBSTATE(asoc, substate) ((asoc)->state &= ~substate)
+#define SCTP_ADD_SUBSTATE(asoc, substate) ((asoc)->state |= 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 inital 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_EVENTWAKE 13
+#define SCTP_TIMER_TYPE_STRRESET 14
+#define SCTP_TIMER_TYPE_INPKILL 15
+#define SCTP_TIMER_TYPE_ASOCKILL 16
+#define SCTP_TIMER_TYPE_ADDR_WQ 17
+#define SCTP_TIMER_TYPE_ZERO_COPY 18
+#define SCTP_TIMER_TYPE_ZCOPY_SENDQ 19
+#define SCTP_TIMER_TYPE_PRIM_DELETED 20
+/* add new timers here - and increment LAST */
+#define SCTP_TIMER_TYPE_LAST 21
+
+#define SCTP_IS_TIMER_TYPE_VALID(t) (((t) > SCTP_TIMER_TYPE_NONE) && \
+ ((t) < SCTP_TIMER_TYPE_LAST))
+
+
+#if defined(__APPLE__)
+/* 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
+ */
+#ifdef __Panda__
+#define SCTP_ASOC_MAX_CHUNKS_ON_QUEUE 10240
+#else
+#define SCTP_ASOC_MAX_CHUNKS_ON_QUEUE 512
+#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.
+ */
+#define MSEC_TO_TICKS(x) ((hz == 1000) ? x : ((((x) * hz) + 999) / 1000))
+#define TICKS_TO_MSEC(x) ((hz == 1000) ? x : ((((x) * 1000) + (hz - 1)) / hz))
+
+#define SEC_TO_TICKS(x) ((x) * hz)
+#define TICKS_TO_SEC(x) (((x) + (hz - 1)) / hz)
+
+/*
+ * 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
+
+/* Max time I will wait for Shutdown to complete */
+#define SCTP_DEF_MAX_SHUTDOWN_SEC 180
+
+
+/*
+ * 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 initally 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 64
+
+/* 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_PANDA 0xb0000000
+#define SCTP_FROM_SCTP_SYSCTL 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
+
+
+/* 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
+
+#ifdef __FreeBSD__
+/* 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_SSN_GT(a, b) (((a < b) && ((uint16_t)(b - a) > (1U<<15))) || \
+ ((a > b) && ((uint16_t)(a - b) < (1U<<15))))
+#define SCTP_SSN_GE(a, b) (SCTP_SSN_GT(a, b) || (a == b))
+#define SCTP_TSN_GT(a, b) (((a < b) && ((uint32_t)(b - a) > (1U<<31))) || \
+ ((a > b) && ((uint32_t)(a - b) < (1U<<31))))
+#define SCTP_TSN_GE(a, b) (SCTP_TSN_GT(a, b) || (a == 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 yeild 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__ and SCTP_SO_LOCK_TESTING
+ */
+#define SCTP_SO_LOCKED 1
+#define SCTP_SO_NOT_LOCKED 0
+
+
+#define SCTP_HOLDS_LOCK 1
+#define SCTP_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) && \
+ (((uint8_t *)&(a)->s_addr)[1] == 0) && \
+ (((uint8_t *)&(a)->s_addr)[2] == 0) && \
+ (((uint8_t *)&(a)->s_addr)[3] == 1))
+
+#define IN4_ISLINKLOCAL_ADDRESS(a) \
+ ((((uint8_t *)&(a)->s_addr)[0] == 169) && \
+ (((uint8_t *)&(a)->s_addr)[1] == 254))
+
+#if defined(__Userspace__)
+#if defined(__Userspace_os_Windows)
+#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(__Windows__) || 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(__Windows__) || 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..b994aaace0
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_crc32.c
@@ -0,0 +1,818 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_crc32.c 235828 2012-05-23 11:26:28Z tuexen $");
+#endif
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_crc32.h>
+#include <netinet/sctp_pcb.h>
+
+
+#if !defined(SCTP_WITH_NO_CSUM)
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+#else
+/**
+ *
+ * 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 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 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 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 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 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
+ */
+
+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 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 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 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);
+}
+
+
+static uint32_t
+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 /* FreeBSD < 80000 || other OS */
+
+static uint32_t
+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.. aka Motorola byte order the result is in
+ * little-endian form. So we must manually swap the bytes. Then we
+ * can call htonl() which does nothing...
+ */
+ 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 INTEL platforms the result comes out in network order. No
+ * htonl is required or the swap above. So we optimize out both the
+ * htonl and the manual swap above.
+ */
+ crc32c = result;
+#endif
+ return (crc32c);
+}
+
+uint32_t
+sctp_calculate_cksum(struct mbuf *m, uint32_t offset)
+{
+ /*
+ * given a mbuf chain with a packetheader offset by 'offset'
+ * pointing at a sctphdr (with csum set to 0) go through the chain
+ * of SCTP_BUF_NEXT()'s and calculate the SCTP checksum. This also
+ * has a side bonus as it will calculate the total length of the
+ * mbuf chain. Note: if offset is greater than the total mbuf
+ * length, checksum=1, pktlen=0 is returned (ie. no real error code)
+ */
+ uint32_t base = 0xffffffff;
+ struct mbuf *at;
+
+ at = m;
+ /* find the correct mbuf and offset into mbuf */
+ while ((at != NULL) && (offset > (uint32_t) SCTP_BUF_LEN(at))) {
+ offset -= SCTP_BUF_LEN(at); /* update remaining offset
+ * left */
+ at = SCTP_BUF_NEXT(at);
+ }
+ while (at != NULL) {
+ if ((SCTP_BUF_LEN(at) - offset) > 0) {
+ base = calculate_crc32c(base,
+ (unsigned char *)(SCTP_BUF_AT(at, offset)),
+ (unsigned int)(SCTP_BUF_LEN(at) - offset));
+ }
+ if (offset) {
+ /* we only offset once into the first mbuf */
+ if (offset < (uint32_t) SCTP_BUF_LEN(at))
+ offset = 0;
+ else
+ offset -= SCTP_BUF_LEN(at);
+ }
+ at = SCTP_BUF_NEXT(at);
+ }
+ base = sctp_finalize_crc32c(base);
+ return (base);
+}
+#endif /* !defined(SCTP_WITH_NO_CSUM) */
+
+
+#if defined(__FreeBSD__)
+void
+sctp_delayed_cksum(struct mbuf *m, uint32_t offset)
+{
+#if defined(SCTP_WITH_NO_CSUM)
+#ifdef INVARIANTS
+ panic("sctp_delayed_cksum() called when using no SCTP CRC.");
+#endif
+#else
+ 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_len)) {
+ SCTP_PRINTF("sctp_delayed_cksum(): m->len: %d, off: %d.\n",
+ (uint32_t) m->m_len, offset);
+ /*
+ * XXX this shouldn't happen, but if it does, the correct
+ * behavior may be to insert the checksum in the appropriate
+ * next mbuf in the chain.
+ */
+ return;
+ }
+ *(uint32_t *) (m->m_data + offset) = 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..2cc14f4503
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_crc32.h
@@ -0,0 +1,54 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_crc32.h 235828 2012-05-23 11:26:28Z tuexen $");
+#endif
+
+#ifndef _NETINET_SCTP_CRC32_H_
+#define _NETINET_SCTP_CRC32_H_
+
+#if defined(_KERNEL)
+#if !defined(SCTP_WITH_NO_CSUM)
+uint32_t sctp_calculate_cksum(struct mbuf *, uint32_t);
+#endif
+#if defined(__FreeBSD__)
+void sctp_delayed_cksum(struct mbuf *, uint32_t offset);
+#endif
+#endif /* _KERNEL */
+#if defined(__Userspace__)
+#if !defined(SCTP_WITH_NO_CSUM)
+uint32_t sctp_calculate_cksum(struct mbuf *, uint32_t);
+#endif
+#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..f62181fecd
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_header.h
@@ -0,0 +1,637 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_header.h 273168 2014-10-16 15:36:04Z tuexen $");
+#endif
+
+#ifndef _NETINET_SCTP_HEADER_H_
+#define _NETINET_SCTP_HEADER_H_
+
+#if defined(__Windows__) && !defined(__Userspace_os_Windows)
+#include <packon.h>
+#endif
+#if !defined(__Userspace_os_Windows)
+#include <sys/time.h>
+#endif
+#include <netinet/sctp.h>
+#include <netinet/sctp_constants.h>
+
+#if !defined(__Userspace_os_Windows)
+#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 stream_id;
+ uint16_t stream_sequence;
+ uint32_t protocol_id;
+ /* user data follows */
+} SCTP_PACKED;
+
+struct sctp_data_chunk {
+ struct sctp_chunkhdr ch;
+ struct sctp_data 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;
+
+
+/* Used for NAT state error cause */
+struct sctp_missing_nat_state {
+ uint16_t cause;
+ uint16_t length;
+ uint8_t data[];
+} SCTP_PACKED;
+
+
+struct sctp_inv_mandatory_param {
+ uint16_t cause;
+ uint16_t length;
+ uint32_t num_param;
+ uint16_t param;
+ /*
+ * We include this to 0 it since only a missing cookie will cause
+ * this error.
+ */
+ uint16_t resv;
+} SCTP_PACKED;
+
+struct sctp_unresolv_addr {
+ uint16_t cause;
+ uint16_t length;
+ uint16_t addr_type;
+ uint16_t reserved; /* Only one invalid addr type */
+} 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;
+
+/* Oper error holding a stale cookie */
+struct sctp_stale_cookie_msg {
+ struct sctp_paramhdr ph;/* really an error cause */
+ uint32_t time_usec;
+} SCTP_PACKED;
+
+struct sctp_adaptation_layer_indication {
+ struct sctp_paramhdr ph;
+ uint32_t indication;
+} SCTP_PACKED;
+
+struct sctp_cookie_while_shutting_down {
+ struct sctphdr sh;
+ struct sctp_chunkhdr ch;
+ struct sctp_paramhdr ph;/* really an error cause */
+} SCTP_PACKED;
+
+struct sctp_shutdown_complete_msg {
+ struct sctphdr sh;
+ struct sctp_shutdown_complete_chunk shut_cmp;
+} 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 stream;
+ uint16_t sequence;
+} SCTP_PACKED;
+
+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;
+
+struct sctp_auth_invalid_hmac {
+ struct sctp_paramhdr ph;
+ uint16_t hmac_id;
+ uint16_t padding;
+} 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(__Windows__)
+#include <packoff.h>
+#endif
+#if defined(__Userspace_os_Windows)
+#pragma pack ()
+#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..f697d0ced9
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_indata.c
@@ -0,0 +1,5373 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_indata.c 280440 2015-03-24 15:05:36Z 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>
+
+
+/*
+ * 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.
+ */
+
+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);
+
+ if (stcb->asoc.sb_cc == 0 &&
+ asoc->size_on_reasm_queue == 0 &&
+ asoc->size_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 stream_no,
+ uint16_t stream_seq, 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;
+ }
+ read_queue_e->sinfo_stream = stream_no;
+ read_queue_e->sinfo_ssn = stream_seq;
+ read_queue_e->sinfo_flags = (flags << 8);
+ read_queue_e->sinfo_ppid = ppid;
+ read_queue_e->sinfo_context = context;
+ read_queue_e->sinfo_timetolive = 0;
+ 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->whoFrom = net;
+ read_queue_e->length = 0;
+ atomic_add_int(&net->ref_count, 1);
+ read_queue_e->data = dm;
+ read_queue_e->spec_flags = 0;
+ read_queue_e->tail_mbuf = NULL;
+ read_queue_e->aux_data = NULL;
+ read_queue_e->stcb = stcb;
+ read_queue_e->port_from = stcb->rport;
+ read_queue_e->do_not_ref_stcb = 0;
+ read_queue_e->end_added = 0;
+ read_queue_e->some_taken = 0;
+ read_queue_e->pdapi_aborted = 0;
+failed_build:
+ return (read_queue_e);
+}
+
+
+/*
+ * Build out our readq entry based on the incoming packet.
+ */
+static struct sctp_queued_to_read *
+sctp_build_readq_entry_chk(struct sctp_tcb *stcb,
+ struct sctp_tmit_chunk *chk)
+{
+ 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;
+ }
+ read_queue_e->sinfo_stream = chk->rec.data.stream_number;
+ read_queue_e->sinfo_ssn = chk->rec.data.stream_seq;
+ read_queue_e->sinfo_flags = (chk->rec.data.rcv_flags << 8);
+ read_queue_e->sinfo_ppid = chk->rec.data.payloadtype;
+ read_queue_e->sinfo_context = stcb->asoc.context;
+ read_queue_e->sinfo_timetolive = 0;
+ read_queue_e->sinfo_tsn = chk->rec.data.TSN_seq;
+ read_queue_e->sinfo_cumtsn = chk->rec.data.TSN_seq;
+ read_queue_e->sinfo_assoc_id = sctp_get_associd(stcb);
+ read_queue_e->whoFrom = chk->whoTo;
+ read_queue_e->aux_data = NULL;
+ read_queue_e->length = 0;
+ atomic_add_int(&chk->whoTo->ref_count, 1);
+ read_queue_e->data = chk->data;
+ read_queue_e->tail_mbuf = NULL;
+ read_queue_e->stcb = stcb;
+ read_queue_e->port_from = stcb->rport;
+ read_queue_e->spec_flags = 0;
+ read_queue_e->do_not_ref_stcb = 0;
+ read_queue_e->end_added = 0;
+ read_queue_e->some_taken = 0;
+ read_queue_e->pdapi_aborted = 0;
+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(__Userspace_os_Windows)
+ 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->sreinfo_next_flags & SCTP_NEXT_MSG_AVAIL)) {
+ provide_nxt = 1;
+ len += CMSG_SPACE(sizeof(struct sctp_rcvinfo));
+ } 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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->sreinfo_next_stream;
+ nxtinfo->nxt_flags = 0;
+ if (seinfo->sreinfo_next_flags & SCTP_NEXT_MSG_IS_UNORDERED) {
+ nxtinfo->nxt_flags |= SCTP_UNORDERED;
+ }
+ if (seinfo->sreinfo_next_flags & SCTP_NEXT_MSG_IS_NOTIFICATION) {
+ nxtinfo->nxt_flags |= SCTP_NOTIFICATION;
+ }
+ if (seinfo->sreinfo_next_flags & SCTP_NEXT_MSG_ISCOMPLETE) {
+ nxtinfo->nxt_flags |= SCTP_COMPLETE;
+ }
+ nxtinfo->nxt_ppid = seinfo->sreinfo_next_ppid;
+ nxtinfo->nxt_length = seinfo->sreinfo_next_length;
+ nxtinfo->nxt_assoc_id = seinfo->sreinfo_next_aid;
+#if defined(__Userspace_os_Windows)
+ 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;
+
+ 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);
+ if (!SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap)) {
+ SCTP_PRINTF("gap:%x tsn:%x\n", gap, tsn);
+ sctp_print_mapping_array(asoc);
+#ifdef INVARIANTS
+ panic("Things are really messed up now!!");
+#endif
+ }
+ SCTP_SET_TSN_PRESENT(asoc->nr_mapping_array, gap);
+ 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;
+ }
+ }
+}
+
+
+/*
+ * We are delivering currently from the reassembly queue. We must continue to
+ * deliver until we either: 1) run out of space. 2) run out of sequential
+ * TSN's 3) hit the SCTP_DATA_LAST_FRAG flag.
+ */
+static void
+sctp_service_reassembly(struct sctp_tcb *stcb, struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+ uint16_t nxt_todel;
+ uint16_t stream_no;
+ int end = 0;
+ int cntDel;
+ struct sctp_queued_to_read *control, *ctl, *nctl;
+
+ if (stcb == NULL)
+ return;
+
+ cntDel = stream_no = 0;
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
+ (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)) {
+ /* socket above is long gone or going.. */
+ abandon:
+ asoc->fragmented_delivery_inprogress = 0;
+ TAILQ_FOREACH_SAFE(chk, &asoc->reasmqueue, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next);
+ asoc->size_on_reasm_queue -= chk->send_size;
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+ /*
+ * Lose the data pointer, since its in the socket
+ * buffer
+ */
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ /* Now free the address and data */
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ /*sa_ignore FREED_MEMORY*/
+ }
+ return;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ TAILQ_FOREACH_SAFE(chk, &asoc->reasmqueue, sctp_next, nchk) {
+ if (chk->rec.data.TSN_seq != (asoc->tsn_last_delivered + 1)) {
+ /* Can't deliver more :< */
+ return;
+ }
+ stream_no = chk->rec.data.stream_number;
+ nxt_todel = asoc->strmin[stream_no].last_sequence_delivered + 1;
+ if (nxt_todel != chk->rec.data.stream_seq &&
+ (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0) {
+ /*
+ * Not the next sequence to deliver in its stream OR
+ * unordered
+ */
+ return;
+ }
+ if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) {
+
+ control = sctp_build_readq_entry_chk(stcb, chk);
+ if (control == NULL) {
+ /* out of memory? */
+ return;
+ }
+ /* save it off for our future deliveries */
+ stcb->asoc.control_pdapi = control;
+ if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG)
+ end = 1;
+ else
+ end = 0;
+ sctp_mark_non_revokable(asoc, chk->rec.data.TSN_seq);
+ sctp_add_to_readq(stcb->sctp_ep,
+ stcb, control, &stcb->sctp_socket->so_rcv, end,
+ SCTP_READ_LOCK_NOT_HELD, SCTP_SO_NOT_LOCKED);
+ cntDel++;
+ } else {
+ if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG)
+ end = 1;
+ else
+ end = 0;
+ sctp_mark_non_revokable(asoc, chk->rec.data.TSN_seq);
+ if (sctp_append_to_readq(stcb->sctp_ep, stcb,
+ stcb->asoc.control_pdapi,
+ chk->data, end, chk->rec.data.TSN_seq,
+ &stcb->sctp_socket->so_rcv)) {
+ /*
+ * something is very wrong, either
+ * control_pdapi is NULL, or the tail_mbuf
+ * is corrupt, or there is a EOM already on
+ * the mbuf chain.
+ */
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ goto abandon;
+ } else {
+#ifdef INVARIANTS
+ if ((stcb->asoc.control_pdapi == NULL) || (stcb->asoc.control_pdapi->tail_mbuf == NULL)) {
+ panic("This should not happen control_pdapi NULL?");
+ }
+ /* if we did not panic, it was a EOM */
+ panic("Bad chunking ??");
+#else
+ if ((stcb->asoc.control_pdapi == NULL) || (stcb->asoc.control_pdapi->tail_mbuf == NULL)) {
+ SCTP_PRINTF("This should not happen control_pdapi NULL?\n");
+ }
+ SCTP_PRINTF("Bad chunking ??\n");
+ SCTP_PRINTF("Dumping re-assembly queue this will probably hose the association\n");
+
+#endif
+ goto abandon;
+ }
+ }
+ cntDel++;
+ }
+ /* pull it we did it */
+ TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next);
+ if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) {
+ asoc->fragmented_delivery_inprogress = 0;
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0) {
+ asoc->strmin[stream_no].last_sequence_delivered++;
+ }
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0) {
+ SCTP_STAT_INCR_COUNTER64(sctps_reasmusrmsgs);
+ }
+ } else if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) {
+ /*
+ * turn the flag back on since we just delivered
+ * yet another one.
+ */
+ asoc->fragmented_delivery_inprogress = 1;
+ }
+ asoc->tsn_of_pdapi_last_delivered = chk->rec.data.TSN_seq;
+ asoc->last_flags_delivered = chk->rec.data.rcv_flags;
+ asoc->last_strm_seq_delivered = chk->rec.data.stream_seq;
+ asoc->last_strm_no_delivered = chk->rec.data.stream_number;
+
+ asoc->tsn_last_delivered = chk->rec.data.TSN_seq;
+ asoc->size_on_reasm_queue -= chk->send_size;
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+ /* free up the chk */
+ chk->data = NULL;
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+
+ if (asoc->fragmented_delivery_inprogress == 0) {
+ /*
+ * Now lets see if we can deliver the next one on
+ * the stream
+ */
+ struct sctp_stream_in *strm;
+
+ strm = &asoc->strmin[stream_no];
+ nxt_todel = strm->last_sequence_delivered + 1;
+ TAILQ_FOREACH_SAFE(ctl, &strm->inqueue, next, nctl) {
+ /* Deliver more if we can. */
+ if (nxt_todel == ctl->sinfo_ssn) {
+ TAILQ_REMOVE(&strm->inqueue, ctl, next);
+ asoc->size_on_all_streams -= ctl->length;
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ strm->last_sequence_delivered++;
+ sctp_mark_non_revokable(asoc, ctl->sinfo_tsn);
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ ctl,
+ &stcb->sctp_socket->so_rcv, 1,
+ SCTP_READ_LOCK_NOT_HELD, SCTP_SO_NOT_LOCKED);
+ } else {
+ break;
+ }
+ nxt_todel = strm->last_sequence_delivered + 1;
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * 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. One big
+ * question still remains, what to do when the socket buffer is FULL??
+ */
+static void
+sctp_queue_data_to_stream(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct sctp_queued_to_read *control, int *abort_flag)
+{
+ /*
+ * 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 useage 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_stream_in *strm;
+ struct sctp_queued_to_read *at;
+ int queue_needed;
+ uint16_t nxt_todel;
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ queue_needed = 1;
+ asoc->size_on_all_streams += control->length;
+ sctp_ucount_incr(asoc->cnt_on_all_streams);
+ strm = &asoc->strmin[control->sinfo_stream];
+ nxt_todel = strm->last_sequence_delivered + 1;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del(control, NULL, SCTP_STR_LOG_FROM_INTO_STRD);
+ }
+ SCTPDBG(SCTP_DEBUG_INDATA1,
+ "queue to stream called for ssn:%u lastdel:%u nxt:%u\n",
+ (uint32_t) control->sinfo_stream,
+ (uint32_t) strm->last_sequence_delivered,
+ (uint32_t) nxt_todel);
+ if (SCTP_SSN_GE(strm->last_sequence_delivered, control->sinfo_ssn)) {
+ /* The incoming sseq is behind where we last delivered? */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Duplicate S-SEQ:%d delivered:%d from peer, Abort association\n",
+ control->sinfo_ssn, strm->last_sequence_delivered);
+ protocol_error:
+ /*
+ * throw it in the stream so it gets cleaned up in
+ * association destruction
+ */
+ TAILQ_INSERT_HEAD(&strm->inqueue, control, next);
+ snprintf(msg, sizeof(msg), "Delivered SSN=%4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ strm->last_sequence_delivered, control->sinfo_tsn,
+ control->sinfo_stream, control->sinfo_ssn);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_1;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+
+ }
+ if (nxt_todel == control->sinfo_ssn) {
+ /* 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;
+ asoc->size_on_all_streams -= control->length;
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ strm->last_sequence_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_NOT_LOCKED);
+ TAILQ_FOREACH_SAFE(control, &strm->inqueue, next, at) {
+ /* all delivered */
+ nxt_todel = strm->last_sequence_delivered + 1;
+ if (nxt_todel == control->sinfo_ssn) {
+ TAILQ_REMOVE(&strm->inqueue, control, next);
+ asoc->size_on_all_streams -= control->length;
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ strm->last_sequence_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_NOT_LOCKED);
+ continue;
+ }
+ break;
+ }
+ }
+ if (queue_needed) {
+ /*
+ * Ok, we did not deliver this guy, find the correct place
+ * to put it on the queue.
+ */
+ if (SCTP_TSN_GE(asoc->cumulative_tsn, control->sinfo_tsn)) {
+ goto protocol_error;
+ }
+ if (TAILQ_EMPTY(&strm->inqueue)) {
+ /* Empty queue */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del(control, NULL, SCTP_STR_LOG_FROM_INSERT_HD);
+ }
+ TAILQ_INSERT_HEAD(&strm->inqueue, control, next);
+ } else {
+ TAILQ_FOREACH(at, &strm->inqueue, next) {
+ if (SCTP_SSN_GT(at->sinfo_ssn, control->sinfo_ssn)) {
+ /*
+ * one in queue is bigger than the
+ * new one, insert before this one
+ */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del(control, at,
+ SCTP_STR_LOG_FROM_INSERT_MD);
+ }
+ TAILQ_INSERT_BEFORE(at, control, next);
+ break;
+ } else if (at->sinfo_ssn == control->sinfo_ssn) {
+ /*
+ * 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!
+ */
+
+ if (control->data)
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ asoc->size_on_all_streams -= control->length;
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ if (control->whoFrom) {
+ sctp_free_remote_addr(control->whoFrom);
+ control->whoFrom = NULL;
+ }
+ sctp_free_a_readq(stcb, control);
+ return;
+ } else {
+ if (TAILQ_NEXT(at, next) == 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(&strm->inqueue,
+ at, control, next);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Returns two things: You get the total size of the deliverable parts of the
+ * first fragmented message on the reassembly queue. And you get a 1 back if
+ * all of the message is ready or a 0 back if the message is still incomplete
+ */
+static int
+sctp_is_all_msg_on_reasm(struct sctp_association *asoc, uint32_t *t_size)
+{
+ struct sctp_tmit_chunk *chk;
+ uint32_t tsn;
+
+ *t_size = 0;
+ chk = TAILQ_FIRST(&asoc->reasmqueue);
+ if (chk == NULL) {
+ /* nothing on the queue */
+ return (0);
+ }
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0) {
+ /* Not a first on the queue */
+ return (0);
+ }
+ tsn = chk->rec.data.TSN_seq;
+ TAILQ_FOREACH(chk, &asoc->reasmqueue, sctp_next) {
+ if (tsn != chk->rec.data.TSN_seq) {
+ return (0);
+ }
+ *t_size += chk->send_size;
+ if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) {
+ return (1);
+ }
+ tsn++;
+ }
+ return (0);
+}
+
+static void
+sctp_deliver_reasm_check(struct sctp_tcb *stcb, struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk;
+ uint16_t nxt_todel;
+ uint32_t tsize, pd_point;
+
+ doit_again:
+ chk = TAILQ_FIRST(&asoc->reasmqueue);
+ if (chk == NULL) {
+ /* Huh? */
+ asoc->size_on_reasm_queue = 0;
+ asoc->cnt_on_reasm_queue = 0;
+ return;
+ }
+ if (asoc->fragmented_delivery_inprogress == 0) {
+ nxt_todel =
+ asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered + 1;
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) &&
+ (nxt_todel == chk->rec.data.stream_seq ||
+ (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED))) {
+ /*
+ * Yep the first one is here and its ok to deliver
+ * but should we?
+ */
+ 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;
+ }
+ if (sctp_is_all_msg_on_reasm(asoc, &tsize) || (tsize >= pd_point)) {
+ /*
+ * Yes, we setup to start reception, by
+ * backing down the TSN just in case we
+ * can't deliver. If we
+ */
+ asoc->fragmented_delivery_inprogress = 1;
+ asoc->tsn_last_delivered =
+ chk->rec.data.TSN_seq - 1;
+ asoc->str_of_pdapi =
+ chk->rec.data.stream_number;
+ asoc->ssn_of_pdapi = chk->rec.data.stream_seq;
+ asoc->pdapi_ppid = chk->rec.data.payloadtype;
+ asoc->fragment_flags = chk->rec.data.rcv_flags;
+ sctp_service_reassembly(stcb, asoc);
+ }
+ }
+ } else {
+ /* Service re-assembly will deliver stream data queued
+ * at the end of fragmented delivery.. but it wont know
+ * to go back and call itself again... we do that here
+ * with the got doit_again
+ */
+ sctp_service_reassembly(stcb, asoc);
+ if (asoc->fragmented_delivery_inprogress == 0) {
+ /* finished our Fragmented delivery, could be
+ * more waiting?
+ */
+ goto doit_again;
+ }
+ }
+}
+
+/*
+ * 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_tmit_chunk *chk, int *abort_flag)
+{
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+ uint32_t cum_ackp1, prev_tsn, post_tsn;
+ struct sctp_tmit_chunk *at, *prev, *next;
+
+ prev = next = NULL;
+ cum_ackp1 = asoc->tsn_last_delivered + 1;
+ if (TAILQ_EMPTY(&asoc->reasmqueue)) {
+ /* This is the first one on the queue */
+ TAILQ_INSERT_HEAD(&asoc->reasmqueue, chk, sctp_next);
+ /*
+ * we do not check for delivery of anything when only one
+ * fragment is here
+ */
+ asoc->size_on_reasm_queue = chk->send_size;
+ sctp_ucount_incr(asoc->cnt_on_reasm_queue);
+ if (chk->rec.data.TSN_seq == cum_ackp1) {
+ if (asoc->fragmented_delivery_inprogress == 0 &&
+ (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) !=
+ SCTP_DATA_FIRST_FRAG) {
+ /*
+ * An empty queue, no delivery inprogress,
+ * we hit the next one and it does NOT have
+ * a FIRST fragment mark.
+ */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Gak, Evil plot, its not first, no fragmented delivery in progress\n");
+ snprintf(msg, sizeof(msg),
+ "Expected B-bit for TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ 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;
+ } else if (asoc->fragmented_delivery_inprogress &&
+ (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == SCTP_DATA_FIRST_FRAG) {
+ /*
+ * We are doing a partial delivery and the
+ * NEXT chunk MUST be either the LAST or
+ * MIDDLE fragment NOT a FIRST
+ */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Gak, Evil plot, it IS a first and fragmented delivery in progress\n");
+ snprintf(msg, sizeof(msg),
+ "Didn't expect B-bit for TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ 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;
+ } else if (asoc->fragmented_delivery_inprogress) {
+ /*
+ * Here we are ok with a MIDDLE or LAST
+ * piece
+ */
+ if (chk->rec.data.stream_number !=
+ asoc->str_of_pdapi) {
+ /* Got to be the right STR No */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Gak, Evil plot, it IS not same stream number %d vs %d\n",
+ chk->rec.data.stream_number,
+ asoc->str_of_pdapi);
+ snprintf(msg, sizeof(msg),
+ "Expected SID=%4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ asoc->str_of_pdapi,
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_4;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ } else if ((asoc->fragment_flags & SCTP_DATA_UNORDERED) !=
+ SCTP_DATA_UNORDERED &&
+ chk->rec.data.stream_seq != asoc->ssn_of_pdapi) {
+ /* Got to be the right STR Seq */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Gak, Evil plot, it IS not same stream seq %d vs %d\n",
+ chk->rec.data.stream_seq,
+ asoc->ssn_of_pdapi);
+ snprintf(msg, sizeof(msg),
+ "Expected SSN=%4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ asoc->ssn_of_pdapi,
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_5;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ }
+ }
+ }
+ return;
+ }
+ /* Find its place */
+ TAILQ_FOREACH(at, &asoc->reasmqueue, sctp_next) {
+ if (SCTP_TSN_GT(at->rec.data.TSN_seq, chk->rec.data.TSN_seq)) {
+ /*
+ * one in queue is bigger than the new one, insert
+ * before this one
+ */
+ /* A check */
+ asoc->size_on_reasm_queue += chk->send_size;
+ sctp_ucount_incr(asoc->cnt_on_reasm_queue);
+ next = at;
+ TAILQ_INSERT_BEFORE(at, chk, sctp_next);
+ break;
+ } else if (at->rec.data.TSN_seq == chk->rec.data.TSN_seq) {
+ /* 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!
+ */
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return;
+ } else {
+ prev = at;
+ if (TAILQ_NEXT(at, sctp_next) == NULL) {
+ /*
+ * We are at the end, insert it after this
+ * one
+ */
+ /* check it first */
+ asoc->size_on_reasm_queue += chk->send_size;
+ sctp_ucount_incr(asoc->cnt_on_reasm_queue);
+ TAILQ_INSERT_AFTER(&asoc->reasmqueue, at, chk, sctp_next);
+ break;
+ }
+ }
+ }
+ /* Now the audits */
+ if (prev) {
+ prev_tsn = chk->rec.data.TSN_seq - 1;
+ if (prev_tsn == prev->rec.data.TSN_seq) {
+ /*
+ * Ok the one I am dropping onto the end is the
+ * NEXT. A bit of valdiation here.
+ */
+ if ((prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) ==
+ SCTP_DATA_FIRST_FRAG ||
+ (prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) ==
+ SCTP_DATA_MIDDLE_FRAG) {
+ /*
+ * Insert chk MUST be a MIDDLE or LAST
+ * fragment
+ */
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) ==
+ SCTP_DATA_FIRST_FRAG) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Prev check - It can be a midlle or last but not a first\n");
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Gak, Evil plot, it's a FIRST!\n");
+ snprintf(msg, sizeof(msg),
+ "Can't handle B-bit, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_6;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ if (chk->rec.data.stream_number !=
+ prev->rec.data.stream_number) {
+ /*
+ * Huh, need the correct STR here,
+ * they must be the same.
+ */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Prev check - Gak, Evil plot, sid:%d not the same as at:%d\n",
+ chk->rec.data.stream_number,
+ prev->rec.data.stream_number);
+ snprintf(msg, sizeof(msg),
+ "Expect SID=%4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ prev->rec.data.stream_number,
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_7;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) !=
+ (prev->rec.data.rcv_flags & SCTP_DATA_UNORDERED)) {
+ /*
+ * Huh, need the same ordering here,
+ * they must be the same.
+ */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Prev check - Gak, Evil plot, U-bit not constant\n");
+ snprintf(msg, sizeof(msg),
+ "Expect U-bit=%d for TSN=%8.8x, got U-bit=%d",
+ (prev->rec.data.rcv_flags & SCTP_DATA_UNORDERED) ? 1 : 0,
+ chk->rec.data.TSN_seq,
+ (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) ? 1 : 0);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_7;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ if ((prev->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0 &&
+ chk->rec.data.stream_seq !=
+ prev->rec.data.stream_seq) {
+ /*
+ * Huh, need the correct STR here,
+ * they must be the same.
+ */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Prev check - Gak, Evil plot, sseq:%d not the same as at:%d\n",
+ chk->rec.data.stream_seq,
+ prev->rec.data.stream_seq);
+ snprintf(msg, sizeof(msg),
+ "Expect SSN=%4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ prev->rec.data.stream_seq,
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_8;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ } else if ((prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) ==
+ SCTP_DATA_LAST_FRAG) {
+ /* Insert chk MUST be a FIRST */
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) !=
+ SCTP_DATA_FIRST_FRAG) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Prev check - Gak, evil plot, its not FIRST and it must be!\n");
+ snprintf(msg, sizeof(msg),
+ "Expect B-bit, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_9;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ }
+ }
+ }
+ if (next) {
+ post_tsn = chk->rec.data.TSN_seq + 1;
+ if (post_tsn == next->rec.data.TSN_seq) {
+ /*
+ * Ok the one I am inserting ahead of is my NEXT
+ * one. A bit of valdiation here.
+ */
+ if (next->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) {
+ /* Insert chk MUST be a last fragment */
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK)
+ != SCTP_DATA_LAST_FRAG) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Next chk - Next is FIRST, we must be LAST\n");
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Gak, Evil plot, its not a last!\n");
+ snprintf(msg, sizeof(msg),
+ "Expect only E-bit, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_10;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ } else if ((next->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) ==
+ SCTP_DATA_MIDDLE_FRAG ||
+ (next->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) ==
+ SCTP_DATA_LAST_FRAG) {
+ /*
+ * Insert chk CAN be MIDDLE or FIRST NOT
+ * LAST
+ */
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) ==
+ SCTP_DATA_LAST_FRAG) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Next chk - Next is a MIDDLE/LAST\n");
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Gak, Evil plot, new prev chunk is a LAST\n");
+ snprintf(msg, sizeof(msg),
+ "Didn't expect E-bit, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_11;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ if (chk->rec.data.stream_number !=
+ next->rec.data.stream_number) {
+ /*
+ * Huh, need the correct STR here,
+ * they must be the same.
+ */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Next chk - Gak, Evil plot, ssn:%d not the same as at:%d\n",
+ chk->rec.data.stream_number,
+ next->rec.data.stream_number);
+ snprintf(msg, sizeof(msg),
+ "Required SID %4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ next->rec.data.stream_number,
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_12;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) !=
+ (next->rec.data.rcv_flags & SCTP_DATA_UNORDERED)) {
+ /*
+ * Huh, need the same ordering here,
+ * they must be the same.
+ */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Next check - Gak, Evil plot, U-bit not constant\n");
+ snprintf(msg, sizeof(msg),
+ "Expect U-bit=%d for TSN=%8.8x, got U-bit=%d",
+ (next->rec.data.rcv_flags & SCTP_DATA_UNORDERED) ? 1 : 0,
+ chk->rec.data.TSN_seq,
+ (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) ? 1 : 0);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_12;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ if ((next->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0 &&
+ chk->rec.data.stream_seq !=
+ next->rec.data.stream_seq) {
+ /*
+ * Huh, need the correct STR here,
+ * they must be the same.
+ */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Next chk - Gak, Evil plot, sseq:%d not the same as at:%d\n",
+ chk->rec.data.stream_seq,
+ next->rec.data.stream_seq);
+ snprintf(msg, sizeof(msg),
+ "Required SSN %4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ next->rec.data.stream_seq,
+ chk->rec.data.TSN_seq,
+ chk->rec.data.stream_number,
+ chk->rec.data.stream_seq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_13;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return;
+ }
+ }
+ }
+ }
+ /* Do we need to do some delivery? check */
+ sctp_deliver_reasm_check(stcb, asoc);
+}
+
+/*
+ * This is an unfortunate routine. It checks to make sure a evil guy is not
+ * stuffing us full of bad packet fragments. A broken peer could also do this
+ * but this is doubtful. It is to bad I must worry about evil crackers sigh
+ * :< more cycles.
+ */
+static int
+sctp_does_tsn_belong_to_reasm(struct sctp_association *asoc,
+ uint32_t TSN_seq)
+{
+ struct sctp_tmit_chunk *at;
+ uint32_t tsn_est;
+
+ TAILQ_FOREACH(at, &asoc->reasmqueue, sctp_next) {
+ if (SCTP_TSN_GT(TSN_seq, at->rec.data.TSN_seq)) {
+ /* is it one bigger? */
+ tsn_est = at->rec.data.TSN_seq + 1;
+ if (tsn_est == TSN_seq) {
+ /* yep. It better be a last then */
+ if ((at->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) !=
+ SCTP_DATA_LAST_FRAG) {
+ /*
+ * Ok this guy belongs next to a guy
+ * that is NOT last, it should be a
+ * middle/last, not a complete
+ * chunk.
+ */
+ return (1);
+ } else {
+ /*
+ * This guy is ok since its a LAST
+ * and the new chunk is a fully
+ * self- contained one.
+ */
+ return (0);
+ }
+ }
+ } else if (TSN_seq == at->rec.data.TSN_seq) {
+ /* Software error since I have a dup? */
+ return (1);
+ } else {
+ /*
+ * Ok, 'at' is larger than new chunk but does it
+ * need to be right before it.
+ */
+ tsn_est = TSN_seq + 1;
+ if (tsn_est == at->rec.data.TSN_seq) {
+ /* Yep, It better be a first */
+ if ((at->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) !=
+ SCTP_DATA_FIRST_FRAG) {
+ return (1);
+ } else {
+ return (0);
+ }
+ }
+ }
+ }
+ return (0);
+}
+
+static int
+sctp_process_a_data_chunk(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ struct mbuf **m, int offset, struct sctp_data_chunk *ch, int chk_length,
+ struct sctp_nets *net, uint32_t *high_tsn, int *abort_flag,
+ int *break_flag, int last_chunk)
+{
+ /* Process a data chunk */
+ /* struct sctp_tmit_chunk *chk; */
+ struct sctp_tmit_chunk *chk;
+ uint32_t tsn, gap;
+ struct mbuf *dmbuf;
+ int the_len;
+ int need_reasm_check = 0;
+ uint16_t strmno, strmseq;
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+ struct sctp_queued_to_read *control;
+ int ordered;
+ uint32_t protocol_id;
+ uint8_t chunk_flags;
+ struct sctp_stream_reset_list *liste;
+
+ chk = NULL;
+ tsn = ntohl(ch->dp.tsn);
+ chunk_flags = ch->ch.chunk_flags;
+ if ((chunk_flags & SCTP_DATA_SACK_IMMEDIATELY) == SCTP_DATA_SACK_IMMEDIATELY) {
+ asoc->send_sack = 1;
+ }
+ protocol_id = ch->dp.protocol_id;
+ ordered = ((chunk_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, ch->ch.chunk_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.
+ */
+
+ /* 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ /* now is it in the mapping array of what we have accepted? */
+ 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 */
+ 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);
+ }
+ }
+ strmno = ntohs(ch->dp.stream_id);
+ if (strmno >= asoc->streamincnt) {
+ struct sctp_paramhdr *phdr;
+ struct mbuf *mb;
+
+ mb = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) * 2),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (mb != NULL) {
+ /* add some space up front so prepend will work well */
+ SCTP_BUF_RESV_UF(mb, sizeof(struct sctp_chunkhdr));
+ phdr = mtod(mb, struct sctp_paramhdr *);
+ /*
+ * 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(mb) = (sizeof(struct sctp_paramhdr) * 2);
+ phdr->param_type = htons(SCTP_CAUSE_INVALID_STREAM);
+ phdr->param_length =
+ htons(sizeof(struct sctp_paramhdr) * 2);
+ phdr++;
+ /* We insert the stream in the type field */
+ phdr->param_type = ch->dp.stream_id;
+ /* And set the length to 0 for the rsvd field */
+ phdr->param_length = 0;
+ sctp_queue_op_err(stcb, mb);
+ }
+ 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);
+ }
+ /*
+ * Before we continue lets validate that we are not being fooled by
+ * an evil attacker. We can only have 4k chunks based on our TSN
+ * spread allowed by the mapping array 512 * 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.
+ */
+ strmseq = ntohs(ch->dp.stream_sequence);
+#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 = strmno;
+ asoc->in_tsnlog[asoc->tsn_in_at].seq = strmseq;
+ 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
+ if ((chunk_flags & SCTP_DATA_FIRST_FRAG) &&
+ (TAILQ_EMPTY(&asoc->resetHead)) &&
+ (chunk_flags & SCTP_DATA_UNORDERED) == 0 &&
+ SCTP_SSN_GE(asoc->strmin[strmno].last_sequence_delivered, strmseq)) {
+ /* The incoming sseq is behind where we last delivered? */
+ SCTPDBG(SCTP_DEBUG_INDATA1, "EVIL/Broken-Dup S-SEQ:%d delivered:%d from peer, Abort!\n",
+ strmseq, asoc->strmin[strmno].last_sequence_delivered);
+
+ snprintf(msg, sizeof(msg), "Delivered SSN=%4.4x, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ asoc->strmin[strmno].last_sequence_delivered,
+ tsn, strmno, strmseq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_14;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *abort_flag = 1;
+ return (0);
+ }
+ /************************************
+ * From here down we may find ch-> invalid
+ * so its a good idea NOT to use it.
+ *************************************/
+
+ the_len = (chk_length - sizeof(struct sctp_data_chunk));
+ if (last_chunk == 0) {
+ 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 */
+ 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);
+ }
+ if ((chunk_flags & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG &&
+ asoc->fragmented_delivery_inprogress == 0 &&
+ TAILQ_EMPTY(&asoc->resetHead) &&
+ ((ordered == 0) ||
+ ((uint16_t)(asoc->strmin[strmno].last_sequence_delivered + 1) == strmseq &&
+ TAILQ_EMPTY(&asoc->strmin[strmno].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....
+ */
+
+ /* It would be nice to avoid this copy if we could :< */
+ sctp_alloc_a_readq(stcb, control);
+ sctp_build_readq_entry_mac(control, stcb, asoc->context, net, tsn,
+ protocol_id,
+ strmno, strmseq,
+ chunk_flags,
+ dmbuf);
+ if (control == NULL) {
+ goto failed_express_del;
+ }
+ 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;
+ }
+ 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 ((chunk_flags & SCTP_DATA_UNORDERED) == 0) {
+ /* for ordered, bump what we delivered */
+ asoc->strmin[strmno].last_sequence_delivered++;
+ }
+ SCTP_STAT_INCR(sctps_recvexpress);
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_STR_LOGGING_ENABLE) {
+ sctp_log_strm_del_alt(stcb, tsn, strmseq, strmno,
+ SCTP_STR_LOG_FROM_EXPRS_DEL);
+ }
+ control = NULL;
+
+ goto finish_express_del;
+ }
+failed_express_del:
+ /* If we reach here this is a new chunk */
+ chk = NULL;
+ control = NULL;
+ /* Express for fragmented delivery? */
+ if ((asoc->fragmented_delivery_inprogress) &&
+ (stcb->asoc.control_pdapi) &&
+ (asoc->str_of_pdapi == strmno) &&
+ (asoc->ssn_of_pdapi == strmseq)
+ ) {
+ control = stcb->asoc.control_pdapi;
+ if ((chunk_flags & SCTP_DATA_FIRST_FRAG) == SCTP_DATA_FIRST_FRAG) {
+ /* Can't be another first? */
+ goto failed_pdapi_express_del;
+ }
+ if (tsn == (control->sinfo_tsn + 1)) {
+ /* Yep, we can add it on */
+ int end = 0;
+
+ if (chunk_flags & SCTP_DATA_LAST_FRAG) {
+ end = 1;
+ }
+ if (sctp_append_to_readq(stcb->sctp_ep, stcb, control, dmbuf, end,
+ tsn,
+ &stcb->sctp_socket->so_rcv)) {
+ SCTP_PRINTF("Append fails end:%d\n", end);
+ goto failed_pdapi_express_del;
+ }
+
+ 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;
+ }
+ SCTP_STAT_INCR(sctps_recvexpressm);
+ asoc->tsn_last_delivered = tsn;
+ asoc->fragment_flags = chunk_flags;
+ asoc->tsn_of_pdapi_last_delivered = tsn;
+ asoc->last_flags_delivered = chunk_flags;
+ asoc->last_strm_seq_delivered = strmseq;
+ asoc->last_strm_no_delivered = strmno;
+ if (end) {
+ /* clean up the flags and such */
+ asoc->fragmented_delivery_inprogress = 0;
+ if ((chunk_flags & SCTP_DATA_UNORDERED) == 0) {
+ asoc->strmin[strmno].last_sequence_delivered++;
+ }
+ stcb->asoc.control_pdapi = NULL;
+ if (TAILQ_EMPTY(&asoc->reasmqueue) == 0) {
+ /* There could be another message ready */
+ need_reasm_check = 1;
+ }
+ }
+ control = NULL;
+ goto finish_express_del;
+ }
+ }
+ failed_pdapi_express_del:
+ control = NULL;
+ 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;
+ }
+ }
+ if ((chunk_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_seq = tsn;
+ chk->no_fr_allowed = 0;
+ chk->rec.data.stream_seq = strmseq;
+ chk->rec.data.stream_number = strmno;
+ chk->rec.data.payloadtype = protocol_id;
+ chk->rec.data.context = stcb->asoc.context;
+ chk->rec.data.doing_fast_retransmit = 0;
+ chk->rec.data.rcv_flags = chunk_flags;
+ chk->asoc = asoc;
+ chk->send_size = the_len;
+ chk->whoTo = net;
+ atomic_add_int(&net->ref_count, 1);
+ chk->data = dmbuf;
+ } else {
+ sctp_alloc_a_readq(stcb, control);
+ sctp_build_readq_entry_mac(control, stcb, asoc->context, net, tsn,
+ protocol_id,
+ strmno, strmseq,
+ chunk_flags,
+ dmbuf);
+ if (control == 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);
+ }
+ control->length = the_len;
+ }
+
+ /* Mark it as received */
+ /* Now queue it where it belongs */
+ if (control != NULL) {
+ /* First a sanity check */
+ if (asoc->fragmented_delivery_inprogress) {
+ /*
+ * Ok, we have a fragmented delivery in progress if
+ * this chunk is next to deliver OR belongs in our
+ * view to the reassembly, the peer is evil or
+ * broken.
+ */
+ uint32_t estimate_tsn;
+
+ estimate_tsn = asoc->tsn_last_delivered + 1;
+ if (TAILQ_EMPTY(&asoc->reasmqueue) &&
+ (estimate_tsn == control->sinfo_tsn)) {
+ /* Evil/Broke peer */
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ if (control->whoFrom) {
+ sctp_free_remote_addr(control->whoFrom);
+ control->whoFrom = NULL;
+ }
+ sctp_free_a_readq(stcb, control);
+ snprintf(msg, sizeof(msg), "Reas. queue emtpy, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ tsn, strmno, strmseq);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ 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;
+ if (last_chunk) {
+ *m = NULL;
+ }
+ return (0);
+ } else {
+ if (sctp_does_tsn_belong_to_reasm(asoc, control->sinfo_tsn)) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ if (control->whoFrom) {
+ sctp_free_remote_addr(control->whoFrom);
+ control->whoFrom = NULL;
+ }
+ sctp_free_a_readq(stcb, control);
+ snprintf(msg, sizeof(msg), "PD ongoing, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ tsn, strmno, strmseq);
+ 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;
+ if (last_chunk) {
+ *m = NULL;
+ }
+ return (0);
+ }
+ }
+ } else {
+ /* No PDAPI running */
+ if (!TAILQ_EMPTY(&asoc->reasmqueue)) {
+ /*
+ * Reassembly queue is NOT empty validate
+ * that this tsn does not need to be in
+ * reasembly queue. If it does then our peer
+ * is broken or evil.
+ */
+ if (sctp_does_tsn_belong_to_reasm(asoc, control->sinfo_tsn)) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ if (control->whoFrom) {
+ sctp_free_remote_addr(control->whoFrom);
+ control->whoFrom = NULL;
+ }
+ sctp_free_a_readq(stcb, control);
+ snprintf(msg, sizeof(msg), "No PD ongoing, got TSN=%8.8x, SID=%4.4x, SSN=%4.4x",
+ tsn, strmno, strmseq);
+ 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;
+ if (last_chunk) {
+ *m = NULL;
+ }
+ return (0);
+ }
+ }
+ }
+ /* ok, if we reach here we have passed the sanity checks */
+ if (chunk_flags & SCTP_DATA_UNORDERED) {
+ /* queue directly into socket buffer */
+ 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 {
+ /*
+ * 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.
+ *
+ * We take complete messages that have a stream reset
+ * intervening (aka the TSN is after where our
+ * cum-ack needs to be) off and put them on a
+ * pending_reply_queue. The reassembly ones we do
+ * not have to worry about since they are all sorted
+ * and proceessed by TSN order. It is only the
+ * singletons I must worry about.
+ */
+ 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 *ctlOn, *nctlOn;
+ unsigned char inserted = 0;
+
+ TAILQ_FOREACH_SAFE(ctlOn, &asoc->pending_reply_queue, next, nctlOn) {
+ if (SCTP_TSN_GT(control->sinfo_tsn, ctlOn->sinfo_tsn)) {
+ continue;
+ } else {
+ /* found it */
+ TAILQ_INSERT_BEFORE(ctlOn, 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);
+ }
+ }
+ } else {
+ sctp_queue_data_to_stream(stcb, asoc, control, abort_flag);
+ if (*abort_flag) {
+ if (last_chunk) {
+ *m = NULL;
+ }
+ return (0);
+ }
+ }
+ }
+ } else {
+ /* Into the re-assembly queue */
+ sctp_queue_data_for_reasm(stcb, asoc, chk, abort_flag);
+ 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:
+ 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, strmseq, strmno, 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);
+ }
+ /* 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.
+ */
+ struct sctp_queued_to_read *ctl, *nctl;
+
+ sctp_reset_in_stream(stcb, liste->number_entries, liste->list_of_streams);
+ TAILQ_REMOVE(&asoc->resetHead, liste, next_resp);
+ 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(ctl, &asoc->pending_reply_queue, next, nctl) {
+ TAILQ_REMOVE(&asoc->pending_reply_queue, ctl, next);
+ sctp_queue_data_to_stream(stcb, asoc, ctl, abort_flag);
+ if (*abort_flag) {
+ return (0);
+ }
+ }
+ } else {
+ TAILQ_FOREACH_SAFE(ctl, &asoc->pending_reply_queue, next, nctl) {
+ if (SCTP_TSN_GT(ctl->sinfo_tsn, liste->tsn)) {
+ break;
+ }
+ /*
+ * if ctl->sinfo_tsn is <= liste->tsn we can
+ * process it which is the NOT of
+ * ctl->sinfo_tsn > liste->tsn
+ */
+ TAILQ_REMOVE(&asoc->pending_reply_queue, ctl, next);
+ sctp_queue_data_to_stream(stcb, asoc, ctl, abort_flag);
+ if (*abort_flag) {
+ return (0);
+ }
+ }
+ }
+ /*
+ * Now service re-assembly to pick up anything that has been
+ * held on reassembly queue?
+ */
+ sctp_deliver_reasm_check(stcb, asoc);
+ need_reasm_check = 0;
+ }
+
+ if (need_reasm_check) {
+ /* Another one waits ? */
+ sctp_deliver_reasm_check(stcb, asoc);
+ }
+ return (1);
+}
+
+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 postion
+ * 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;
+
+ 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;
+ }
+
+ /*
+ * Now we need to see if we need to queue a sack or just start the
+ * timer (if allowed).
+ */
+ if (SCTP_GET_STATE(asoc) == 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));
+ sctp_send_sack(stcb, SCTP_SO_NOT_LOCKED);
+ } else {
+ int is_a_gap;
+
+ /* is there a gap now ? */
+ is_a_gap = SCTP_TSN_GT(highest_tsn, stcb->asoc.cumulative_tsn);
+
+ /*
+ * 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.
+ */
+ (void)SCTP_OS_TIMER_STOP(&stcb->asoc.dack_timer.timer);
+ 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);
+ }
+ }
+ }
+}
+
+void
+sctp_service_queues(struct sctp_tcb *stcb, struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk;
+ uint32_t tsize, pd_point;
+ uint16_t nxt_todel;
+
+ if (asoc->fragmented_delivery_inprogress) {
+ sctp_service_reassembly(stcb, asoc);
+ }
+ /* Can we proceed further, i.e. the PD-API is complete */
+ if (asoc->fragmented_delivery_inprogress) {
+ /* no */
+ return;
+ }
+ /*
+ * Now is there some other chunk I can deliver from the reassembly
+ * queue.
+ */
+ doit_again:
+ chk = TAILQ_FIRST(&asoc->reasmqueue);
+ if (chk == NULL) {
+ asoc->size_on_reasm_queue = 0;
+ asoc->cnt_on_reasm_queue = 0;
+ return;
+ }
+ nxt_todel = asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered + 1;
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) &&
+ ((nxt_todel == chk->rec.data.stream_seq) ||
+ (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED))) {
+ /*
+ * Yep the first one is here. We setup to start reception,
+ * by backing down the TSN just in case we can't deliver.
+ */
+
+ /*
+ * Before we start though either all of the message should
+ * be here or the socket buffer max or nothing on the
+ * delivery queue and something can be delivered.
+ */
+ 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;
+ }
+ if (sctp_is_all_msg_on_reasm(asoc, &tsize) || (tsize >= pd_point)) {
+ asoc->fragmented_delivery_inprogress = 1;
+ asoc->tsn_last_delivered = chk->rec.data.TSN_seq - 1;
+ asoc->str_of_pdapi = chk->rec.data.stream_number;
+ asoc->ssn_of_pdapi = chk->rec.data.stream_seq;
+ asoc->pdapi_ppid = chk->rec.data.payloadtype;
+ asoc->fragment_flags = chk->rec.data.rcv_flags;
+ sctp_service_reassembly(stcb, asoc);
+ if (asoc->fragmented_delivery_inprogress == 0) {
+ goto doit_again;
+ }
+ }
+ }
+}
+
+int
+sctp_process_data(struct mbuf **mm, int iphlen, int *offset, int length,
+ struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh, struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t *high_tsn,
+#if defined(__FreeBSD__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ struct sctp_data_chunk *ch, chunk_buf;
+ struct sctp_association *asoc;
+ int num_chunks = 0; /* number of control chunks processed */
+ int stop_proc = 0;
+ int chk_length, break_flag, last_chunk;
+ int abort_flag = 0, was_a_gap;
+ struct mbuf *m;
+ uint32_t highest_tsn;
+
+ /* 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;
+
+#ifndef __Panda__
+ /*-
+ * 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. Note for __Panda__ we don't do this
+ * since it has clusters all the way down to 64 bytes.
+ */
+ 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);
+ /* sucess, back copy */
+ *mm = m;
+ } else {
+ /* We are in trouble in the mbuf world .. yikes */
+ m = *mm;
+ }
+ }
+#endif
+ /* get pointer to the first chunk header */
+ ch = (struct sctp_data_chunk *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_data_chunk), (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->ch.chunk_length);
+ if (length - *offset < chk_length) {
+ /* all done, mutulated chunk */
+ stop_proc = 1;
+ continue;
+ }
+ if (ch->ch.chunk_type == SCTP_DATA) {
+ if ((size_t)chk_length < sizeof(struct sctp_data_chunk)) {
+ /*
+ * Need to send an abort since we had a
+ * invalid data chunk.
+ */
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ snprintf(msg, sizeof(msg), "DATA chunk of length %d",
+ chk_length);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_19;
+ sctp_abort_association(inp, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ return (2);
+ }
+ if ((size_t)chk_length == sizeof(struct sctp_data_chunk)) {
+ /*
+ * Need to send an abort since we had an
+ * empty data chunk.
+ */
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_no_user_data_cause(ch->dp.tsn);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA+SCTP_LOC_19;
+ sctp_abort_association(inp, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ 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, ch,
+ chk_length, net, high_tsn, &abort_flag, &break_flag,
+ last_chunk)) {
+ 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->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.
+ */
+ if (SCTP_BASE_SYSCTL(sctp_strict_data_order)) {
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, "");
+ sctp_abort_association(inp, stcb,
+ m, iphlen,
+ src, dst,
+ sh, op_err,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ return (2);
+ }
+ break;
+ default:
+ /* unknown chunk type, use bit rules */
+ if (ch->ch.chunk_type & 0x40) {
+ /* Add a error report to the queue */
+ struct mbuf *merr;
+ struct sctp_paramhdr *phd;
+
+ merr = sctp_get_mbuf_for_msg(sizeof(*phd), 0, M_NOWAIT, 1, MT_DATA);
+ if (merr) {
+ phd = mtod(merr, struct sctp_paramhdr *);
+ /*
+ * We cheat and use param
+ * type since we did not
+ * bother to define a error
+ * cause struct. They are
+ * the same basic format
+ * with different names.
+ */
+ phd->param_type =
+ htons(SCTP_CAUSE_UNRECOG_CHUNK);
+ phd->param_length =
+ htons(chk_length + sizeof(*phd));
+ SCTP_BUF_LEN(merr) = sizeof(*phd);
+ SCTP_BUF_NEXT(merr) = SCTP_M_COPYM(m, *offset, chk_length, M_NOWAIT);
+ if (SCTP_BUF_NEXT(merr)) {
+ if (sctp_pad_lastmbuf(SCTP_BUF_NEXT(merr), SCTP_SIZE32(chk_length) - chk_length, NULL) == NULL) {
+ sctp_m_freem(merr);
+ } else {
+ sctp_queue_op_err(stcb, merr);
+ }
+ } else {
+ sctp_m_freem(merr);
+ }
+ }
+ }
+ if ((ch->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_data_chunk *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_data_chunk), (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 (!(TAILQ_EMPTY(&asoc->reasmqueue)))
+ sctp_service_queues(stcb, asoc);
+
+ if (SCTP_GET_STATE(asoc) == 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_seq;
+ 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_seq;
+ tp1->whoTo->find_rtx_pseudo_cumack = 0;
+ }
+ if (tp1->rec.data.TSN_seq == 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_seq,
+ *biggest_newly_acked_tsn)) {
+ *biggest_newly_acked_tsn = tp1->rec.data.TSN_seq;
+ }
+ /*-
+ * 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_seq,
+ tp1->whoTo->this_sack_highest_newack)) {
+ tp1->whoTo->this_sack_highest_newack =
+ tp1->rec.data.TSN_seq;
+ }
+ /*-
+ * 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_seq,
+ 0,
+ 0,
+ SCTP_LOG_TSN_ACKED);
+ }
+ *this_sack_lowest_newack = tp1->rec.data.TSN_seq;
+ }
+ /*-
+ * 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_seq == 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_seq, SCTP_CWND_LOG_FROM_SACK);
+ }
+ if (tp1->rec.data.TSN_seq == 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_seq,
+ 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,
+ (uintptr_t)tp1->whoTo,
+ tp1->rec.data.TSN_seq);
+ }
+ 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-retransmited chunk
+ */
+ tp1->whoTo->net_ack2 += tp1->send_size;
+
+ /*-
+ * update RTO too ?
+ */
+ if (tp1->do_rtt) {
+ if (*rto_ok) {
+ tp1->whoTo->RTO =
+ sctp_calculate_rto(stcb,
+ &stcb->asoc,
+ tp1->whoTo,
+ &tp1->sent_rcv_time,
+ sctp_align_safe_nocopy,
+ 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_seq,
+ stcb->asoc.this_sack_highest_gap)) {
+ stcb->asoc.this_sack_highest_gap =
+ tp1->rec.data.TSN_seq;
+ }
+ 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.stream_number].chunks_on_queues > 0) {
+ stcb->asoc.strmout[tp1->rec.data.stream_number].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", tp1->rec.data.stream_number);
+#endif
+ }
+ 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_seq == theTSN) */
+ if (SCTP_TSN_GT(tp1->rec.data.TSN_seq, 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_seq, 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_seq, 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,
+ (uintptr_t)tp1->whoTo,
+ tp1->rec.data.TSN_seq);
+ }
+ 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_seq,
+ 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_seq;
+ }
+
+ /* 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_seq,
+ tp1->sent,
+ SCTP_FR_LOG_CHECK_STRIKE);
+ }
+ if (SCTP_TSN_GT(tp1->rec.data.TSN_seq, 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? */
+#ifndef __FreeBSD__
+ 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_seq, asoc->this_sack_highest_gap)) {
+ /* 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_seq,
+ tp1->whoTo->this_sack_highest_newack)) {
+ /*
+ * 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_seq,
+ 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_seq)) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(16 + num_dests_sacked,
+ tp1->rec.data.TSN_seq,
+ 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_seq,
+ 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_seq)) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(32 + num_dests_sacked,
+ tp1->rec.data.TSN_seq,
+ 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_seq,
+ 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_seq,
+ 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_seq)) {
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(48 + num_dests_sacked,
+ tp1->rec.data.TSN_seq,
+ 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,
+ (uintptr_t)tp1->whoTo,
+ tp1->rec.data.TSN_seq);
+ }
+ 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 */
+ 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_seq, 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_seq);
+ */
+ 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_seq;
+ }
+
+ 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_seq, 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?
+ */
+#ifndef __FreeBSD__
+ 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_seq, asoc->advanced_peer_ack_point)) {
+ asoc->advanced_peer_ack_point = tp1->rec.data.TSN_seq;
+ a_adv = tp1;
+ } else if (tp1->rec.data.TSN_seq == 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 entry_flight, entry_cnt, ret;
+
+ entry_flight = asoc->total_flight;
+ entry_cnt = asoc->total_flight_count;
+ ret = 0;
+
+ 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_seq,
+ 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,
+ (uintptr_t)tp1->whoTo,
+ tp1->rec.data.TSN_seq);
+ 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,
+ (uintptr_t)tp1->whoTo,
+ tp1->rec.data.TSN_seq);
+ }
+}
+
+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;
+
+ 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 (SCTP_BASE_SYSCTL(sctp_strict_sacks)) {
+ uint32_t send_s;
+
+ if (!TAILQ_EMPTY(&asoc->sent_queue)) {
+ tp1 = TAILQ_LAST(&asoc->sent_queue,
+ sctpchunk_listhead);
+ send_s = tp1->rec.data.TSN_seq + 1;
+ } else {
+ send_s = asoc->sending_seq;
+ }
+ if (SCTP_TSN_GE(cumack, send_s)) {
+#ifndef INVARIANTS
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+#endif
+#ifdef INVARIANTS
+ panic("Impossible sack 1");
+#else
+
+ *abort_now = 1;
+ /* XXX */
+ snprintf(msg, sizeof(msg), "Cum ack %8.8x greater or equal then TSN %8.8x",
+ cumack, send_s);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_25;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ return;
+#endif
+ }
+ }
+ 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_seq)) {
+ 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,
+ (uintptr_t)tp1->whoTo,
+ tp1->rec.data.TSN_seq);
+ }
+ 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-retransmited
+ * chunk
+ */
+ tp1->whoTo->net_ack2 +=
+ tp1->send_size;
+
+ /* update RTO too? */
+ if (tp1->do_rtt) {
+ if (rto_ok) {
+ tp1->whoTo->RTO =
+ /*
+ * sa_ignore
+ * NO_NULL_CHK
+ */
+ sctp_calculate_rto(stcb,
+ asoc, tp1->whoTo,
+ &tp1->sent_rcv_time,
+ sctp_align_safe_nocopy,
+ 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_seq, 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.stream_number].chunks_on_queues > 0) {
+ asoc->strmout[tp1->rec.data.stream_number].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", tp1->rec.data.stream_number);
+#endif
+ }
+ }
+ 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_seq,
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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_INPUT + SCTP_LOC_3);
+ 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) {
+ int to_ticks;
+ 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->RTO == 0) {
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = MSEC_TO_TICKS(net->RTO);
+ }
+ if (net->flight_size) {
+ j++;
+ (void)SCTP_OS_TIMER_START(&net->rxt_timer.timer, to_ticks,
+ sctp_timeout_handler, &net->rxt_timer);
+ 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_OS_TIMER_START(&net->rxt_timer.timer, to_ticks,
+ sctp_timeout_handler, &net->rxt_timer);
+ }
+ } 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_22);
+ }
+ }
+ }
+ 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) ||
+ (asoc->state & SCTP_STATE_SHUTDOWN_RECEIVED)) &&
+ (asoc->locked_on_sending)
+ ) {
+ struct sctp_stream_queue_pending *sp;
+ /* I may be in a state where we got
+ * all across.. but cannot write more due
+ * to a shutdown... we abort since the
+ * user did not indicate EOR in this case. The
+ * sp will be cleaned during free of the asoc.
+ */
+ sp = TAILQ_LAST(&((asoc->locked_on_sending)->outqueue),
+ sctp_streamhead);
+ if ((sp) && (sp->length == 0)) {
+ /* Let cleanup code purge it */
+ if (sp->msg_is_complete) {
+ asoc->stream_queue_cnt--;
+ } else {
+ asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT;
+ asoc->locked_on_sending = NULL;
+ asoc->stream_queue_cnt--;
+ }
+ }
+ }
+ if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) &&
+ (asoc->stream_queue_cnt == 0)) {
+ if (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT) {
+ /* Need to abort here */
+ struct mbuf *op_err;
+
+ abort_out_now:
+ *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_24;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ } else {
+ struct sctp_nets *netp;
+
+ 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_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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, netp);
+ }
+ } else if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (asoc->stream_queue_cnt == 0)) {
+ struct sctp_nets *netp;
+
+ if (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT) {
+ goto abort_out_now;
+ }
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ SCTP_SET_STATE(asoc, SCTP_STATE_SHUTDOWN_ACK_SENT);
+ SCTP_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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);
+ }
+ }
+ }
+ if (lchk) {
+ /* 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);
+ }
+ }
+ if (SCTP_BASE_SYSCTL(sctp_strict_sacks)) {
+ /* reality check */
+ if (!TAILQ_EMPTY(&asoc->sent_queue)) {
+ tp1 = TAILQ_LAST(&asoc->sent_queue,
+ sctpchunk_listhead);
+ send_s = tp1->rec.data.TSN_seq + 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_seq, (void *)tp1);
+ }
+ hopeless_peer:
+ *abort_now = 1;
+ /* XXX */
+ snprintf(msg, sizeof(msg), "Cum ack %8.8x greater or equal then 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_25;
+ 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_26);
+ 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);
+ }
+ }
+ /* process the new consecutive TSN first */
+ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) {
+ if (SCTP_TSN_GE(last_tsn, tp1->rec.data.TSN_seq)) {
+ 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,
+ (uintptr_t)tp1->whoTo,
+ tp1->rec.data.TSN_seq);
+ }
+ 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_seq;
+ tp1->whoTo->saw_newack = 1;
+
+ if (tp1->snd_count < 2) {
+ /*
+ * True non-retransmited
+ * chunk
+ */
+ tp1->whoTo->net_ack2 +=
+ tp1->send_size;
+
+ /* update RTO too? */
+ if (tp1->do_rtt) {
+ if (rto_ok) {
+ tp1->whoTo->RTO =
+ sctp_calculate_rto(stcb,
+ asoc, tp1->whoTo,
+ &tp1->sent_rcv_time,
+ sctp_align_safe_nocopy,
+ 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_seq,
+ 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_seq, 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)) {
+
+ /*
+ * 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.
+ */
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ net->saw_newack = 0;
+ net->this_sack_highest_newack = last_tsn;
+ }
+
+ /*
+ * 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++;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_strict_sacks)) {
+ /*
+ * 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_27);
+
+ }
+ } 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_28);
+ }
+ }
+ }
+ /********************************************/
+ /* 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_seq, cum_ack)) {
+ break;
+ }
+ if (tp1->sent != SCTP_DATAGRAM_NR_ACKED) {
+ if (asoc->strmout[tp1->rec.data.stream_number].chunks_on_queues > 0) {
+ asoc->strmout[tp1->rec.data.stream_number].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", tp1->rec.data.stream_number);
+#endif
+ }
+ }
+ 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_seq,
+ 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 postive 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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,
+ (uintptr_t)tp1->whoTo,
+ tp1->rec.data.TSN_seq);
+ }
+ 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_INPUT + SCTP_LOC_3);
+ 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_30);
+ 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) ||
+ (asoc->state & SCTP_STATE_SHUTDOWN_RECEIVED)) &&
+ (asoc->locked_on_sending)
+ ) {
+ struct sctp_stream_queue_pending *sp;
+ /* I may be in a state where we got
+ * all across.. but cannot write more due
+ * to a shutdown... we abort since the
+ * user did not indicate EOR in this case.
+ */
+ sp = TAILQ_LAST(&((asoc->locked_on_sending)->outqueue),
+ sctp_streamhead);
+ if ((sp) && (sp->length == 0)) {
+ asoc->locked_on_sending = NULL;
+ if (sp->msg_is_complete) {
+ asoc->stream_queue_cnt--;
+ } else {
+ asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT;
+ asoc->stream_queue_cnt--;
+ }
+ }
+ }
+ if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) &&
+ (asoc->stream_queue_cnt == 0)) {
+ if (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT) {
+ /* Need to abort here */
+ struct mbuf *op_err;
+
+ abort_out_now:
+ *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_31;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ return;
+ } else {
+ struct sctp_nets *netp;
+
+ 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_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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, netp);
+ }
+ return;
+ } else if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (asoc->stream_queue_cnt == 0)) {
+ struct sctp_nets *netp;
+
+ if (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT) {
+ goto abort_out_now;
+ }
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ SCTP_SET_STATE(asoc, SCTP_STATE_SHUTDOWN_ACK_SENT);
+ SCTP_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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_22);
+ }
+ }
+ }
+ 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);
+ }
+ }
+ }
+ if (lchk) {
+ /* 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 *ctl, *nctl;
+ struct sctp_association *asoc;
+ uint16_t tt;
+
+ asoc = &stcb->asoc;
+ tt = strmin->last_sequence_delivered;
+ /*
+ * First deliver anything prior to and including the stream no that
+ * came in
+ */
+ TAILQ_FOREACH_SAFE(ctl, &strmin->inqueue, next, nctl) {
+ if (SCTP_SSN_GE(tt, ctl->sinfo_ssn)) {
+ /* this is deliverable now */
+ TAILQ_REMOVE(&strmin->inqueue, ctl, next);
+ /* subtract pending on streams */
+ asoc->size_on_all_streams -= ctl->length;
+ 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, ctl->sinfo_tsn);
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ ctl,
+ &stcb->sctp_socket->so_rcv, 1, SCTP_READ_LOCK_HELD, SCTP_SO_NOT_LOCKED);
+ }
+ } else {
+ /* no more delivery now. */
+ break;
+ }
+ }
+ /*
+ * now we must deliver things in queue the normal way if any are
+ * now ready.
+ */
+ tt = strmin->last_sequence_delivered + 1;
+ TAILQ_FOREACH_SAFE(ctl, &strmin->inqueue, next, nctl) {
+ if (tt == ctl->sinfo_ssn) {
+ /* this is deliverable now */
+ TAILQ_REMOVE(&strmin->inqueue, ctl, next);
+ /* subtract pending on streams */
+ asoc->size_on_all_streams -= ctl->length;
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ /* deliver it to at least the delivery-q */
+ strmin->last_sequence_delivered = ctl->sinfo_ssn;
+ if (stcb->sctp_socket) {
+ sctp_mark_non_revokable(asoc, ctl->sinfo_tsn);
+ sctp_add_to_readq(stcb->sctp_ep, stcb,
+ ctl,
+ &stcb->sctp_socket->so_rcv, 1, SCTP_READ_LOCK_HELD, SCTP_SO_NOT_LOCKED);
+
+ }
+ tt = strmin->last_sequence_delivered + 1;
+ } else {
+ break;
+ }
+ }
+}
+
+static void
+sctp_flush_reassm_for_str_seq(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ uint16_t stream, uint16_t seq)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+
+ /* For each one on here see if we need to toss it */
+ /*
+ * For now large messages held on the reasmqueue 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.
+ */
+ TAILQ_FOREACH_SAFE(chk, &asoc->reasmqueue, sctp_next, nchk) {
+ /* Do not toss it if on a different stream or
+ * marked for unordered delivery in which case
+ * the stream sequence number has no meaning.
+ */
+ if ((chk->rec.data.stream_number != stream) ||
+ ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == SCTP_DATA_UNORDERED)) {
+ continue;
+ }
+ if (chk->rec.data.stream_seq == seq) {
+ /* It needs to be tossed */
+ TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next);
+ if (SCTP_TSN_GT(chk->rec.data.TSN_seq, asoc->tsn_last_delivered)) {
+ asoc->tsn_last_delivered = chk->rec.data.TSN_seq;
+ asoc->str_of_pdapi = chk->rec.data.stream_number;
+ asoc->ssn_of_pdapi = chk->rec.data.stream_seq;
+ asoc->fragment_flags = chk->rec.data.rcv_flags;
+ }
+ asoc->size_on_reasm_queue -= chk->send_size;
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+
+ /* Clear up any stream problem */
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) != SCTP_DATA_UNORDERED &&
+ SCTP_SSN_GT(chk->rec.data.stream_seq, asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered)) {
+ /*
+ * We must dump forward this streams
+ * sequence number if the chunk is
+ * not unordered that is being
+ * skipped. There is a chance that
+ * if the peer does not include the
+ * last fragment in its FWD-TSN we
+ * WILL have a problem here since
+ * you would have a partial chunk in
+ * queue that may not be
+ * deliverable. Also if a Partial
+ * delivery API as started the user
+ * may get a partial chunk. The next
+ * read returning a new chunk...
+ * really ugly but I see no way
+ * around it! Maybe a notify??
+ */
+ asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered = chk->rec.data.stream_seq;
+ }
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ } else if (SCTP_SSN_GT(chk->rec.data.stream_seq, seq)) {
+ /* If the stream_seq is > than the purging one, we are done */
+ break;
+ }
+ }
+}
+
+
+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_tmit_chunk *chk, *nchk;
+ struct sctp_queued_to_read *ctl, *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;
+ 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_33;
+ 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 */
+ /*************************************************************/
+ /*
+ * First service it if pd-api is up, just in case we can progress it
+ * forward
+ */
+ if (asoc->fragmented_delivery_inprogress) {
+ sctp_service_reassembly(stcb, asoc);
+ }
+ /* For each one on here see if we need to toss it */
+ /*
+ * For now large messages held on the reasmqueue 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.
+ */
+ TAILQ_FOREACH_SAFE(chk, &asoc->reasmqueue, sctp_next, nchk) {
+ if (SCTP_TSN_GE(new_cum_tsn, chk->rec.data.TSN_seq)) {
+ /* It needs to be tossed */
+ TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next);
+ if (SCTP_TSN_GT(chk->rec.data.TSN_seq, asoc->tsn_last_delivered)) {
+ asoc->tsn_last_delivered = chk->rec.data.TSN_seq;
+ asoc->str_of_pdapi = chk->rec.data.stream_number;
+ asoc->ssn_of_pdapi = chk->rec.data.stream_seq;
+ asoc->fragment_flags = chk->rec.data.rcv_flags;
+ }
+ asoc->size_on_reasm_queue -= chk->send_size;
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+
+ /* Clear up any stream problem */
+ if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) != SCTP_DATA_UNORDERED &&
+ SCTP_SSN_GT(chk->rec.data.stream_seq, asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered)) {
+ /*
+ * We must dump forward this streams
+ * sequence number if the chunk is
+ * not unordered that is being
+ * skipped. There is a chance that
+ * if the peer does not include the
+ * last fragment in its FWD-TSN we
+ * WILL have a problem here since
+ * you would have a partial chunk in
+ * queue that may not be
+ * deliverable. Also if a Partial
+ * delivery API as started the user
+ * may get a partial chunk. The next
+ * read returning a new chunk...
+ * really ugly but I see no way
+ * around it! Maybe a notify??
+ */
+ asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered = chk->rec.data.stream_seq;
+ }
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ } else {
+ /*
+ * Ok we have gone beyond the end of the
+ * fwd-tsn's mark.
+ */
+ break;
+ }
+ }
+ /*******************************************************/
+ /* 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;
+ struct sctp_strseq *stseq, strseqbuf;
+ offset += sizeof(*fwd);
+
+ SCTP_INP_READ_LOCK(stcb->sctp_ep);
+ num_str = fwd_sz / sizeof(struct sctp_strseq);
+ for (i = 0; i < num_str; i++) {
+ uint16_t st;
+ 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;
+ }
+ /* Convert */
+ st = ntohs(stseq->stream);
+ stseq->stream = st;
+ st = ntohs(stseq->sequence);
+ stseq->sequence = st;
+
+ /* 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 (stseq->stream >= asoc->streamincnt) {
+ /* screwed up streams, stop! */
+ break;
+ }
+ if ((asoc->str_of_pdapi == stseq->stream) &&
+ (asoc->ssn_of_pdapi == stseq->sequence)) {
+ /* 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;
+ }
+ sctp_flush_reassm_for_str_seq(stcb, asoc, stseq->stream, stseq->sequence);
+ TAILQ_FOREACH(ctl, &stcb->sctp_ep->read_queue, next) {
+ if ((ctl->sinfo_stream == stseq->stream) &&
+ (ctl->sinfo_ssn == stseq->sequence)) {
+ str_seq = (stseq->stream << 16) | stseq->sequence;
+ ctl->end_added = 1;
+ ctl->pdapi_aborted = 1;
+ sv = stcb->asoc.control_pdapi;
+ stcb->asoc.control_pdapi = ctl;
+ 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 ((ctl->sinfo_stream == stseq->stream) &&
+ SCTP_SSN_GT(ctl->sinfo_ssn, stseq->sequence)) {
+ /* We are past our victim SSN */
+ break;
+ }
+ }
+ strm = &asoc->strmin[stseq->stream];
+ if (SCTP_SSN_GT(stseq->sequence, strm->last_sequence_delivered)) {
+ /* Update the sequence number */
+ strm->last_sequence_delivered = stseq->sequence;
+ }
+ /* 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);
+
+ if (!TAILQ_EMPTY(&asoc->reasmqueue)) {
+ /* now lets kick out and check for more fragmented delivery */
+ /*sa_ignore NO_NULL_CHK*/
+ sctp_deliver_reasm_check(stcb, &stcb->asoc);
+ }
+}
diff --git a/netwerk/sctp/src/netinet/sctp_indata.h b/netwerk/sctp/src/netinet/sctp_indata.h
new file mode 100755
index 0000000000..d90602a21d
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_indata.h
@@ -0,0 +1,131 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_indata.h 252585 2013-07-03 18:48:43Z 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 stream_no,
+ uint16_t stream_seq, uint8_t flags,
+ struct mbuf *dm);
+
+
+#define sctp_build_readq_entry_mac(_ctl, in_it, context, net, tsn, ppid, stream_no, stream_seq, flags, dm) do { \
+ if (_ctl) { \
+ atomic_add_int(&((net)->ref_count), 1); \
+ (_ctl)->sinfo_stream = stream_no; \
+ (_ctl)->sinfo_ssn = stream_seq; \
+ (_ctl)->sinfo_flags = (flags << 8); \
+ (_ctl)->sinfo_ppid = ppid; \
+ (_ctl)->sinfo_context = context; \
+ (_ctl)->sinfo_timetolive = 0; \
+ (_ctl)->sinfo_tsn = tsn; \
+ (_ctl)->sinfo_cumtsn = tsn; \
+ (_ctl)->sinfo_assoc_id = sctp_get_associd((in_it)); \
+ (_ctl)->length = 0; \
+ (_ctl)->held_length = 0; \
+ (_ctl)->whoFrom = net; \
+ (_ctl)->data = dm; \
+ (_ctl)->tail_mbuf = NULL; \
+ (_ctl)->aux_data = NULL; \
+ (_ctl)->stcb = (in_it); \
+ (_ctl)->port_from = (in_it)->rport; \
+ (_ctl)->spec_flags = 0; \
+ (_ctl)->do_not_ref_stcb = 0; \
+ (_ctl)->end_added = 0; \
+ (_ctl)->pdapi_aborted = 0; \
+ (_ctl)->some_taken = 0; \
+ } \
+} 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 sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *,
+ struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *, uint32_t *,
+#if defined(__FreeBSD__)
+ uint8_t, uint32_t,
+#endif
+ uint32_t, uint16_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..54f2f9ba35
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_input.c
@@ -0,0 +1,6429 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_input.c 279859 2015-03-10 19:49:25Z 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(INET) || defined(INET6)
+#if !defined(__Userspace_os_Windows)
+#include <netinet/udp.h>
+#endif
+#endif
+#if defined(__FreeBSD__)
+#include <sys/smp.h>
+#endif
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 2
+#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, int *abort_no_unlock,
+#if defined(__FreeBSD__)
+ 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__)
+ 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__)
+ 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__)
+ 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__)
+ 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__)
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (stcb)
+ *abort_no_unlock = 1;
+ goto outnow;
+ }
+ /* We are only accepting if we have a socket with positive so_qlimit.*/
+ if ((stcb == NULL) &&
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
+ (inp->sctp_socket == NULL) ||
+ (inp->sctp_socket->so_qlimit == 0))) {
+ /*
+ * 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ }
+ goto outnow;
+ }
+ if ((stcb != NULL) &&
+ (SCTP_GET_STATE(&stcb->asoc) == 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, m, iphlen, offset, src, dst,
+ sh, cp,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port,
+ ((stcb == NULL) ? SCTP_HOLDS_LOCK : SCTP_NOT_LOCKED));
+ }
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+ int unsent_data = 0;
+ unsigned int i;
+ struct sctp_stream_queue_pending *sp;
+ struct sctp_association *asoc;
+
+ /* This function returns the number of streams that have
+ * true unsent data on them. 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;
+ 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);
+ 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);
+ } else {
+ unsent_data++;
+ 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.stream_number >= newcnt) {
+ TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next);
+ asoc->send_queue_cnt--;
+ if (asoc->strmout[chk->rec.data.stream_number].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.stream_number].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.stream_number);
+#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) {
+ TAILQ_REMOVE(&outs->outqueue, sp, next);
+ asoc->stream_queue_cnt--;
+ 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*/
+ }
+ }
+ }
+ /* cut back the count */
+ asoc->pre_open_streams = newcnt;
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ asoc->strm_realoutsize = asoc->streamoutcnt = asoc->pre_open_streams;
+
+ /* 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 */
+ struct sctp_queued_to_read *ctl, *nctl;
+
+ for (i = 0; i < asoc->streamincnt; i++) {
+ TAILQ_FOREACH_SAFE(ctl, &asoc->strmin[i].inqueue, next, nctl) {
+ TAILQ_REMOVE(&asoc->strmin[i].inqueue, ctl, next);
+ sctp_free_remote_addr(ctl->whoFrom);
+ ctl->whoFrom = NULL;
+ sctp_m_freem(ctl->data);
+ ctl->data = NULL;
+ sctp_free_a_readq(stcb, ctl);
+ }
+ }
+ 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].stream_no = i;
+ asoc->strmin[i].last_sequence_delivered = 0xffff;
+ TAILQ_INIT(&asoc->strmin[i].inqueue);
+ 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__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id)
+{
+ struct sctp_association *asoc;
+ struct mbuf *op_err;
+ int retval, abort_flag;
+ uint32_t initack_limit;
+ int nat_friendly = 0;
+
+ /* First verify that we have no illegal param's */
+ abort_flag = 0;
+
+ op_err = sctp_arethere_unrecognized_parameters(m,
+ (offset + sizeof(struct sctp_init_chunk)),
+ &abort_flag, (struct sctp_chunkhdr *)cp, &nat_friendly);
+ 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);
+ }
+ 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) {
+ 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))) {
+ 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__)
+ 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_4);
+
+ /* calculate the RTO */
+ net->RTO = sctp_calculate_rto(stcb, asoc, net, &asoc->time_entered, sctp_align_safe_nocopy,
+ SCTP_RTT_FROM_NON_DATA);
+
+ retval = sctp_send_cookie_echo(m, offset, stcb, net);
+ if (retval < 0) {
+ /*
+ * No cookie, we probably should send a op error. But in any
+ * case if there is no cookie in the INIT-ACK, we can
+ * abandon the peer, its broke.
+ */
+ if (retval == -3) {
+ /* We abort with an error of missing mandatory param */
+ op_err = sctp_generate_cause(SCTP_CAUSE_MISSING_PARAM, "");
+ if (op_err) {
+ /*
+ * Expand beyond to include the mandatory
+ * param cookie
+ */
+ struct sctp_inv_mandatory_param *mp;
+
+ SCTP_BUF_LEN(op_err) =
+ sizeof(struct sctp_inv_mandatory_param);
+ mp = mtod(op_err,
+ struct sctp_inv_mandatory_param *);
+ /* Subtract the reserved param */
+ mp->length =
+ htons(sizeof(struct sctp_inv_mandatory_param) - 2);
+ mp->num_param = htonl(1);
+ mp->param = htons(SCTP_STATE_COOKIE);
+ mp->resv = 0;
+ }
+ sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ *abort_no_unlock = 1;
+ }
+ return (retval);
+ }
+
+ return (0);
+}
+
+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 efficent 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_3);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, r_net);
+ }
+ 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 */
+ r_net->RTO = sctp_calculate_rto(stcb, &stcb->asoc, r_net, &tv, sctp_align_safe_nocopy,
+ 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_3);
+ 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_TIMER+SCTP_LOC_7);
+ 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,
+ stcb->asoc.deleted_primary);
+ }
+ }
+}
+
+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
+ */
+ struct sctpasochead *head;
+
+ if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_WAIT) {
+ /* generate a new vtag and send init */
+ LIST_REMOVE(stcb, sctp_asocs);
+ stcb->asoc.my_vtag = sctp_select_a_tag(stcb->sctp_ep, stcb->sctp_ep->sctp_lport, stcb->rport, 1);
+ 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_send_initiate(stcb->sctp_ep, stcb, SCTP_SO_NOT_LOCKED);
+ return (1);
+ }
+ if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_ECHOED) {
+ /* 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);
+ stcb->asoc.state &= ~SCTP_STATE_COOKIE_ECHOED;
+ stcb->asoc.state |= SCTP_STATE_COOKIE_WAIT;
+ sctp_stop_all_cookie_timers(stcb);
+ sctp_toss_old_cookies(stcb, &stcb->asoc);
+ stcb->asoc.my_vtag = sctp_select_a_tag(stcb->sctp_ep, stcb->sctp_ep->sctp_lport, stcb->rport, 1);
+ 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_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);
+}
+
+
+static void
+sctp_handle_abort(struct sctp_abort_chunk *abort,
+ struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+#endif
+ uint16_t len;
+ uint16_t error;
+
+ SCTPDBG(SCTP_DEBUG_INPUT2, "sctp_handle_abort: handling ABORT\n");
+ if (stcb == NULL)
+ return;
+
+ len = ntohs(abort->ch.chunk_length);
+ if (len > sizeof (struct sctp_chunkhdr)) {
+ /* Need to check the cause codes for our
+ * two magic nat aborts which don't kill the assoc
+ * necessarily.
+ */
+ struct sctp_missing_nat_state *natc;
+
+ natc = (struct sctp_missing_nat_state *)(abort + 1);
+ error = ntohs(natc->cause);
+ 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;
+ }
+ } 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;
+ }
+ }
+ } else {
+ error = 0;
+ }
+ /* stop any receive timers */
+ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INPUT+SCTP_LOC_6);
+ /* 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->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&stcb->asoc) == 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(SCTP_SO_LOCK_TESTING)
+ 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
+ stcb->asoc.state |= SCTP_STATE_WAS_ABORTED;
+ (void)sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT+SCTP_LOC_6);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ SCTPDBG(SCTP_DEBUG_INPUT2, "sctp_handle_abort: finished\n");
+}
+
+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;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(asoc) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED)) {
+ return;
+ }
+ if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_shutdown_chunk)) {
+ /* Shutdown NOT the expected size */
+ return;
+ } else {
+ 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);
+ 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(SCTP_SO_LOCK_TESTING)
+ 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_sorwakeup(stcb->sctp_ep, stcb->sctp_socket);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ /* goto SHUTDOWN_RECEIVED state to block new requests */
+ if (stcb->sctp_socket) {
+ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT)) {
+ SCTP_SET_STATE(asoc, SCTP_STATE_SHUTDOWN_RECEIVED);
+ SCTP_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ /* 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(asoc) == 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_8);
+ }
+ /* 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(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_ACK_SENT);
+ SCTP_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ sctp_stop_timers_for_shutdown(stcb);
+ sctp_send_shutdown_ack(stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, stcb->sctp_ep,
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(asoc) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(asoc) == 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(asoc) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(asoc) != 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+#ifdef INVARIANTS
+ if (!TAILQ_EMPTY(&asoc->send_queue) ||
+ !TAILQ_EMPTY(&asoc->sent_queue) ||
+ !stcb->asoc.ss_functions.sctp_ss_is_empty(stcb, asoc)) {
+ 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_9);
+ /* 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(SCTP_SO_LOCK_TESTING)
+ 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_10);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+}
+
+/*
+ * Skip past the param header and then we will find the chunk that caused the
+ * problem. There are two possiblities ASCONF or FWD-TSN other than that and
+ * our peer must be broken.
+ */
+static void
+sctp_process_unrecog_chunk(struct sctp_tcb *stcb, struct sctp_paramhdr *phdr,
+ struct sctp_nets *net)
+{
+ struct sctp_chunkhdr *chk;
+
+ chk = (struct sctp_chunkhdr *)((caddr_t)phdr + sizeof(*phdr));
+ switch (chk->chunk_type) {
+ case SCTP_ASCONF_ACK:
+ case SCTP_ASCONF:
+ sctp_asconf_cleanup(stcb, net);
+ break;
+ case SCTP_FORWARD_CUM_TSN:
+ stcb->asoc.prsctp_supported = 0;
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "Peer does not support chunk type %d(%x)??\n",
+ chk->chunk_type, (uint32_t) chk->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, struct sctp_paramhdr *phdr)
+{
+ struct sctp_paramhdr *pbad;
+
+ pbad = phdr + 1;
+ switch (ntohs(pbad->param_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(%x)??\n",
+ pbad->param_type, (uint32_t) pbad->param_type);
+ break;
+ }
+}
+
+static int
+sctp_handle_error(struct sctp_chunkhdr *ch,
+ struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ int chklen;
+ struct sctp_paramhdr *phdr;
+ uint16_t error, error_type;
+ uint16_t error_len;
+ struct sctp_association *asoc;
+ int adjust;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+#endif
+
+ /* parse through all of the errors and process */
+ asoc = &stcb->asoc;
+ phdr = (struct sctp_paramhdr *)((caddr_t)ch +
+ sizeof(struct sctp_chunkhdr));
+ chklen = ntohs(ch->chunk_length) - sizeof(struct sctp_chunkhdr);
+ error = 0;
+ while ((size_t)chklen >= sizeof(struct sctp_paramhdr)) {
+ /* Process an Error Cause */
+ error_type = ntohs(phdr->param_type);
+ error_len = ntohs(phdr->param_length);
+ if ((error_len > chklen) || (error_len == 0)) {
+ /* invalid param length for this param */
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Bogus length in error param- chunk left:%d errorlen:%d\n",
+ chklen, error_len);
+ return (0);
+ }
+ if (error == 0) {
+ /* report the first error cause */
+ error = error_type;
+ }
+ switch (error_type) {
+ 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 %d back? We have a bug :/ (or do they?)\n",
+ error_type);
+ 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 (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) {
+ int *p;
+
+ p = (int *)((caddr_t)phdr + sizeof(*phdr));
+ /* Save the time doubled */
+ asoc->cookie_preserve_req = ntohl(*p) << 1;
+ 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(SCTP_SO_LOCK_TESTING)
+ 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_11);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (-1);
+ }
+ /* blast back to INIT state */
+ sctp_toss_old_cookies(stcb, &stcb->asoc);
+ asoc->state &= ~SCTP_STATE_COOKIE_ECHOED;
+ asoc->state |= 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 recieve 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:
+ sctp_process_unrecog_chunk(stcb, phdr, net);
+ break;
+ case SCTP_CAUSE_UNRECOG_PARAM:
+ sctp_process_unrecog_param(stcb, phdr);
+ 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 Operational Error?<%d>?\n",
+ error_type);
+ 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 error type = 0x%xh\n",
+ error_type);
+ break;
+ }
+ adjust = SCTP_SIZE32(error_len);
+ chklen -= adjust;
+ phdr = (struct sctp_paramhdr *)((caddr_t)phdr + adjust);
+ }
+ sctp_ulp_notify(SCTP_NOTIFY_REMOTE_ERROR, stcb, error, 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__)
+ 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__)
+ 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__)
+ 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__)
+ 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__)
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, net->port);
+ *abort_no_unlock = 1;
+ return (-1);
+ }
+ /* process according to association state... */
+ switch (stcb->asoc.state & SCTP_STATE_MASK) {
+ 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__)
+ 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->asoc, 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__)
+ 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__)
+ 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_nets *net;
+ struct mbuf *op_err;
+ int init_offset, initack_offset, i;
+ int retval;
+ int spec_flag = 0;
+ uint32_t how_indx;
+#if defined(SCTP_DETAILED_STR_STATS)
+ int j;
+#endif
+
+ net = *netp;
+ /* I know that the TCB is non-NULL from the caller */
+ asoc = &stcb->asoc;
+ for (how_indx = 0; how_indx < sizeof(asoc->cookie_how); how_indx++) {
+ if (asoc->cookie_how[how_indx] == 0)
+ break;
+ }
+ if (how_indx < sizeof(asoc->cookie_how)) {
+ asoc->cookie_how[how_indx] = 1;
+ }
+ if (SCTP_GET_STATE(asoc) == 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__)
+ mflowtype, mflowid,
+#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(asoc)) {
+ 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_12);
+ sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, net, SCTP_FROM_SCTP_INPUT+SCTP_LOC_13);
+ /* update current state */
+ if (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED)
+ SCTP_STAT_INCR_COUNTER32(sctps_activeestab);
+ else
+ SCTP_STAT_INCR_COUNTER32(sctps_collisionestab);
+
+ SCTP_SET_STATE(asoc, SCTP_STATE_OPEN);
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, asoc->primary_destination);
+ }
+ 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)) &&
+ (inp->sctp_socket->so_qlimit == 0)
+ ) {
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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
+ */
+ net->hb_responded = 1;
+ net->RTO = sctp_calculate_rto(stcb, asoc, net,
+ &cookie->time_entered,
+ sctp_align_unsafe_makecopy,
+ 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)) {
+ 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(asoc) == 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__)
+ mflowtype, mflowid,
+#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_14);
+ 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);
+ asoc->pre_open_streams = ntohs(initack_cp->init.num_outbound_streams);
+
+ 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.
+ */
+ struct sctp_tmit_chunk *chk;
+ 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)) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 10;
+ return (NULL);
+ }
+ if ((asoc->state & SCTP_STATE_COOKIE_WAIT) ||
+ (asoc->state & 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)) &&
+ (inp->sctp_socket->so_qlimit == 0)) {
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+#endif
+ stcb->sctp_ep->sctp_flags |=
+ SCTP_PCB_FLAGS_CONNECTED;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ if (SCTP_GET_STATE(asoc) == 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(asoc) == SCTP_STATE_OPEN) {
+ SCTP_STAT_INCR_COUNTER32(sctps_restartestab);
+ } else {
+ SCTP_STAT_INCR_COUNTER32(sctps_collisionestab);
+ }
+ SCTP_SET_STATE(asoc, SCTP_STATE_OPEN);
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, asoc->primary_destination);
+ }
+ 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(SCTP_SO_LOCK_TESTING)
+ 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__)
+ 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_15);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net, SCTP_FROM_SCTP_INPUT+SCTP_LOC_16);
+
+ /* notify upper layer */
+ *notification = SCTP_NOTIFY_ASSOC_RESTART;
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_OPEN) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT)) {
+ SCTP_STAT_INCR_GAUGE32(sctps_currestab);
+ }
+ if (SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) {
+ SCTP_STAT_INCR_GAUGE32(sctps_restartestab);
+ } else if (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) {
+ SCTP_STAT_INCR_GAUGE32(sctps_collisionestab);
+ }
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ SCTP_SET_STATE(asoc, SCTP_STATE_OPEN);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, asoc->primary_destination);
+
+ } else if (!(asoc->state & SCTP_STATE_SHUTDOWN_SENT)) {
+ /* move to OPEN state, if not in SHUTDOWN_SENT */
+ SCTP_SET_STATE(asoc, SCTP_STATE_OPEN);
+ }
+ asoc->pre_open_streams =
+ ntohs(initack_cp->init.num_outbound_streams);
+ 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(SCTP_SO_LOCK_TESTING)
+ 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].stream_no = i;
+ stcb->asoc.strmout[i].next_sequence_send = 0;
+ stcb->asoc.strmout[i].last_msg_incomplete = 0;
+ }
+ /* 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(SCTP_SO_LOCK_TESTING)
+ 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)) {
+ 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__)
+ 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_PARAM_BUFFER_SIZE];
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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,
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ (struct thread *)NULL
+#elif defined(__Windows__)
+ (PKTHREAD)NULL
+#else
+ (struct proc *)NULL
+#endif
+ );
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ return (NULL);
+ }
+ /* get the correct sctp_nets */
+ if (netp)
+ *netp = sctp_findnet(stcb, init_src);
+
+ 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.
+ */
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC,
+ SCTP_FROM_SCTP_INPUT+SCTP_LOC_16);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ 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->pre_open_streams = ntohs(initack_cp->init.num_outbound_streams);
+ 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) */
+ if (netp)
+ retval = sctp_process_init(init_cp, stcb);
+ else
+ retval = 0;
+ if (retval < 0) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT+SCTP_LOC_16);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ 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)) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT+SCTP_LOC_17);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ 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;
+
+ auth = (struct sctp_auth_chunk *)
+ sctp_m_getptr(m, auth_offset, auth_len, auth_chunk_buf);
+ 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");
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT+SCTP_LOC_18);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return (NULL);
+ } else {
+ /* remaining chunks checked... good to go */
+ stcb->asoc.authenticated = 1;
+ }
+ }
+ /* update current state */
+ SCTPDBG(SCTP_DEBUG_INPUT2, "moving to OPEN state\n");
+ SCTP_SET_STATE(asoc, SCTP_STATE_OPEN);
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, asoc->primary_destination);
+ }
+ sctp_stop_all_cookie_timers(stcb);
+ SCTP_STAT_INCR_COUNTER32(sctps_passiveestab);
+ SCTP_STAT_INCR_GAUGE32(sctps_currestab);
+
+ /*
+ * 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:
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_TCB_UNLOCK(stcb);
+ SCTP_SOCKET_LOCK(so, 1);
+ SCTP_TCB_LOCK(stcb);
+#endif
+ (void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT+SCTP_LOC_19);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ return (NULL);
+ }
+
+ /* 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)) &&
+ (inp->sctp_socket->so_qlimit == 0)) {
+ /*
+ * 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ } else if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (inp->sctp_socket->so_qlimit)) {
+ /*
+ * 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.
+ */
+ ;
+ }
+ /* since we did not send a HB make sure we don't double things */
+ if ((netp) && (*netp))
+ (*netp)->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);
+ }
+ /* calculate the RTT */
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+ if ((netp) && (*netp)) {
+ (*netp)->RTO = sctp_calculate_rto(stcb, asoc, *netp,
+ &cookie->time_entered, sctp_align_unsafe_makecopy,
+ 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__)
+ 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->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 (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);
+ }
+ /*
+ * 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 (memcmp(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 (memcmp(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 + TICKS_TO_SEC(cookie->cookie_life);
+ time_expires.tv_usec = cookie->time_entered.tv_usec;
+ /* TODO sctp_constants.h needs alternative time macros when
+ * _KERNEL is undefined.
+ */
+#ifndef __FreeBSD__
+ if (timercmp(&now, &time_expires, >))
+#else
+ if (timevalcmp(&now, &time_expires, >))
+#endif
+ {
+ /* cookie is stale! */
+ struct mbuf *op_err;
+ struct sctp_stale_cookie_msg *scm;
+ uint32_t tim;
+ op_err = sctp_get_mbuf_for_msg(sizeof(struct sctp_stale_cookie_msg),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (op_err == NULL) {
+ /* FOOBAR */
+ return (NULL);
+ }
+ /* Set the len */
+ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_stale_cookie_msg);
+ scm = mtod(op_err, struct sctp_stale_cookie_msg *);
+ scm->ph.param_type = htons(SCTP_CAUSE_STALE_COOKIE);
+ scm->ph.param_length = htons((sizeof(struct sctp_paramhdr) +
+ (sizeof(uint32_t))));
+ /* seconds to usec */
+ tim = (now.tv_sec - time_expires.tv_sec) * 1000000;
+ /* add in usec */
+ if (tim == 0)
+ tim = now.tv_usec - cookie->time_entered.tv_usec;
+ scm->time_usec = htonl(tim);
+ sctp_send_operr_to(src, dst, sh, cookie->peers_vtag, op_err,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#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__)
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ }
+
+ if (*stcb == NULL) {
+ /* still no TCB... must be bad cookie-echo */
+ return (NULL);
+ }
+#if defined(__FreeBSD__)
+ if ((*netp != NULL) && (mflowtype != M_HASHTYPE_NONE)) {
+ (*netp)->flowtype = mflowtype;
+ }
+#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, 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;
+#if (defined(__FreeBSD__) && __FreeBSD_version < 700000)
+ /*
+ * We do this to keep the sockets side happy during
+ * the sonewcon ONLY.
+ */
+ NET_LOCK_GIANT();
+#endif
+ atomic_add_int(&(*stcb)->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK((*stcb));
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_SET(oso->so_vnet);
+#endif
+#if defined(__APPLE__)
+ SCTP_SOCKET_LOCK(oso, 1);
+#endif
+ so = sonewconn(oso, 0
+#if defined(__APPLE__)
+ ,NULL
+#endif
+#ifdef __Panda__
+ ,NULL , (*inp_p)->def_vrf_id
+#endif
+ );
+#if (defined(__FreeBSD__) && __FreeBSD_version < 700000)
+ NET_UNLOCK_GIANT();
+#endif
+#if defined(__APPLE__)
+ SCTP_SOCKET_UNLOCK(oso, 1);
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ 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(SCTP_SO_LOCK_TESTING)
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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_20);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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->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) {
+ (*stcb)->asoc.state |= 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__)
+ 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(SCTP_SO_LOCK_TESTING)
+ atomic_add_int(&(*stcb)->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK((*stcb));
+ SCTP_SOCKET_LOCK(so, 1);
+#endif
+ soisconnected(so);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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;
+
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_cookie_ack: handling COOKIE-ACK\n");
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+
+ asoc = &stcb->asoc;
+
+ sctp_stop_all_cookie_timers(stcb);
+ /* process according to association state */
+ if (SCTP_GET_STATE(asoc) == 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(asoc, 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, asoc->primary_destination);
+
+ }
+ /* update RTO */
+ SCTP_STAT_INCR_COUNTER32(sctps_activeestab);
+ SCTP_STAT_INCR_GAUGE32(sctps_currestab);
+ if (asoc->overall_error_count == 0) {
+ net->RTO = sctp_calculate_rto(stcb, asoc, net,
+ &asoc->time_entered, sctp_align_safe_nocopy,
+ 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(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+
+#endif
+ stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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);
+ if (!TAILQ_EMPTY(&asoc->sent_queue)) {
+ /* Restart the timer if we have pending data */
+ struct sctp_tmit_chunk *chk;
+
+ chk = TAILQ_FIRST(&asoc->sent_queue);
+ 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_seq;
+ }
+
+ /* Find where it was sent to if possible. */
+ net = NULL;
+ TAILQ_FOREACH(lchk, &stcb->asoc.sent_queue, sctp_next) {
+ if (lchk->rec.data.TSN_seq == tsn) {
+ net = lchk->whoTo;
+ net->ecn_prev_cwnd = lchk->rec.data.cwnd_at_send;
+ break;
+ }
+ if (SCTP_TSN_GT(lchk->rec.data.TSN_seq, 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;
+ 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(chk, &stcb->asoc.control_send_queue, sctp_next) {
+ 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);
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ stcb->asoc.ctrl_queue_cnt--;
+ 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)
+{
+ struct sctp_association *asoc;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+#endif
+
+ SCTPDBG(SCTP_DEBUG_INPUT2,
+ "sctp_handle_shutdown_complete: handling SHUTDOWN-COMPLETE\n");
+ if (stcb == NULL)
+ return;
+
+ asoc = &stcb->asoc;
+ /* process according to association state */
+ if (SCTP_GET_STATE(asoc) != 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(&asoc->send_queue) ||
+ !TAILQ_EMPTY(&asoc->sent_queue) ||
+ !stcb->asoc.ss_functions.sctp_ss_is_empty(stcb, asoc)) {
+ 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_22);
+ 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(SCTP_SO_LOCK_TESTING)
+ 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_23);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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_seq == tsn) {
+ /* found it */
+ break;
+ }
+ if (SCTP_TSN_GT(tp1->rec.data.TSN_seq, 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_seq == 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_seq;
+ }
+
+ /* restart the timer */
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep,
+ stcb, tp1->whoTo, SCTP_FROM_SCTP_INPUT+SCTP_LOC_24);
+ 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,
+ (uintptr_t)stcb,
+ tp1->rec.data.TSN_seq);
+ }
+ 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_25);
+ 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_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 0xffff 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_sequence_delivered = 0xffff;
+ }
+ } else {
+ list = NULL;
+ for (i = 0; i < stcb->asoc.streamincnt; i++) {
+ stcb->asoc.strmin[i].last_sequence_delivered = 0xffff;
+ }
+ }
+ 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_sequence_send = 0;
+ }
+ } else {
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ stcb->asoc.strmout[i].next_sequence_send = 0;
+ }
+ }
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_SEND, stcb, number_entries, (void *)list, SCTP_SO_NOT_LOCKED);
+}
+
+
+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 = stcb->asoc.str_reset;
+
+ if (stcb->asoc.str_reset == NULL) {
+ return;
+ }
+ asoc = &stcb->asoc;
+
+ sctp_timer_stop(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo, SCTP_FROM_SCTP_INPUT+SCTP_LOC_26);
+ TAILQ_REMOVE(&asoc->control_send_queue,
+ chk,
+ sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ asoc->ctrl_queue_cnt--;
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ /*sa_ignore NO_NULL_CHK*/
+ stcb->asoc.str_reset = NULL;
+}
+
+
+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 lparm_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);
+ lparm_len = ntohs(req_param->ph.param_length);
+ if (type == SCTP_STR_RESET_OUT_REQUEST) {
+ req_out_param = (struct sctp_stream_reset_out_request *)req_param;
+ number_entries = (lparm_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 {
+ sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_FAILED_OUT, stcb, number_entries, req_out_param->list_of_streams, SCTP_SO_NOT_LOCKED);
+ }
+ } else if (type == SCTP_STR_RESET_IN_REQUEST) {
+ req_in_param = (struct sctp_stream_reset_in_request *)req_param;
+ number_entries = (lparm_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 */
+ stcb->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);
+ }
+ }
+ }
+ 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));
+ for (i = 0; i < number_entries; i++) {
+ temp = ntohs(req->list_of_streams[i]);
+ req->list_of_streams[i] = temp;
+ }
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_PERFORMED;
+ sctp_add_stream_reset_out(chk, number_entries, req->list_of_streams,
+ asoc->str_reset_seq_out,
+ seq, (asoc->sending_seq - 1));
+ asoc->stream_reset_out_is_outstanding = 1;
+ asoc->str_reset = chk;
+ sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo);
+ stcb->asoc.stream_reset_outstanding++;
+ } else {
+ /* Can't do it, since we have sent one out */
+ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_ERR_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) {
+ 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);
+ }
+}
+
+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->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_PERFORMED;
+ }
+ 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);
+ stcb->asoc.strmin[i].stream_no = i;
+ stcb->asoc.strmin[i].last_sequence_delivered = oldstrm[i].last_sequence_delivered;
+ stcb->asoc.strmin[i].delivery_started = oldstrm[i].delivery_started;
+ /* now anything on those queues? */
+ TAILQ_FOREACH_SAFE(ctl, &oldstrm[i].inqueue, next, nctl) {
+ TAILQ_REMOVE(&oldstrm[i].inqueue, ctl, next);
+ TAILQ_INSERT_TAIL(&stcb->asoc.strmin[i].inqueue, ctl, next);
+ }
+ }
+ /* Init the new streams */
+ for (i = stcb->asoc.streamincnt; i < num_stream; i++) {
+ TAILQ_INIT(&stcb->asoc.strmin[i].inqueue);
+ stcb->asoc.strmin[i].stream_no = i;
+ stcb->asoc.strmin[i].last_sequence_delivered = 0xffff;
+ 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, 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);
+ }
+}
+
+#if !defined(__Panda__)
+#ifdef __GNUC__
+__attribute__ ((noinline))
+#endif
+#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 debateable 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
+ */
+#if !defined(__Panda__)
+#ifdef __GNUC__
+__attribute__ ((noinline))
+#endif
+#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__)
+ uint8_t mflowtype, uint32_t mflowid,
+#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;
+ 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];
+ struct sctp_tcb *locked_tcb = stcb;
+ int got_auth = 0;
+ uint32_t auth_offset = 0, auth_len = 0;
+ int auth_skipped = 0;
+ int asconf_cnt = 0;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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);
+
+ /* validate chunk header length... */
+ if (ntohs(ch->chunk_length) < sizeof(*ch)) {
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Invalid header length %d\n",
+ ntohs(ch->chunk_length));
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ /*
+ * validate the verification tag
+ */
+ vtag_in = ntohl(sh->v_tag);
+
+ if (locked_tcb) {
+ SCTP_TCB_LOCK_ASSERT(locked_tcb);
+ }
+ 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 (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ 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;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_chunkhdr), chunk_buf);
+ }
+ if (ch == NULL) {
+ /* Help */
+ *offset = length;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ 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);
+ } else {
+ locked_tcb = stcb;
+ }
+
+ /* now go back and verify any auth chunk to be sure */
+ if (auth_skipped && (stcb != NULL)) {
+ struct sctp_auth_chunk *auth;
+
+ auth = (struct sctp_auth_chunk *)
+ sctp_m_getptr(m, auth_offset,
+ auth_len, chunk_buf);
+ got_auth = 1;
+ auth_skipped = 0;
+ if ((auth == NULL) || sctp_handle_auth(stcb, auth, m,
+ auth_offset)) {
+ /* auth HMAC failed so dump it */
+ *offset = length;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ } else {
+ /* remaining chunks are HMAC checked */
+ stcb->asoc.authenticated = 1;
+ }
+ }
+ }
+ if (stcb == NULL) {
+ snprintf(msg, sizeof(msg), "OOTB, %s:%d at %s\n", __FILE__, __LINE__, __FUNCTION__);
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ *offset = length;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ 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) &&
+ (vtag_in == asoc->peer_vtag))) {
+ /* this is valid */
+ } else {
+ /* drop this packet... */
+ SCTP_STAT_INCR(sctps_badvtag);
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ 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 (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ snprintf(msg, sizeof(msg), "OOTB, %s:%d at %s\n", __FILE__, __LINE__, __FUNCTION__);
+ 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__)
+ mflowtype, mflowid,
+#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 (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *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->asoc) == SCTP_STATE_COOKIE_ECHOED)) {
+ /* implied cookie-ack.. we must have lost the ack */
+ 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_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;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ SCTP_STAT_INCR_COUNTER64(sctps_incontrolchunks);
+ /*
+ * 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.
+ */
+ if ((ch->chunk_type == SCTP_INITIATION_ACK) ||
+ (ch->chunk_type == SCTP_INITIATION)) {
+ /* get an init-ack chunk */
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ sizeof(struct sctp_init_ack_chunk), chunk_buf);
+ if (ch == NULL) {
+ *offset = length;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ } else {
+ /* For cookies and all other chunks. */
+ if (chk_length > sizeof(chunk_buf)) {
+ /*
+ * use just the size of the chunk buffer
+ * so the front part of our chunks fit in
+ * contiguous space up to the chunk buffer
+ * size (508 bytes).
+ * For chunks that need to get more than that
+ * they must use the sctp_m_getptr() function
+ * or other means (e.g. know how to parse mbuf
+ * chains). Cookies do this already.
+ */
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ (sizeof(chunk_buf) - 4),
+ chunk_buf);
+ if (ch == NULL) {
+ *offset = length;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ } else {
+ /* We can fit it all */
+ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset,
+ chk_length, chunk_buf);
+ if (ch == NULL) {
+ SCTP_PRINTF("sctp_process_control: Can't get the all data....\n");
+ *offset = length;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ 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) &&
+ (stcb->asoc.auth_supported == 1) &&
+ 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))) {
+ op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
+ "INIT not the only chunk");
+ sctp_abort_association(inp, stcb, m, iphlen,
+ src, dst, sh, op_err,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ *offset = length;
+ 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__)
+ 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, &abort_no_unlock,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ *offset = length;
+ if ((!abort_no_unlock) && (locked_tcb)) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ 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) && (stcb->asoc.total_output_queue_size)) {
+ ;
+ } else {
+ if (locked_tcb != stcb) {
+ /* Very unlikely */
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ if (stcb) {
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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_27);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ if ((netp) && (*netp)) {
+ 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__)
+ 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);
+ }
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ break;
+ case SCTP_SELECTIVE_ACK:
+ {
+ struct sctp_sack_chunk *sack;
+ int abort_now = 0;
+ uint32_t a_rwnd, cum_ack;
+ uint16_t num_seg, num_dup;
+ uint8_t flags;
+ int offset_seg, offset_dup;
+
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_SACK\n");
+ SCTP_STAT_INCR(sctps_recvsacks);
+ if (stcb == NULL) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "No stcb when processing SACK chunk\n");
+ break;
+ }
+ if (chk_length < sizeof(struct sctp_sack_chunk)) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "Bad size on SACK chunk, too small\n");
+ break;
+ }
+ if (SCTP_GET_STATE(&stcb->asoc) == 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;
+ }
+ sack = (struct sctp_sack_chunk *)ch;
+ flags = ch->chunk_flags;
+ cum_ack = ntohl(sack->sack.cum_tsn_ack);
+ num_seg = ntohs(sack->sack.num_gap_ack_blks);
+ num_dup = ntohs(sack->sack.num_dup_tsns);
+ a_rwnd = (uint32_t) 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);
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_SACK process cum_ack:%x num_seg:%d a_rwnd:%d\n",
+ cum_ack, num_seg, a_rwnd);
+ stcb->asoc.seen_a_sack_this_pkt = 1;
+ if ((stcb->asoc.pr_sctp_cnt == 0) &&
+ (num_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 && *netp)
+ sctp_handle_sack(m, offset_seg, offset_dup, stcb,
+ num_seg, 0, 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;
+ /* EY - nr_sack: If the received chunk is an nr_sack chunk */
+ case SCTP_NR_SELECTIVE_ACK:
+ {
+ struct sctp_nr_sack_chunk *nr_sack;
+ 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, "SCTP_NR_SACK\n");
+ SCTP_STAT_INCR(sctps_recvsacks);
+ if (stcb == NULL) {
+ SCTPDBG(SCTP_DEBUG_INDATA1, "No stcb when processing NR-SACK chunk\n");
+ break;
+ }
+ 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->asoc) == 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;
+ }
+ nr_sack = (struct sctp_nr_sack_chunk *)ch;
+ flags = ch->chunk_flags;
+ 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 = (uint32_t) 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 * sizeof(struct sctp_gap_ack_block);
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_NR_SACK process cum_ack:%x num_seg:%d a_rwnd:%d\n",
+ 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 && *netp)
+ 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) && netp && *netp) {
+ SCTP_STAT_INCR(sctps_recvheartbeat);
+ sctp_send_heartbeat_ack(stcb, m, *offset,
+ chk_length, *netp);
+
+ /* 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;
+ }
+ 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;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ /* He's alive so give him credit */
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_INPUT,
+ __LINE__);
+ }
+ stcb->asoc.overall_error_count = 0;
+ SCTP_STAT_INCR(sctps_recvheartbeatack);
+ if (netp && *netp)
+ 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);
+ if ((stcb) && netp && *netp)
+ sctp_handle_abort((struct sctp_abort_chunk *)ch,
+ stcb, *netp);
+ *offset = length;
+ 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;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ if (netp && *netp) {
+ 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) && (netp) && (*netp))
+ 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) && netp && *netp && sctp_handle_error(ch, stcb, *netp) < 0) {
+ *offset = length;
+ return (NULL);
+ }
+ break;
+ case SCTP_COOKIE_ECHO:
+ SCTPDBG(SCTP_DEBUG_INPUT3,
+ "SCTP_COOKIE-ECHO, stcb %p\n", (void *)stcb);
+ if ((stcb) && (stcb->asoc.total_output_queue_size)) {
+ ;
+ } else {
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* We are not interested anymore */
+ abend:
+ if (stcb) {
+ 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.
+ */
+
+ if ((stcb == NULL) && (inp->sctp_socket->so_qlen >= inp->sctp_socket->so_qlimit)) {
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ }
+ *offset = length;
+ return (NULL);
+ } else {
+ struct mbuf *ret_buf;
+ struct sctp_inpcb *linp;
+ if (stcb) {
+ linp = NULL;
+ } else {
+ linp = inp;
+ }
+
+ if (linp) {
+ 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) {
+ 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_tcb,
+#if defined(__FreeBSD__)
+ mflowtype,
+ mflowid,
+#endif
+ vrf_id,
+ port);
+ } else {
+ ret_buf = NULL;
+ }
+ if (linp) {
+ SCTP_ASOC_CREATE_UNLOCK(linp);
+ }
+ if (ret_buf == NULL) {
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ 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;
+ }
+ if (!TAILQ_EMPTY(&stcb->asoc.sent_queue)) {
+ /*
+ * Restart the timer if we have
+ * pending data
+ */
+ struct sctp_tmit_chunk *chk;
+
+ chk = TAILQ_FIRST(&stcb->asoc.sent_queue);
+ 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)) {
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ 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(SCTP_SO_LOCK_TESTING)
+ 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_27);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ *offset = length;
+ return (NULL);
+ }
+ }
+ /* He's alive so give him credit */
+ if ((stcb) && netp && *netp) {
+ 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_cookie_ack((struct sctp_cookie_ack_chunk *)ch,stcb, *netp);
+ }
+ break;
+ case SCTP_ECN_ECHO:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_ECN-ECHO\n");
+ /* He's alive so give him credit */
+ if ((stcb == NULL) || (chk_length != sizeof(struct sctp_ecne_chunk))) {
+ /* Its not ours */
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+ if (stcb) {
+ if (stcb->asoc.ecn_supported == 0) {
+ goto unknown_chunk;
+ }
+ 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_ecn_echo((struct sctp_ecne_chunk *)ch,
+ stcb);
+ ecne_seen = 1;
+ }
+ break;
+ case SCTP_ECN_CWR:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_ECN-CWR\n");
+ /* He's alive so give him credit */
+ if ((stcb == NULL) || (chk_length != sizeof(struct sctp_cwr_chunk))) {
+ /* Its not ours */
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+ if (stcb) {
+ if (stcb->asoc.ecn_supported == 0) {
+ goto unknown_chunk;
+ }
+ 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_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;
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ return (NULL);
+ }
+ if ((stcb) && netp && *netp) {
+ 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");
+ /* He's alive so give him credit */
+ if (stcb) {
+ if (stcb->asoc.asconf_supported == 0) {
+ goto unknown_chunk;
+ }
+ 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(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 */
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+ if ((stcb) && netp && *netp) {
+ 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:
+ SCTPDBG(SCTP_DEBUG_INPUT3, "SCTP_FWD-TSN\n");
+ if (chk_length < sizeof(struct sctp_forward_tsn_chunk)) {
+ /* Its not ours */
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+
+ /* He's alive so give him credit */
+ if (stcb) {
+ int abort_flag = 0;
+
+ if (stcb->asoc.prsctp_supported == 0) {
+ goto unknown_chunk;
+ }
+ 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_INPUT,
+ __LINE__);
+ }
+ *fwd_tsn_seen = 1;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
+ /* We are not interested anymore */
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ *offset = length;
+ return (NULL);
+ }
+ sctp_handle_forward_tsn(stcb,
+ (struct sctp_forward_tsn_chunk *)ch, &abort_flag, m, *offset);
+ if (abort_flag) {
+ *offset = length;
+ return (NULL);
+ } else {
+ 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;
+ }
+
+ }
+ 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 */
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+ 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 */
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+
+
+ if (ch && (stcb) && netp && (*netp)) {
+ if (stcb->asoc.pktdrop_supported == 0) {
+ goto unknown_chunk;
+ }
+ sctp_handle_packet_dropped((struct sctp_pktdrop_chunk *)ch,
+ stcb, *netp,
+ min(chk_length, (sizeof(chunk_buf) - 4)));
+
+ }
+
+ 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 */
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+ 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 mbuf *mm;
+ struct sctp_paramhdr *phd;
+ int len;
+
+ mm = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (mm) {
+ len = min(SCTP_SIZE32(chk_length), (uint32_t)(length - *offset));
+ phd = mtod(mm, struct sctp_paramhdr *);
+ /*
+ * We cheat and use param type since
+ * we did not bother to define a
+ * error cause struct. They are the
+ * same basic format with different
+ * names.
+ */
+ phd->param_type = htons(SCTP_CAUSE_UNRECOG_CHUNK);
+ phd->param_length = htons(len + sizeof(*phd));
+ SCTP_BUF_LEN(mm) = sizeof(*phd);
+ SCTP_BUF_NEXT(mm) = SCTP_M_COPYM(m, *offset, len, M_NOWAIT);
+ if (SCTP_BUF_NEXT(mm)) {
+ if (sctp_pad_lastmbuf(SCTP_BUF_NEXT(mm), SCTP_SIZE32(len) - len, NULL) == NULL) {
+ sctp_m_freem(mm);
+ } else {
+#ifdef SCTP_MBUF_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_MBUF_LOGGING_ENABLE) {
+ sctp_log_mbc(SCTP_BUF_NEXT(mm), SCTP_MBUF_ICOPY);
+ }
+#endif
+ sctp_queue_op_err(stcb, mm);
+ }
+ } else {
+ sctp_m_freem(mm);
+ }
+ }
+ }
+ 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) {
+ if (locked_tcb) {
+ SCTP_TCB_UNLOCK(locked_tcb);
+ }
+ *offset = length;
+ return (NULL);
+ }
+ } /* while */
+
+ if (asconf_cnt > 0 && stcb != NULL) {
+ sctp_send_asconf_ack(stcb);
+ }
+ return (stcb);
+}
+
+
+#ifdef INVARIANTS
+#ifdef __GNUC__
+__attribute__((noinline))
+#endif
+void
+sctp_validate_no_locks(struct sctp_inpcb *inp)
+{
+#ifndef __APPLE__
+ struct sctp_tcb *lstcb;
+
+ LIST_FOREACH(lstcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ if (mtx_owned(&lstcb->tcb_mtx)) {
+ panic("Own lock on stcb at return from input");
+ }
+ }
+ if (mtx_owned(&inp->inp_create_mtx)) {
+ panic("Own create lock on inp");
+ }
+ if (mtx_owned(&inp->inp_mtx)) {
+ panic("Own inp lock on inp");
+ }
+#endif
+}
+#endif
+
+/*
+ * 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,
+#if !defined(SCTP_WITH_NO_CSUM)
+ uint8_t compute_crc,
+#endif
+ uint8_t ecn_bits,
+#if defined(__FreeBSD__)
+ uint8_t mflowtype, uint32_t mflowid,
+#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;
+
+ SCTP_STAT_INCR(sctps_recvdatagrams);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xE0, 1);
+ sctp_auditing(0, inp, stcb, net);
+#endif
+#if !defined(SCTP_WITH_NO_CSUM)
+ 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 ((net != NULL) && (port != 0)) {
+ if (net->port == 0) {
+ sctp_pathmtu_adjustment(stcb, net->mtu - sizeof(struct udphdr));
+ }
+ net->port = port;
+ }
+#endif
+#if defined(__FreeBSD__)
+ if ((net != NULL) && (mflowtype != M_HASHTYPE_NONE)) {
+ net->flowtype = mflowtype;
+ }
+#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;
+ }
+ }
+#endif
+ /* 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 ((net != NULL) && (port != 0)) {
+ if (net->port == 0) {
+ sctp_pathmtu_adjustment(stcb, net->mtu - sizeof(struct udphdr));
+ }
+ net->port = port;
+ }
+#endif
+#if defined(__FreeBSD__)
+ if ((net != NULL) && (mflowtype != M_HASHTYPE_NONE)) {
+ net->flowtype = mflowtype;
+ }
+#endif
+ if (inp == NULL) {
+ SCTP_STAT_INCR(sctps_noport);
+#if defined(__FreeBSD__) && (((__FreeBSD_version < 900000) && (__FreeBSD_version >= 804000)) || (__FreeBSD_version > 900000))
+ 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__)
+ mflowtype, mflowid,
+#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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ }
+ }
+ goto out;
+ } else if (stcb == NULL) {
+ inp_decr = inp;
+ }
+#ifdef IPSEC
+ /*-
+ * I very much doubt any of the IPSEC stuff will work but I have no
+ * idea, so I will leave it in place.
+ */
+ if (inp != NULL) {
+ switch (dst->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (ipsec4_in_reject(m, &inp->ip_inp.inp)) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ goto out;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (ipsec6_in_reject(m, &inp->ip_inp.inp)) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ goto out;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+#endif
+ 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;
+ snprintf(msg, sizeof(msg), "OOTB, %s:%d at %s\n", __FILE__, __LINE__, __FUNCTION__);
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ goto out;
+ }
+
+ }
+ 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__)
+ mflowtype, mflowid,
+#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 ((net) && (port)) {
+ if (net->port == 0) {
+ sctp_pathmtu_adjustment(stcb, net->mtu - sizeof(struct udphdr));
+ }
+ 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) &&
+ (stcb->asoc.auth_supported == 1) &&
+ sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.local_auth_chunks)) {
+ /* "silently" ignore */
+ SCTP_STAT_INCR(sctps_recvauthmissing);
+ goto out;
+ }
+ if (stcb == NULL) {
+ /* out of the blue DATA chunk */
+ snprintf(msg, sizeof(msg), "OOTB, %s:%d at %s\n", __FILE__, __LINE__, __FUNCTION__);
+ 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__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ goto out;
+ }
+ if (stcb->asoc.my_vtag != ntohl(sh->v_tag)) {
+ /* v_tag mismatch! */
+ SCTP_STAT_INCR(sctps_badvtag);
+ goto out;
+ }
+ }
+
+ 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;
+ }
+
+ /*
+ * 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) &&
+ (stcb->asoc.auth_supported == 1) &&
+ 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->asoc)) {
+ 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.
+ */
+ 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_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.
+ */
+ snprintf(msg, sizeof(msg), "OOTB, %s:%d at %s\n", __FILE__, __LINE__, __FUNCTION__);
+ 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__)
+ mflowtype, mflowid,
+#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,
+ src, dst, sh,
+ inp, stcb, net, &high_tsn,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ 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 (cnt_ctrl_ready ||
+ ((un_sent) &&
+ (stcb->asoc.peers_rwnd > 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 (inp_decr != NULL) {
+ /* reduce ref-count */
+ SCTP_INP_WLOCK(inp_decr);
+ SCTP_INP_DECR_REF(inp_decr);
+ SCTP_INP_WUNLOCK(inp_decr);
+ }
+#ifdef INVARIANTS
+ if (inp != NULL) {
+ sctp_validate_no_locks(inp);
+ }
+#endif
+ return;
+}
+
+#ifdef INET
+#if !defined(__Userspace__)
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+void
+sctp_input_with_port(struct mbuf *i_pak, int off, uint16_t port)
+#elif defined(__Panda__)
+void
+sctp_input(pakhandle_type i_pak)
+#else
+void
+#if __STDC__
+sctp_input(struct mbuf *i_pak,...)
+#else
+sctp_input(i_pak, va_alist)
+ struct mbuf *i_pak;
+#endif
+#endif
+{
+ 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;
+#if !defined(SCTP_WITH_NO_CSUM)
+ uint8_t compute_crc;
+#endif
+#if defined(__FreeBSD__)
+ uint32_t mflowid;
+ uint8_t mflowtype;
+#endif
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__))
+ uint16_t port = 0;
+#endif
+
+#if defined(__Panda__)
+ /* This is Evil, but its the only way to make panda work right. */
+ iphlen = sizeof(struct ip);
+#else
+ iphlen = off;
+#endif
+ if (SCTP_GET_PKT_VRFID(i_pak, vrf_id)) {
+ SCTP_RELEASE_PKT(i_pak);
+ return;
+ }
+ m = SCTP_HEADER_TO_CHAIN(i_pak);
+#ifdef __Panda__
+ SCTP_DETACH_HEADER_FROM_CHAIN(i_pak);
+ (void)SCTP_RELEASE_HEADER(i_pak);
+#endif
+#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__)
+#if __FreeBSD_version > 1000049
+ 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);
+#elif __FreeBSD_version >= 800000
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp_input(): Packet of length %d received on %s with csum_flags 0x%x.\n",
+ m->m_pkthdr.len,
+ if_name(m->m_pkthdr.rcvif),
+ m->m_pkthdr.csum_flags);
+#else
+ 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
+#endif
+#if defined(__APPLE__)
+ 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(__Windows__)
+ 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__)
+ mflowid = m->m_pkthdr.flowid;
+ mflowtype = M_HASHTYPE_GET(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(__Windows__)
+ NTOHS(ip->ip_len);
+#endif
+#if defined(__Userspace_os_Linux) || defined(__Userspace_os_Windows)
+ ip->ip_len = ntohs(ip->ip_len);
+#endif
+#if defined(__FreeBSD__)
+#if __FreeBSD_version >= 1000000
+ length = ntohs(ip->ip_len);
+#else
+ length = ip->ip_len + iphlen;
+#endif
+#elif defined(__APPLE__)
+ length = ip->ip_len + iphlen;
+#elif defined(__Userspace__)
+#if defined(__Userspace_os_Linux) || defined(__Userspace_os_Windows)
+ length = ip->ip_len;
+#else
+ length = ip->ip_len + iphlen;
+#endif
+#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(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_recvnocrc);
+#else
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ 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_recvnocrc);
+ compute_crc = 0;
+ } else {
+#endif
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ compute_crc = 1;
+ }
+#endif
+ sctp_common_input_processing(&m, iphlen, offset, length,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+#if !defined(SCTP_WITH_NO_CSUM)
+ compute_crc,
+#endif
+ ecn_bits,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ out:
+ if (m) {
+ sctp_m_freem(m);
+ }
+ return;
+}
+
+#if defined(__FreeBSD__) && defined(SCTP_MCORE_INPUT) && defined(SMP)
+extern int *sctp_cpuarry;
+#endif
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 1100020
+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(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);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 1100020
+ return (IPPROTO_DONE);
+#else
+ return;
+#endif
+ }
+ }
+ 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);
+ }
+ cpu_to_use = sctp_cpuarry[flowid % mp_ncpus];
+ sctp_queue_to_mcore(m, off, cpu_to_use);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 1100020
+ return (IPPROTO_DONE);
+#else
+ return;
+#endif
+ }
+#endif
+ sctp_input_with_port(m, off, 0);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 1100020
+ 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..5d45fef20c
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_input.h
@@ -0,0 +1,66 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_input.h 273168 2014-10-16 15:36:04Z 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 *,
+#if !defined(SCTP_WITH_NO_CSUM)
+ uint8_t,
+#endif
+ uint8_t,
+#if defined(__FreeBSD__)
+ uint8_t, uint32_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..83a565c371
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_lock_userspace.h
@@ -0,0 +1,251 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#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_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_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)
+
+
+/* not sure if __Userspace__ needs these (but copied nonetheless...) */
+#if defined(SCTP_SO_LOCK_TESTING)
+#define SCTP_INP_SO(sctpinp) (sctpinp)->ip_inp.inp.inp_socket
+#define SCTP_SOCKET_LOCK(so, refcnt)
+#define SCTP_SOCKET_UNLOCK(so, refcnt)
+#endif
+
+
+/* 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..48883811a1
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_os.h
@@ -0,0 +1,94 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_os.h 235828 2012-05-23 11:26:28Z 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__)
+#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__)
+#include <netinet/sctp_os_macosx.h>
+#endif
+
+#if defined(__Panda__)
+#include <ip/sctp/sctp_os_iox.h>
+#endif
+
+#if defined(__Windows__)
+#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..bf4a3b5193
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_os_userspace.h
@@ -0,0 +1,1171 @@
+/*-
+ * 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(__Userspace_os_Windows)
+#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 _MSC_VER >= 1600
+#include <stdint.h>
+#else
+#define uint64_t unsigned __int64
+#define uint32_t unsigned __int32
+#define int32_t __int32
+#define uint16_t unsigned __int16
+#define int16_t __int16
+#define uint8_t unsigned __int8
+#define int8_t __int8
+#endif
+#ifndef _SIZE_T_DEFINED
+#define size_t __int32
+#endif
+#define u_long unsigned __int64
+#define u_int unsigned __int32
+#define u_int32_t unsigned __int32
+#define u_int16_t unsigned __int16
+#define u_int8_t unsigned __int8
+#define u_char unsigned char
+#define n_short unsigned __int16
+#define u_short unsigned __int16
+#define n_time unsigned __int32
+#define sa_family_t unsigned __int8
+#define ssize_t __int64
+#define __func__ __FUNCTION__
+
+#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 _MSC_VER < 1900
+#define snprintf(data, size, format, ...) _snprintf_s(data, size, _TRUNCATE, format, __VA_ARGS__)
+#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(__Userspace_os_DragonFly) || defined(__Userspace_os_FreeBSD) || defined(__Userspace_os_Linux) || defined(__Userspace_os_NetBSD) || defined(__Userspace_os_OpenBSD) || defined(__Userspace_os_NaCl)
+#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(__Userspace_os_Windows) || defined(__Userspace_os_NaCl)
+
+#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 {
+ unsigned long 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;
+};
+
+struct ifreq {
+ char ifr_name[16];
+ union {
+ struct sockaddr ifru_addr;
+ struct sockaddr ifru_dstaddr;
+ struct sockaddr ifru_broadaddr;
+ short ifru_flags;
+ int ifru_metric;
+ int ifru_mtu;
+ int ifru_phys;
+ int ifru_media;
+ int ifru_intval;
+ char* ifru_data;
+ struct ifdevmtu ifru_devmtu;
+ struct ifkpi ifru_kpi;
+ uint32_t ifru_wake_flags;
+ } ifr_ifru;
+#define ifr_addr ifr_ifru.ifru_addr
+#define ifr_dstaddr ifr_ifru.ifru_dstaddr
+#define ifr_broadaddr ifr_ifru.ifru_broadaddr
+#define ifr_flags ifr_ifru.ifru_flags[0]
+#define ifr_prevflags ifr_ifru.ifru_flags[1]
+#define ifr_metric ifr_ifru.ifru_metric
+#define ifr_mtu ifr_ifru.ifru_mtu
+#define ifr_phys ifr_ifru.ifru_phys
+#define ifr_media ifr_ifru.ifru_media
+#define ifr_data ifr_ifru.ifru_data
+#define ifr_devmtu ifr_ifru.ifru_devmtu
+#define ifr_intval ifr_ifru.ifru_intval
+#define ifr_kpi ifr_ifru.ifru_kpi
+#define ifr_wake_flags ifr_ifru.ifru_wake_flags
+};
+
+#endif
+
+#if defined(__Userspace_os_Windows)
+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(__Userspace_os_FreeBSD)
+struct mtx {int dummy;};
+#if !defined(__Userspace_os_NetBSD)
+struct selinfo {int dummy;};
+#endif
+struct sx {int dummy;};
+#endif
+
+#include <stdio.h>
+#include <string.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__) && __FreeBSD_version > 602000
+#include <sys/rwlock.h>
+#endif
+/* #include <sys/kthread.h> */
+#if defined(__FreeBSD__) && __FreeBSD_version > 602000
+#include <sys/priv.h>
+#endif
+/* #include <sys/random.h> */
+/* #include <sys/limits.h> */
+/* #include <machine/cpu.h> */
+
+#if defined(__Userspace_os_Darwin)
+/* was a 0 byte file. needed for structs if_data(64) and net_event_data */
+#include <net/if_var.h>
+#endif
+#if defined(__Userspace_os_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(__Userspace_os_Windows) && !defined(__Userspace_os_NaCl)
+#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(__Userspace_os_Windows)
+#if !defined(ANDROID) && (defined(INET) || defined(INET6))
+#include <ifaddrs.h>
+#endif
+
+/* for ioctl */
+#include <sys/ioctl.h>
+
+/* for close, etc. */
+#include <unistd.h>
+#endif
+
+/* lots of errno's used and needed in userspace */
+
+/* for offsetof */
+#include <stddef.h>
+
+#if defined(SCTP_PROCESS_LEVEL_LOCKS) && !defined(__Userspace_os_Windows)
+/* 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(__Userspace_os_FreeBSD)
+#include <sys/domain.h>
+#endif
+#ifdef IPSEC
+#include <netipsec/ipsec6.h>
+#endif
+#if !defined(__Userspace_os_Windows)
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#endif
+#if defined(__Userspace_os_Darwin) || defined(__Userspace_os_FreeBSD) || defined(__Userspace_os_Linux) || defined(__Userspace_os_NetBSD) || defined(__Userspace_os_OpenBSD) || defined(__Userspace_os_Windows)
+#include "user_ip6_var.h"
+#else
+#include <netinet6/ip6_var.h>
+#endif
+#if defined(__Userspace_os_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 __FreeBSD_version >= 700000
+#include <netinet/ip_options.h>
+#endif
+
+#define SCTP_PRINTF(...) \
+ if (SCTP_BASE_VAR(debug_printf)) { \
+ SCTP_BASE_VAR(debug_printf)(__VA_ARGS__); \
+ }
+
+#if defined(__FreeBSD__)
+#ifndef in6pcb
+#define in6pcb inpcb
+#endif
+#endif
+/* 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(__Userspace_os_Darwin)
+#define USER_ADDR_NULL (NULL) /* FIX ME: temp */
+#endif
+
+#if defined(SCTP_DEBUG)
+#include <netinet/sctp_constants.h>
+#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(__Userspace_os_Windows)
+#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
+
+/* global struct ifaddrs used in sctp_init_ifns_for_vrf getifaddrs call
+ * but references to fields are needed to persist as the vrf is queried.
+ * getifaddrs allocates memory that needs to be freed with a freeifaddrs
+ * call; this global is used to call freeifaddrs upon in sctp_pcb_finish
+ */
+extern struct ifaddrs *g_interfaces;
+
+
+/*
+ * __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
+
+#define sctp_get_tick_count() (ticks)
+
+/* The packed define for 64 bit platforms */
+#if !defined(__Userspace_os_Windows)
+#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); \
+ }
+
+/* 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) 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);
+ rt = NULL;
+}
+#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)
+
+/* (de-)register interface event notifications */
+#define SCTP_REGISTER_INTERFACE(ifhandle, af)
+#define SCTP_DEREGISTER_INTERFACE(ifhandle, af)
+
+
+/*************************/
+/* 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)
+/* UDP __Userspace__ - dummy definition */
+#define SCTP_ENABLE_UDP_CSUM(m) m=m
+/* BSD definition */
+/* #define SCTP_ENABLE_UDP_CSUM(m) do { \ */
+/* m->m_pkthdr.csum_flags = CSUM_UDP; \ */
+/* m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum); \ */
+/* } while (0) */
+
+#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 /* As done for __Windows__ */
+#define IPv6_HOP_LIMIT 128
+
+/* is the endpoint v6only? */
+#define SCTP_IPV6_V6ONLY(inp) (((struct inpcb *)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
+
+/* Future zero copy wakeup/send function */
+#define SCTP_ZERO_COPY_EVENT(inp, so)
+/* This is re-pulse ourselves for sendbuf */
+#define SCTP_ZERO_COPY_SENDQ_EVENT(inp, so)
+
+#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(__Userspace_os_Windows)
+#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;
+#endif
+ uint8_t sconn_family;
+ uint16_t sconn_port;
+ void *sconn_addr;
+};
+
+/*
+ * 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 in6pcb *)(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(__Userspace_os_DragonFly) || defined(__Userspace_os_FreeBSD) || defined(__Userspace_os_OpenBSD) || defined(__Userspace_os_NaCl)
+/* stolen from /usr/include/sys/socket.h */
+#define CMSG_ALIGN(n) _ALIGN(n)
+#elif defined(__Userspace_os_NetBSD)
+#define CMSG_ALIGN(n) (((n) + __ALIGNBYTES) & ~__ALIGNBYTES)
+#elif defined(__Userspace_os_Darwin)
+#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__ , __FUNCTION__); \
+ } 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(__Userspace_os_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(__Userspace_os_DragonFly)
+#define TAILQ_FOREACH_SAFE TAILQ_FOREACH_MUTABLE
+#define LIST_FOREACH_SAFE LIST_FOREACH_MUTABLE
+#endif
+
+#if defined(__Userspace_os_NaCl)
+#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
+
+#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..49447fa9da
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_output.c
@@ -0,0 +1,14561 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_output.c 280371 2015-03-23 15:12:02Z tuexen $");
+#endif
+
+#include <netinet/sctp_os.h>
+#ifdef __FreeBSD__
+#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(__Userspace_os_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(__Userspace_os_Windows)
+#include <netinet/udp.h>
+#endif
+#endif
+#if defined(__APPLE__)
+#include <netinet/in.h>
+#endif
+#if defined(__FreeBSD__)
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+#include <netinet/udp_var.h>
+#endif
+#include <machine/in_cksum.h>
+#endif
+#if defined(__Userspace__) && defined(INET6)
+#include <netinet6/sctp6_var.h>
+#endif
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 3
+#endif
+
+#if defined(__APPLE__)
+#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];
+};
+
+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;
+
+#if !defined(__Panda__)
+ /* Must update the flags, bummer, which
+ * means any IFA locks must now be applied HERE <->
+ */
+ if (do_update) {
+ sctp_gather_internal_ifa_flags(ifa);
+ }
+#endif
+ 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 *parmh;
+ 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 */
+ parmh = (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);
+ parmh = 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 *)parmh;
+ parmh->param_type = htons(SCTP_IPV4_ADDRESS);
+ parmh->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 *)parmh;
+ parmh->param_type = htons(SCTP_IPV6_ADDRESS);
+ parmh->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__)
+#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__)
+#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",
+ __FUNCTION__);
+ 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",
+ __FUNCTION__);
+ 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__)
+#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__)
+#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__)
+#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__)
+ 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__)
+#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__)
+ 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__)
+#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 prefered -- 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__)
+#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 resticted\n");
+ sifa = NULL;
+ continue;
+ }
+ } else {
+ SCTP_PRINTF("Stcb is null - no print\n");
+ }
+ 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__)
+#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 ((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__)
+#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 (ro->ro_rt == NULL) {
+ /*
+ * Need a route to cache.
+ */
+ SCTP_RTALLOC(ro, vrf_id);
+ }
+ if (ro->ro_rt == NULL) {
+ return (NULL);
+ }
+#if defined(__Userspace_os_Windows)
+ /* 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ WSACMSGHDR cmh;
+#else
+ struct cmsghdr cmh;
+#endif
+ int tlen, at, found;
+ struct sctp_sndinfo sndinfo;
+ struct sctp_prinfo prinfo;
+ struct sctp_authinfo authinfo;
+
+ tlen = SCTP_BUF_LEN(control);
+ at = 0;
+ found = 0;
+ /*
+ * Independent of how many mbufs, find the c_type inside the control
+ * structure and copy out the data.
+ */
+ while (at < tlen) {
+ if ((tlen - at) < (int)CMSG_ALIGN(sizeof(cmh))) {
+ /* There is not enough room for one more. */
+ return (found);
+ }
+ m_copydata(control, at, sizeof(cmh), (caddr_t)&cmh);
+ if (cmh.cmsg_len < CMSG_ALIGN(sizeof(cmh))) {
+ /* We dont't have a complete CMSG header. */
+ return (found);
+ }
+ if (((int)cmh.cmsg_len + at) > tlen) {
+ /* We don't have the complete CMSG. */
+ return (found);
+ }
+ 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 ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < cpsize) {
+ return (found);
+ }
+ /* It is exactly what we want. Copy it out. */
+ m_copydata(control, at + CMSG_ALIGN(sizeof(cmh)), 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 ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < sizeof(struct sctp_sndinfo)) {
+ return (found);
+ }
+ m_copydata(control, at + CMSG_ALIGN(sizeof(cmh)), 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 ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < sizeof(struct sctp_prinfo)) {
+ return (found);
+ }
+ m_copydata(control, at + CMSG_ALIGN(sizeof(cmh)), 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 ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < sizeof(struct sctp_authinfo)) {
+ return (found);
+ }
+ m_copydata(control, at + CMSG_ALIGN(sizeof(cmh)), sizeof(struct sctp_authinfo), (caddr_t)&authinfo);
+ sndrcvinfo->sinfo_keynumber_valid = 1;
+ sndrcvinfo->sinfo_keynumber = authinfo.auth_keynumber;
+ break;
+ default:
+ return (found);
+ }
+ found = 1;
+ }
+ }
+ at += CMSG_ALIGN(cmh.cmsg_len);
+ }
+ return (found);
+}
+
+static int
+sctp_process_cmsgs_for_init(struct sctp_tcb *stcb, struct mbuf *control, int *error)
+{
+#if defined(__Userspace_os_Windows)
+ WSACMSGHDR cmh;
+#else
+ struct cmsghdr cmh;
+#endif
+ int tlen, at;
+ struct sctp_initmsg initmsg;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+
+ tlen = SCTP_BUF_LEN(control);
+ at = 0;
+ while (at < tlen) {
+ if ((tlen - at) < (int)CMSG_ALIGN(sizeof(cmh))) {
+ /* There is not enough room for one more. */
+ *error = EINVAL;
+ return (1);
+ }
+ m_copydata(control, at, 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 (((int)cmh.cmsg_len + at) > tlen) {
+ /* We don't have the complete CMSG. */
+ *error = EINVAL;
+ return (1);
+ }
+ if (cmh.cmsg_level == IPPROTO_SCTP) {
+ switch (cmh.cmsg_type) {
+ case SCTP_INIT:
+ if ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < sizeof(struct sctp_initmsg)) {
+ *error = EINVAL;
+ return (1);
+ }
+ m_copydata(control, at + CMSG_ALIGN(sizeof(cmh)), 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_sequence_send = 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].stream_no = i;
+ stcb->asoc.strmout[i].last_msg_incomplete = 0;
+ stcb->asoc.ss_functions.sctp_ss_init_stream(&stcb->asoc.strmout[i], NULL);
+ }
+ }
+ break;
+#ifdef INET
+ case SCTP_DSTADDRV4:
+ if ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < 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, at + CMSG_ALIGN(sizeof(cmh)), 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,
+ SCTP_DONOT_SETSCOPE, SCTP_ADDR_IS_CONFIRMED)) {
+ *error = ENOBUFS;
+ return (1);
+ }
+ break;
+#endif
+#ifdef INET6
+ case SCTP_DSTADDRV6:
+ if ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < 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, at + CMSG_ALIGN(sizeof(cmh)), 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,
+ SCTP_DONOT_SETSCOPE, SCTP_ADDR_IS_CONFIRMED)) {
+ *error = ENOBUFS;
+ return (1);
+ }
+ } else
+#endif
+ if (sctp_add_remote_addr(stcb, (struct sockaddr *)&sin6, NULL,
+ SCTP_DONOT_SETSCOPE, SCTP_ADDR_IS_CONFIRMED)) {
+ *error = ENOBUFS;
+ return (1);
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ at += CMSG_ALIGN(cmh.cmsg_len);
+ }
+ return (0);
+}
+
+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(__Userspace_os_Windows)
+ WSACMSGHDR cmh;
+#else
+ struct cmsghdr cmh;
+#endif
+ int tlen, at;
+ struct sctp_tcb *stcb;
+ struct sockaddr *addr;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+
+ tlen = SCTP_BUF_LEN(control);
+ at = 0;
+ while (at < tlen) {
+ if ((tlen - at) < (int)CMSG_ALIGN(sizeof(cmh))) {
+ /* There is not enough room for one more. */
+ *error = EINVAL;
+ return (NULL);
+ }
+ m_copydata(control, at, 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 (((int)cmh.cmsg_len + at) > tlen) {
+ /* We don't have the complete CMSG. */
+ *error = EINVAL;
+ return (NULL);
+ }
+ if (cmh.cmsg_level == IPPROTO_SCTP) {
+ switch (cmh.cmsg_type) {
+#ifdef INET
+ case SCTP_DSTADDRV4:
+ if ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < 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, at + CMSG_ALIGN(sizeof(cmh)), sizeof(struct in_addr), (caddr_t)&sin.sin_addr);
+ addr = (struct sockaddr *)&sin;
+ break;
+#endif
+#ifdef INET6
+ case SCTP_DSTADDRV6:
+ if ((size_t)(cmh.cmsg_len - CMSG_ALIGN(sizeof(cmh))) < 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, at + CMSG_ALIGN(sizeof(cmh)), 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);
+ }
+ }
+ }
+ at += CMSG_ALIGN(cmh.cmsg_len);
+ }
+ return (NULL);
+}
+
+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;
+ uint8_t *foo;
+ int sig_offset;
+ 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_SECRET_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_LEN(sig) = 0;
+ SCTP_BUF_NEXT(m_at) = sig;
+ sig_offset = 0;
+ foo = (uint8_t *) (mtod(sig, caddr_t) + sig_offset);
+ memset(foo, 0, SCTP_SIGNATURE_SIZE);
+ *signature = foo;
+ SCTP_BUF_LEN(sig) += SCTP_SIGNATURE_SIZE;
+ cookie_sz += SCTP_SIGNATURE_SIZE;
+ ph->param_length = htons(cookie_sz);
+ 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__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ int so_locked SCTP_UNUSED
+#else
+ int so_locked
+#endif
+ )
+/* 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 */
+#ifdef __Panda__
+ pakhandle_type o_pak;
+#endif
+ 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)
+#if !defined(__Panda__)
+ struct mbuf *o_pak;
+#endif
+ sctp_route_t *ro = NULL;
+ struct udphdr *udp = NULL;
+#endif
+ uint8_t tos_value;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so = NULL;
+#endif
+
+#if defined(__APPLE__)
+ 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 = sizeof(struct ip) + sizeof(struct sctphdr);
+ 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__)
+ 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.
+ */
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Panda__) || defined(__Windows__) || defined(__Userspace__)
+ tos_value = inp->ip_inp.inp.inp_ip_tos;
+#else
+ tos_value = inp->inp_ip_tos;
+#endif
+ }
+ tos_value &= 0xfc;
+ if (ecn_ok) {
+ tos_value |= sctp_get_ect(stcb);
+ }
+ if ((nofragment_flag) && (port == 0)) {
+#if defined(__FreeBSD__)
+#if __FreeBSD_version >= 1000000
+ ip->ip_off = htons(IP_DF);
+#else
+ ip->ip_off = IP_DF;
+#endif
+#elif defined(WITH_CONVERT_IP_OFF) || defined(__APPLE__) || defined(__Userspace__)
+ ip->ip_off = IP_DF;
+#else
+ ip->ip_off = htons(IP_DF);
+#endif
+ } else {
+#if defined(__FreeBSD__) && __FreeBSD_version >= 1000000
+ ip->ip_off = htons(0);
+#else
+ ip->ip_off = 0;
+#endif
+ }
+#if defined(__FreeBSD__)
+ /* FreeBSD has a function for ip_id's */
+ ip->ip_id = ip_newid();
+#elif defined(RANDOM_IP_ID)
+ /* Apple has RANDOM_IP_ID switch */
+ ip->ip_id = htons(ip_randomid());
+#elif defined(__Userspace__)
+ ip->ip_id = htons(SCTP_IP_ID(inp)++);
+#else
+ ip->ip_id = SCTP_IP_ID(inp)++;
+#endif
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Panda__) || defined(__Windows__) || defined(__Userspace__)
+ ip->ip_ttl = inp->ip_inp.inp.inp_ip_ttl;
+#else
+ ip->ip_ttl = inp->inp_ip_ttl;
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version >= 1000000
+ 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 (ro->ro_rt) {
+ RTFREE(ro->ro_rt);
+ ro->ro_rt = NULL;
+ }
+ }
+ 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);
+ }
+ }
+ 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(packet_length - sizeof(struct ip));
+#if !defined(__Windows__) && !defined(__Userspace__)
+#if defined(__FreeBSD__) && ((__FreeBSD_version > 803000 && __FreeBSD_version < 900000) || __FreeBSD_version > 900000)
+ 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 (ro->ro_rt == NULL) {
+ /*
+ * 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)));
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "RTP route is %p through\n",
+ (void *)ro->ro_rt);
+
+ if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) {
+ /* failed to prepend data, give up */
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ sctp_m_freem(m);
+ return (ENOMEM);
+ }
+ SCTP_ATTACH_CHAIN(o_pak, m, packet_length);
+ if (port) {
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+ sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip) + sizeof(struct udphdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#endif
+#if defined(__FreeBSD__) && ((__FreeBSD_version > 803000 && __FreeBSD_version < 900000) || __FreeBSD_version > 900000)
+ if (V_udp_cksum) {
+ SCTP_ENABLE_UDP_CSUM(o_pak);
+ }
+#else
+ SCTP_ENABLE_UDP_CSUM(o_pak);
+#endif
+ } else {
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ 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_sendnocrc);
+ }
+#endif
+#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(SCTP_SO_LOCK_TESTING)
+ if ((SCTP_BASE_SYSCTL(sctp_output_unlocked)) && (so_locked)) {
+ so = SCTP_INP_SO(inp);
+ SCTP_SOCKET_UNLOCK(so, 0);
+ }
+#endif
+ SCTP_IP_OUTPUT(ret, o_pak, ro, stcb, vrf_id);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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
+ 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__) && __FreeBSD_version > 901000
+ RO_RTFREE(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 ((ro->ro_rt != NULL) &&
+ (net->ro._s_addr)) {
+ uint32_t mtu;
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, ro->ro_rt);
+ if (net->port) {
+ mtu -= sizeof(struct udphdr);
+ }
+ if (mtu && (stcb->asoc.smallest_mtu > mtu)) {
+ sctp_mtu_size_reset(inp, &stcb->asoc, mtu);
+ net->mtu = mtu;
+ }
+ } else if (ro->ro_rt == NULL) {
+ /* 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(__Panda__) || 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(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION))
+ flowlabel = ntohl(inp->ip_inp.inp.inp_flow);
+#else
+ flowlabel = ntohl(((struct in6pcb *)inp)->in6p_flowinfo);
+#endif
+ }
+ flowlabel &= 0x000fffff;
+ len = sizeof(struct ip6_hdr) + sizeof(struct sctphdr);
+ 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__)
+ 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__)
+#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);
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Panda__) || defined(__Windows__) || defined(__Userspace__)
+#if defined(__APPLE__) && (!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 in6pcb *)inp)->in6p_flowinfo) >> 20) & 0xff;
+#endif
+#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 = (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.
+ */
+ bzero(&lsa6_tmp, 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 (ro->ro_rt) {
+ RTFREE(ro->ro_rt);
+ ro->ro_rt = NULL;
+ }
+ }
+ 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__)
+#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);
+ 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__)
+#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);
+ 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);
+ }
+#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 (ro->ro_rt == NULL) {
+ /*
+ * 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.
+ */
+ bzero(&lsa6_storage, 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(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(__Panda__) || 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) {
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+ sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip6_hdr) + sizeof(struct udphdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#endif
+#if defined(__Windows__)
+ udp->uh_sum = 0;
+#elif !defined(__Userspace__)
+ 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
+ } else {
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+#if __FreeBSD_version < 900000
+ sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip6_hdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#else
+#if __FreeBSD_version > 901000
+ m->m_pkthdr.csum_flags = CSUM_SCTP_IPV6;
+#else
+ m->m_pkthdr.csum_flags = CSUM_SCTP;
+#endif
+ m->m_pkthdr.csum_data = offsetof(struct sctphdr, checksum);
+ SCTP_STAT_INCR(sctps_sendhwcrc);
+#endif
+#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_sendnocrc);
+ }
+#endif
+#endif
+ }
+ /* send it out. table id is taken from stcb */
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(__Panda__) || defined(__Userspace__))
+ 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(SCTP_SO_LOCK_TESTING)
+ 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);
+ 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__) && __FreeBSD_version > 901000
+ RO_RTFREE(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 (ro->ro_rt == NULL) {
+ /* 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 ((ro->ro_rt != NULL) &&
+ (net->ro._s_addr)) {
+ uint32_t mtu;
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, ro->ro_rt);
+ if (mtu &&
+ (stcb->asoc.smallest_mtu > mtu)) {
+ sctp_mtu_size_reset(inp, &stcb->asoc, mtu);
+ net->mtu = mtu;
+ if (net->port) {
+ net->mtu -= sizeof(struct udphdr);
+ }
+ }
+ }
+#if !defined(__Panda__) && !defined(__Userspace__)
+ else if (ifp) {
+#if defined(__Windows__)
+#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 defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+ sctphdr->checksum = sctp_calculate_cksum(m, 0);
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#endif
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ 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 ret;
+ uint16_t num_ext, chunk_len, padding_len, parameter_len;
+
+#if defined(__APPLE__)
+ 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.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.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");
+ ret = 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__)
+ 0, 0,
+#endif
+ so_locked);
+ SCTPDBG(SCTP_DEBUG_OUTPUT4, "lowlevel_output - %d\n", ret);
+ 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)
+{
+ /*
+ * 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, *op_err;
+ char tempbuf[SCTP_PARAM_BUFFER_SIZE];
+ int at, limit, pad_needed;
+ uint16_t ptype, plen, padded_size;
+ int err_at;
+
+ *abort_processing = 0;
+ mat = in_initpkt;
+ err_at = 0;
+ limit = ntohs(cp->chunk_length) - sizeof(struct sctp_init_chunk);
+ at = param_offset;
+ op_err = NULL;
+ 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_STATE_COOKIE:
+ case SCTP_UNRECOG_PARAM:
+ case SCTP_ERROR_CAUSE_IND:
+ /* ok skip fwd */
+ 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:
+ {
+ /* We can NOT handle HOST NAME addresses!! */
+ int l_len;
+
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Can't handle hostname addresses.. abort processing\n");
+ *abort_processing = 1;
+ if (op_err == NULL) {
+ /* Ok need to try to get a mbuf */
+#ifdef INET6
+ l_len = sizeof(struct ip6_hdr) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+#else
+ l_len = sizeof(struct ip) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+#endif
+ l_len += plen;
+ 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;
+ /*
+ * pre-reserve space for ip and sctp
+ * header and chunk hdr
+ */
+#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));
+ }
+ }
+ if (op_err) {
+ /* If we have space */
+ struct sctp_paramhdr s;
+
+ if (err_at % 4) {
+ uint32_t cpthis = 0;
+
+ pad_needed = 4 - (err_at % 4);
+ m_copyback(op_err, err_at, pad_needed, (caddr_t)&cpthis);
+ err_at += pad_needed;
+ }
+ s.param_type = htons(SCTP_CAUSE_UNRESOLVABLE_ADDR);
+ s.param_length = htons(sizeof(s) + plen);
+ m_copyback(op_err, err_at, sizeof(s), (caddr_t)&s);
+ err_at += sizeof(s);
+ phdr = sctp_get_next_param(mat, at, (struct sctp_paramhdr *)tempbuf, min(sizeof(tempbuf),plen));
+ if (phdr == NULL) {
+ sctp_m_freem(op_err);
+ /*
+ * we are out of memory but we still
+ * need to have a look at what to do
+ * (the system is in trouble
+ * though).
+ */
+ return (NULL);
+ }
+ m_copyback(op_err, err_at, plen, (caddr_t)phdr);
+ }
+ return (op_err);
+ break;
+ }
+ 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 = sizeof(struct ip6_hdr) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+#else
+ l_len = sizeof(struct ip) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+#endif
+ l_len += plen;
+ 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));
+ }
+ }
+ if (op_err) {
+ /* If we have space */
+ struct sctp_paramhdr s;
+
+ if (err_at % 4) {
+ uint32_t cpthis = 0;
+
+ pad_needed = 4 - (err_at % 4);
+ m_copyback(op_err, err_at, pad_needed, (caddr_t)&cpthis);
+ err_at += pad_needed;
+ }
+ s.param_type = htons(SCTP_UNRECOG_PARAM);
+ s.param_length = htons(sizeof(s) + plen);
+ m_copyback(op_err, err_at, sizeof(s), (caddr_t)&s);
+ err_at += sizeof(s);
+ if (plen > sizeof(tempbuf)) {
+ plen = sizeof(tempbuf);
+ }
+ phdr = sctp_get_next_param(mat, at, (struct sctp_paramhdr *)tempbuf, min(sizeof(tempbuf),plen));
+ if (phdr == NULL) {
+ sctp_m_freem(op_err);
+ /*
+ * we are out of memory but
+ * we still need to have a
+ * look at what to do (the
+ * system is in trouble
+ * though).
+ */
+ op_err = NULL;
+ goto more_processing;
+ }
+ m_copyback(op_err, err_at, plen, (caddr_t)phdr);
+ err_at += plen;
+ }
+ }
+ 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;
+ if ((op_err == NULL) && phdr) {
+ int l_len;
+#ifdef INET6
+ l_len = sizeof(struct ip6_hdr) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+#else
+ l_len = sizeof(struct ip) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr);
+#endif
+ 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));
+ }
+ }
+ if ((op_err) && phdr) {
+ struct sctp_paramhdr s;
+
+ if (err_at % 4) {
+ uint32_t cpthis = 0;
+
+ pad_needed = 4 - (err_at % 4);
+ m_copyback(op_err, err_at, pad_needed, (caddr_t)&cpthis);
+ err_at += pad_needed;
+ }
+ s.param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION);
+ s.param_length = htons(sizeof(s) + sizeof(struct sctp_paramhdr));
+ m_copyback(op_err, err_at, sizeof(s), (caddr_t)&s);
+ err_at += sizeof(s);
+ /* Only copy back the p-hdr that caused the issue */
+ m_copyback(op_err, err_at, sizeof(struct sctp_paramhdr), (caddr_t)phdr);
+ }
+ 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;
+#ifdef INET
+ struct sockaddr_in sin4, *sa4;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6, *sa6;
+#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 ? */
+ 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 (fnd == 0) {
+ /* New address added! no need to look futher. */
+ 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;
+
+ phdr = sctp_get_next_param(in_initpkt, offset,
+ (struct sctp_paramhdr *)&p4_buf, sizeof(p4_buf));
+ if (plen != sizeof(struct sctp_ipv4addr_param) ||
+ phdr == NULL) {
+ return (1);
+ }
+ 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;
+
+ phdr = sctp_get_next_param(in_initpkt, offset,
+ (struct sctp_paramhdr *)&p6_buf, sizeof(p6_buf));
+ if (plen != sizeof(struct sctp_ipv6addr_param) ||
+ phdr == NULL) {
+ return (1);
+ }
+ 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 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__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port, int hold_inp_lock)
+{
+ 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;
+#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;
+ 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(asoc) != SCTP_STATE_COOKIE_WAIT) &&
+ (sctp_are_there_new_addresses(asoc, init_pkt, offset, src))) {
+ /* new addresses, out of here in non-cookie-wait states */
+ /*
+ * Send a ABORT, we don't add the new address error clause
+ * though we even set the T bit and copy in the 0 tag.. 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__)
+ mflowtype, mflowid,
+#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);
+ if (abort_flag) {
+ do_a_abort:
+ if (op_err == NULL) {
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ snprintf(msg, sizeof(msg), "%s:%d at %s\n", __FILE__, __LINE__, __FUNCTION__);
+ 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__)
+ mflowtype, mflowid,
+#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. */
+ if (op_err)
+ 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(&stc.time_entered);
+
+ /* 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 extentions, 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
+ }
+#ifdef SCTP_DONT_DO_PRIVADDR_SCOPE
+ stc.ipv4_scope = 1;
+#else
+ stc.ipv4_scope = 0;
+#endif
+ 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;
+#ifndef SCTP_DONT_DO_PRIVADDR_SCOPE
+ if (IN4_ISPRIVATE_ADDRESS(&src4->sin_addr)) {
+ stc.ipv4_scope = 1;
+ }
+#else
+ stc.ipv4_scope = 1;
+#endif /* SCTP_DONT_DO_PRIVADDR_SCOPE */
+ /* 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__) && (((__FreeBSD_version < 900000) && (__FreeBSD_version >= 804000)) || (__FreeBSD_version > 900000))
+ stc.scope_id = 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)) {
+ /*
+ * If the new destination 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__)
+ /* 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)) {
+ /*
+ * If the new destination 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)
+ 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)
+ 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 = 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(asoc) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_INUSE) ||
+ (SCTP_GET_STATE(asoc) == 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 (hold_inp_lock) {
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_RUNLOCK(inp);
+ }
+ 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 {
+ 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));
+ }
+ if (hold_inp_lock) {
+ 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(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 > inp->sctp_ep.pre_open_stream_count) {
+ i_want = asoc->streamoutcnt;
+ } else {
+ i_want = inp->sctp_ep.pre_open_stream_count;
+ }
+ } 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->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->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);
+ /*
+ * 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;
+ }
+
+ (void)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__)
+ mflowtype, mflowid,
+#endif
+ SCTP_SO_NOT_LOCKED);
+ 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 or equal
+ * 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 sufficent 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_MED_OVERHEAD;
+ } else {
+ ovh = SCTP_MED_V4_OVERHEAD;
+ }
+
+ 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.
+ */
+#ifndef __FreeBSD__
+ 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->asoc) == SCTP_STATE_SHUTDOWN_SENT) ||
+ (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) ||
+ (SCTP_GET_STATE(&stcb->asoc) == 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;
+ 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->stream = 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 &&
+#if defined(__Panda__)
+ 0
+#else
+ (sizeofcpy <= (int)((((SCTP_BASE_SYSCTL(sctp_mbuf_threshold_count) - 1) * MLEN) + MHLEN)))
+#endif
+ ) {
+ /* 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 = M_TRAILINGSPACE(*endofchain);
+ } else {
+ /* how much is left at the end? */
+ len = 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
+ * postion
+ */
+ 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 postion */
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ );
+
+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(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 */
+ int cnt;
+ cnt = sctp_is_there_unsent_data(stcb, SCTP_SO_NOT_LOCKED);
+
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (cnt == 0)) {
+ if (asoc->locked_on_sending) {
+ goto abort_anyway;
+ }
+ /* there is nothing queued to send, so I'm done... */
+ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ /* only send SHUTDOWN the first time through */
+ if (SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(asoc, SCTP_STATE_SHUTDOWN_SENT);
+ SCTP_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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,
+ asoc->primary_destination);
+ 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(asoc) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ if (asoc->locked_on_sending) {
+ /* Locked to send out the data */
+ struct sctp_stream_queue_pending *sp;
+ sp = TAILQ_LAST(&asoc->locked_on_sending->outqueue, sctp_streamhead);
+ if (sp) {
+ if ((sp->length == 0) && (sp->msg_is_complete == 0))
+ asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT;
+ }
+ }
+ asoc->state |= SCTP_STATE_SHUTDOWN_PENDING;
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ abort_anyway:
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ sctp_abort_an_association(stcb->sctp_ep, stcb,
+ NULL, 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,
+ asoc->primary_destination);
+ }
+ }
+
+ }
+ }
+ un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) +
+ (stcb->asoc.stream_queue_cnt * sizeof(struct sctp_data_chunk)));
+
+ 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 feasable 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 */
+ sctp_m_freem(ca->m);
+ SCTP_FREE(ca, SCTP_M_COPYAL);
+}
+
+static struct mbuf *
+sctp_copy_out_all(struct uio *uio, int len)
+{
+ struct mbuf *ret, *at;
+ int 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 = M_TRAILINGSPACE(ret);
+ willcpy = min(cancpy, left);
+ at = ret;
+ while (left > 0) {
+ /* Align data to the end */
+ error = uiomove(mtod(at, caddr_t), willcpy, uio);
+ if (error) {
+ err_out_now:
+ sctp_m_freem(at);
+ return (NULL);
+ }
+ SCTP_BUF_LEN(at) = 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(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 = 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;
+
+ 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__)
+#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__)
+ SCTP_SOCKET_UNLOCK(SCTP_INP_SO(inp), 0);
+#endif
+ ca->m = sctp_copy_out_all(uio, ca->sndlen);
+#if defined(__APPLE__)
+ 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);
+ }
+ }
+ ret = sctp_initiate_iterator(NULL, sctp_sendall_iterator, NULL,
+ SCTP_PCB_ANY_FLAGS, SCTP_PCB_ANY_FEATURES,
+ SCTP_ASOC_ANY_STATE,
+ (void *)ca, 0,
+ sctp_sendall_completes, inp, 1);
+ if (ret) {
+ SCTP_PRINTF("Failed to initiate iterator for sendall\n");
+ 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);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ asoc->ctrl_queue_cnt--;
+ 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);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ asoc->ctrl_queue_cnt--;
+ 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_seq;
+ 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_seq, data_list[i]->rec.data.TSN_seq)) {
+ 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_seq, data_list[i]->rec.data.TSN_seq)) {
+ 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,
+ (uintptr_t)data_list[i]->whoTo,
+ data_list[i]->rec.data.TSN_seq);
+ }
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+ 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);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ asoc->ctrl_queue_cnt--;
+ 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 int
+sctp_can_we_split_this(struct sctp_tcb *stcb,
+ uint32_t length,
+ uint32_t goal_mtu, 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 (goal_mtu >= 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 (goal_mtu);
+ }
+ }
+ /*-
+ * 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 <= goal_mtu) ||
+ ((length - goal_mtu) < 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 goal_mtu. Do we wish to split
+ * it for the sake of packet putting together?
+ */
+ if (goal_mtu >= min(SCTP_BASE_SYSCTL(sctp_min_split_point), frag_point)) {
+ /* Its ok to split it */
+ return (min(goal_mtu, 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 goal_mtu,
+ uint32_t frag_point,
+ int *locked,
+ int *giveup,
+ int eeor_mode,
+ int *bail,
+ int so_locked
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ /* 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;
+ uint32_t to_move, length;
+ 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) {
+ *locked = 0;
+ 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 (strq->last_msg_incomplete) {
+ SCTP_PRINTF("Huh? Stream:%d lm_in_c=%d but queue is NULL\n",
+ strq->stream_no,
+ 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 differed 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 (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 */
+ *locked = 0;
+ stcb->asoc.locked_on_sending = NULL;
+ 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
+ */
+ *locked = 1;
+ *giveup = 1;
+ to_move = 0;
+ goto out_of;
+ }
+ } else {
+ /* is there some to get */
+ if (sp->length == 0) {
+ /* no */
+ *locked = 1;
+ *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;
+ *locked = 1;
+ *giveup = 1;
+ to_move = 0;
+ goto out_of;
+ }
+ }
+ some_taken = sp->some_taken;
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ sp->msg_is_complete = 1;
+ }
+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;
+ sp->put_last_out = 1;
+ } else {
+ rcv_flags |= SCTP_DATA_NOT_FRAG;
+ sp->put_last_out = 1;
+ }
+ } 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, goal_mtu, 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. */
+ if (sp->some_taken) {
+ *locked = 1;
+ }
+ *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)) ||
+ ((sp->sinfo_flags & SCTP_SACK_IMMEDIATELY) == SCTP_SACK_IMMEDIATELY)) {
+ 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 useage
+ * 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);
+ }
+ if (M_LEADINGSPACE(chk->data) < (int)sizeof(struct sctp_data_chunk)) {
+ /* Not enough room for a chunk header, get some */
+ struct mbuf *m;
+
+ m = sctp_get_mbuf_for_msg(1, 0, M_NOWAIT, 0, 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, sizeof(struct sctp_data_chunk), 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, sizeof(struct sctp_data_chunk));
+ chk->book_size = chk->send_size = (to_move + sizeof(struct sctp_data_chunk));
+ 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;
+ chk->rec.data.stream_seq = strq->next_sequence_send;
+ if ((rcv_flags & SCTP_DATA_LAST_FRAG) &&
+ !(rcv_flags & SCTP_DATA_UNORDERED)) {
+ strq->next_sequence_send++;
+ }
+ chk->rec.data.stream_number = sp->stream;
+ chk->rec.data.payloadtype = 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(__Panda__)
+ chk->rec.data.TSN_seq = atomic_fetchadd_int(&asoc->sending_seq, 1);
+#else
+ chk->rec.data.TSN_seq = asoc->sending_seq++;
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LOG_AT_SEND_2_OUTQ) {
+ sctp_misc_ints(SCTP_STRMOUT_LOG_SEND,
+ (uintptr_t)stcb, sp->length,
+ (uint32_t)((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq),
+ chk->rec.data.TSN_seq);
+ }
+ dchkh = mtod(chk->data, struct sctp_data_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_seq;
+ asoc->out_tsnlog[asoc->tsn_out_at].strm = chk->rec.data.stream_number;
+ asoc->out_tsnlog[asoc->tsn_out_at].seq = chk->rec.data.stream_seq;
+ 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
+
+ dchkh->ch.chunk_type = SCTP_DATA;
+ dchkh->ch.chunk_flags = chk->rec.data.rcv_flags;
+ dchkh->dp.tsn = htonl(chk->rec.data.TSN_seq);
+ dchkh->dp.stream_id = htons(strq->stream_no);
+ dchkh->dp.stream_sequence = htons(chk->rec.data.stream_seq);
+ dchkh->dp.protocol_id = chk->rec.data.payloadtype;
+ dchkh->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 */
+ atomic_subtract_int(&asoc->stream_queue_cnt, 1);
+ 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;
+ }
+ TAILQ_REMOVE(&strq->outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_remove_from_stream(stcb, asoc, strq, sp, send_lock_up);
+ 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 */
+ *locked = 0;
+ stcb->asoc.locked_on_sending = NULL;
+ } else {
+ /* more to go, we are locked */
+ *locked = 1;
+ }
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+ struct sctp_association *asoc;
+ struct sctp_stream_out *strq;
+ int goal_mtu, moved_how_much, total_moved = 0, bail = 0;
+ int locked, giveup;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ asoc = &stcb->asoc;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ goal_mtu = net->mtu - SCTP_MIN_V4_OVERHEAD;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ goal_mtu = net->mtu - SCTP_MIN_OVERHEAD;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ goal_mtu = net->mtu - sizeof(struct sctphdr);
+ break;
+#endif
+ default:
+ /* TSNH */
+ goal_mtu = net->mtu;
+ break;
+ }
+ /* Need an allowance for the data chunk header too */
+ goal_mtu -= sizeof(struct sctp_data_chunk);
+
+ /* must make even word boundary */
+ goal_mtu &= 0xfffffffc;
+ if (asoc->locked_on_sending) {
+ /* We are stuck on one stream until the message completes. */
+ strq = asoc->locked_on_sending;
+ locked = 1;
+ } else {
+ strq = stcb->asoc.ss_functions.sctp_ss_select_stream(stcb, net, asoc);
+ locked = 0;
+ }
+ while ((goal_mtu > 0) && strq) {
+ giveup = 0;
+ bail = 0;
+ moved_how_much = sctp_move_to_outqueue(stcb, strq, goal_mtu, frag_point, &locked,
+ &giveup, eeor_mode, &bail, so_locked);
+ if (moved_how_much)
+ stcb->asoc.ss_functions.sctp_ss_scheduled(stcb, net, asoc, strq, moved_how_much);
+
+ if (locked) {
+ asoc->locked_on_sending = strq;
+ if ((moved_how_much == 0) || (giveup) || bail)
+ /* no more to move for now */
+ break;
+ } else {
+ asoc->locked_on_sending = NULL;
+ if ((giveup) || bail) {
+ break;
+ }
+ strq = stcb->asoc.ss_functions.sctp_ss_select_stream(stcb, net, asoc);
+ if (strq == NULL) {
+ break;
+ }
+ }
+ total_moved += moved_how_much;
+ goal_mtu -= (moved_how_much + sizeof(struct sctp_data_chunk));
+ goal_mtu &= 0xfffffffc;
+ }
+ if (bail)
+ *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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ /**
+ * 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 - 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 = 0;
+ struct sctp_auth_chunk *auth = NULL;
+ 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__)
+ 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) ||
+ (asoc->state & 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) || (asoc->stream_reset_outstanding))
+ 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) &&
+ stcb->asoc.ss_functions.sctp_ss_is_empty(stcb, asoc)) {
+ 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;
+ 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 - (sizeof(struct ip) + sizeof(struct sctphdr));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ mtu = net->mtu - (sizeof(struct ip6_hdr) + sizeof(struct sctphdr));
+ 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;
+ }
+ /************************/
+ /* 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 ((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__)
+ 0, 0,
+#endif
+ so_locked))) {
+ if (error == ENOBUFS) {
+ asoc->ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ if (from_where == 0) {
+ SCTP_STAT_INCR(sctps_lowlevelerrusr);
+ }
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time);
+ *now_filled = 1;
+ *now = net->last_sent_time;
+ } else {
+ net->last_sent_time = *now;
+ }
+ hbflag = 0;
+ /* error, could not output */
+ if (error == EHOSTUNREACH) {
+ /*
+ * Destination went
+ * unreachable
+ * during this send
+ */
+ sctp_move_chunks_from_net(stcb, net);
+ }
+ *reason_code = 7;
+ continue;
+ } else
+ asoc->ifp_had_enobuf = 0;
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time);
+ *now_filled = 1;
+ *now = net->last_sent_time;
+ } else {
+ net->last_sent_time = *now;
+ }
+ hbflag = 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;
+ }
+ }
+ }
+ /************************/
+ /* 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, net, 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;
+ }
+ 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__)
+ 0, 0,
+#endif
+ so_locked))) {
+ if (error == ENOBUFS) {
+ asoc->ifp_had_enobuf = 1;
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ }
+ if (from_where == 0) {
+ SCTP_STAT_INCR(sctps_lowlevelerrusr);
+ }
+ /* error, could not output */
+ if (hbflag) {
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time);
+ *now_filled = 1;
+ *now = net->last_sent_time;
+ } else {
+ net->last_sent_time = *now;
+ }
+ hbflag = 0;
+ }
+ if (error == EHOSTUNREACH) {
+ /*
+ * Destination went
+ * unreachable
+ * during this send
+ */
+ sctp_move_chunks_from_net(stcb, net);
+ }
+ *reason_code = 7;
+ continue;
+ } else
+ asoc->ifp_had_enobuf = 0;
+ /* Only HB or ASCONF advances time */
+ if (hbflag) {
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time);
+ *now_filled = 1;
+ *now = net->last_sent_time;
+ } else {
+ net->last_sent_time = *now;
+ }
+ hbflag = 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;
+ }
+ }
+ }
+ /* 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 > (sizeof(struct ip) + sizeof(struct sctphdr)))
+ omtu = net->mtu - (sizeof(struct ip) + sizeof(struct sctphdr));
+ else
+ omtu = 0;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (net->mtu > (sizeof(struct ip6_hdr) + sizeof(struct sctphdr)))
+ omtu = net->mtu - (sizeof(struct ip6_hdr) + sizeof(struct sctphdr));
+ 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 ((((asoc->state & SCTP_STATE_OPEN) == SCTP_STATE_OPEN) &&
+ (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) == 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);
+ }
+ /* 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__)
+ 0, 0,
+#endif
+ so_locked))) {
+ /* error, we could not output */
+ if (error == ENOBUFS) {
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ asoc->ifp_had_enobuf = 1;
+ }
+ if (from_where == 0) {
+ SCTP_STAT_INCR(sctps_lowlevelerrusr);
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
+ if (hbflag) {
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time);
+ *now_filled = 1;
+ *now = net->last_sent_time;
+ } else {
+ net->last_sent_time = *now;
+ }
+ hbflag = 0;
+ }
+ 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 (bundle_at || hbflag) {
+ /* For data/asconf and hb set time */
+ if (*now_filled == 0) {
+ (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time);
+ *now_filled = 1;
+ *now = net->last_sent_time;
+ } else {
+ net->last_sent_time = *now;
+ }
+ }
+ 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_seq;
+ /* 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;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ sctp_alloc_a_chunk(stcb, chk);
+ if (chk == NULL) {
+ /* no memory */
+ sctp_m_freem(op_err);
+ return;
+ }
+ chk->copy_by_ref = 0;
+ SCTP_BUF_PREPEND(op_err, sizeof(struct sctp_chunkhdr), M_NOWAIT);
+ if (op_err == NULL) {
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ return;
+ }
+ chk->send_size = 0;
+ for (mat = op_err; mat != NULL; mat = SCTP_BUF_NEXT(mat)) {
+ chk->send_size += SCTP_BUF_LEN(mat);
+ }
+ 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,
+ 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 parm, *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, &parm, sizeof(parm));
+ if (phdr == NULL) {
+ return (-3);
+ }
+ ptype = ntohs(phdr->param_type);
+ plen = ntohs(phdr->param_length);
+ if (ptype == SCTP_STATE_COOKIE) {
+ int pad;
+
+ /* found the cookie */
+ if ((pad = (plen % 4))) {
+ plen += 4 - pad;
+ }
+ cookie = SCTP_M_COPYM(m, at, plen, M_NOWAIT);
+ if (cookie == NULL) {
+ /* No memory */
+ return (-2);
+ }
+#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 = 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) {
+ /* need pad */
+ uint32_t cpthis = 0;
+ int padlen;
+
+ padlen = 4 - (chk_length % 4);
+ m_copyback(outchain, chk_length, padlen, (caddr_t)&cpthis);
+ }
+ 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->flags = 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;
+
+ 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->flags = 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++;
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ /*-
+ * 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__)
+ 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 suceed 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__)
+ 0, 0,
+#endif
+ so_locked))) {
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ return (error);
+ }
+ 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(asoc) == SCTP_STATE_COOKIE_ECHOED) ||
+ (SCTP_GET_STATE(asoc) == 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_seq, chk->snd_count, chk->sent);
+ continue;
+ }
+ if ((SCTP_BASE_SYSCTL(sctp_max_retran_chunk)) &&
+ (chk->snd_count >= SCTP_BASE_SYSCTL(sctp_max_retran_chunk))) {
+ /* Gak, we have exceeded max unlucky retran, abort! */
+ SCTP_PRINTF("Gak, chk->snd_count:%d >= max:%d - send abort\n",
+ chk->snd_count,
+ SCTP_BASE_SYSCTL(sctp_max_retran_chunk));
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ sctp_abort_an_association(stcb->sctp_ep, stcb, NULL, 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_seq) {
+ /*
+ * 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 suceed 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__)
+ 0, 0,
+#endif
+ so_locked))) {
+ /* error, we could not output */
+ SCTP_STAT_INCR(sctps_lowlevelerr);
+ return (error);
+ }
+ 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_seq;
+ }
+ 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,
+ (uintptr_t)data_list[i]->whoTo,
+ data_list[i]->rec.data.TSN_seq);
+ }
+ 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_4);
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ /*-
+ * 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__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(inp));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(inp));
+ }
+#endif
+ asoc = &stcb->asoc;
+ /* 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)) {
+ /* 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);
+ (void)SCTP_OS_TIMER_STOP(&stcb->asoc.dack_timer.timer);
+ }
+ 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) +
+ (stcb->asoc.stream_queue_cnt * sizeof(struct sctp_data_chunk)));
+ if ((un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD)) &&
+ (stcb->asoc.total_flight > 0) &&
+ ((stcb->asoc.locked_on_sending == NULL) ||
+ sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR))) {
+ break;
+ }
+ }
+ if (TAILQ_EMPTY(&asoc->control_send_queue) &&
+ TAILQ_EMPTY(&asoc->send_queue) &&
+ stcb->asoc.ss_functions.sctp_ss_is_empty(stcb, asoc)) {
+ /* 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);
+ return;
+}
+
+
+int
+sctp_output(
+ struct sctp_inpcb *inp,
+#if defined(__Panda__)
+ pakhandle_type m,
+#else
+ struct mbuf *m,
+#endif
+ struct sockaddr *addr,
+#if defined(__Panda__)
+ pakhandle_type control,
+#else
+ struct mbuf *control,
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ struct thread *p,
+#elif defined(__Windows__)
+ PKTHREAD p,
+#else
+#if defined(__APPLE__)
+ 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(__Panda__)
+ flags
+#else
+ flags, p
+#endif
+ ));
+}
+
+void
+send_forward_tsn(struct sctp_tcb *stcb,
+ struct sctp_association *asoc)
+{
+ struct sctp_tmit_chunk *chk;
+ struct sctp_forward_tsn_chunk *fwdtsn;
+ uint32_t advance_peer_ack_point;
+
+ 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;
+ 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;
+ {
+ struct sctp_tmit_chunk *at, *tp1, *last;
+ struct sctp_strseq *strseq;
+ unsigned int cnt_of_space, i, ovh;
+ unsigned int space_needed;
+ unsigned int cnt_of_skipped = 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 (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED) {
+ /* We don't report these */
+ continue;
+ }
+ cnt_of_skipped++;
+ }
+ space_needed = (sizeof(struct sctp_forward_tsn_chunk) +
+ (cnt_of_skipped * sizeof(struct sctp_strseq)));
+
+ cnt_of_space = 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);
+ 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_seq,
+ 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_seq;
+ 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;
+ 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.
+ */
+ strseq = (struct sctp_strseq *)fwdtsn;
+ /*-
+ * 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 duplcates.. but it does add more
+ * overhead. So for now... not!
+ */
+ at = TAILQ_FIRST(&asoc->sent_queue);
+ for (i = 0; i < cnt_of_skipped; i++) {
+ tp1 = TAILQ_NEXT(at, sctp_next);
+ if (tp1 == NULL)
+ break;
+ if (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED) {
+ /* We don't report these */
+ i--;
+ at = tp1;
+ continue;
+ }
+ if (at->rec.data.TSN_seq == advance_peer_ack_point) {
+ at->rec.data.fwd_tsn_cnt = 0;
+ }
+ strseq->stream = ntohs(at->rec.data.stream_number);
+ strseq->sequence = ntohs(at->rec.data.stream_seq);
+ strseq++;
+ at = tp1;
+ }
+ }
+ return;
+}
+
+void
+sctp_send_sack(struct sctp_tcb *stcb, int so_locked
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+ /*-
+ * 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;
+ 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_5);
+ 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->numduptsns) ||
+ (!(asoc->last_data_chunk_from->dest_state & SCTP_ADDR_REACHABLE))) {
+ /*-
+ * Ok, we have some duplicates or the destination for the
+ * sack is unreachable, lets see if we can select an
+ * alternate than asoc->last_data_chunk_from
+ */
+ if ((asoc->last_data_chunk_from->dest_state & SCTP_ADDR_REACHABLE) &&
+ (asoc->used_alt_onsack > asoc->numnets)) {
+ /* We used an alt last time, don't this time */
+ a_chk->whoTo = NULL;
+ } else {
+ asoc->used_alt_onsack++;
+ 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;
+ asoc->used_alt_onsack = 0;
+ }
+ } else {
+ /*
+ * No duplicates so we use the last place we received data
+ * from.
+ */
+ asoc->used_alt_onsack = 0;
+ 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_6);
+ 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 = 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 - highest_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 &= (~0 << (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 &= (~0 << (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 = 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 = 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ 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;
+ uint16_t cause_len, chunk_len, padding_len;
+
+#if defined(__APPLE__)
+ 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;
+ }
+ }
+ (void)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__)
+ 0, 0,
+#endif
+ so_locked);
+ 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;
+ 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);
+ (void)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__)
+ 0, 0,
+#endif
+ SCTP_SO_NOT_LOCKED);
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ return;
+}
+
+#if defined(__FreeBSD__)
+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,
+ 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
+{
+#ifdef __Panda__
+ pakhandle_type o_pak;
+#else
+ struct mbuf *o_pak;
+#endif
+ struct mbuf *mout;
+ struct sctphdr *shout;
+ struct sctp_chunkhdr *ch;
+#if defined(INET) || defined(INET6)
+ struct udphdr *udp;
+ int ret;
+#endif
+ int len, cause_len, padding_len;
+#ifdef INET
+#if defined(__APPLE__) || defined(__Panda__)
+ 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__)
+#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__)
+#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__)
+ 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__)
+ ip->ip_id = ip_newid();
+#elif defined(__APPLE__)
+#if RANDOM_IP_ID
+ ip->ip_id = ip_randomid();
+#else
+ ip->ip_id = htons(ip_id++);
+#endif
+#else
+ ip->ip_id = htons(ip_id++);
+#endif
+ ip->ip_off = 0;
+ 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__)
+ 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(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(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(__Panda__)
+ /* zap the stack pointer to the route */
+ bzero(&ro, sizeof(sctp_route_t));
+#if defined(__Panda__)
+ ro._l_addr.sa.sa_family = AF_INET;
+#endif
+#endif
+ if (port) {
+#if !defined(__Windows__) && !defined(__Userspace__)
+#if defined(__FreeBSD__) && ((__FreeBSD_version > 803000 && __FreeBSD_version < 900000) || __FreeBSD_version > 900000)
+ 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__)
+#if __FreeBSD_version >= 1000000
+ ip->ip_len = htons(len);
+#else
+ ip->ip_len = len;
+#endif
+#elif defined(__APPLE__) || defined(__Userspace__)
+ ip->ip_len = len;
+#else
+ ip->ip_len = htons(len);
+#endif
+ if (port) {
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+ shout->checksum = sctp_calculate_cksum(mout, sizeof(struct ip) + sizeof(struct udphdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#endif
+#if defined(__FreeBSD__) && ((__FreeBSD_version > 803000 && __FreeBSD_version < 900000) || __FreeBSD_version > 900000)
+ if (V_udp_cksum) {
+ SCTP_ENABLE_UDP_CSUM(o_pak);
+ }
+#else
+ SCTP_ENABLE_UDP_CSUM(o_pak);
+#endif
+ } else {
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ 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
+#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(__Panda__)
+ 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
+ SCTP_IP_OUTPUT(ret, o_pak, NULL, NULL, vrf_id);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ip6->ip6_plen = len - sizeof(struct ip6_hdr);
+ if (port) {
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+ shout->checksum = sctp_calculate_cksum(mout, sizeof(struct ip6_hdr) + sizeof(struct udphdr));
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#endif
+#if defined(__Windows__)
+ udp->uh_sum = 0;
+#elif !defined(__Userspace__)
+ 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
+ } else {
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+#if defined(__FreeBSD__) && __FreeBSD_version >= 900000
+#if __FreeBSD_version > 901000
+ mout->m_pkthdr.csum_flags = CSUM_SCTP_IPV6;
+#else
+ mout->m_pkthdr.csum_flags = CSUM_SCTP;
+#endif
+ 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
+#endif
+ }
+#ifdef SCTP_PACKET_LOGGING
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING) {
+ sctp_packet_log(o_pak);
+ }
+#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 defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_sendnocrc);
+#else
+ shout->checksum = sctp_calculate_cksum(mout, 0);
+ SCTP_STAT_INCR(sctps_sendswcrc);
+#endif
+#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);
+ SCTP_BASE_VAR(conn_output)(sconn->sconn_addr, buffer, len, 0, 0);
+ free(buffer);
+ }
+ 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;
+ }
+ SCTP_STAT_INCR(sctps_sendpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_outpackets);
+ SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks);
+ return;
+}
+
+void
+sctp_send_shutdown_complete2(struct sockaddr *src, struct sockaddr *dst,
+ struct sctphdr *sh,
+#if defined(__FreeBSD__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ sctp_send_resp_msg(src, dst, sh, 0, SCTP_SHUTDOWN_COMPLETE, NULL,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+}
+
+void
+sctp_send_hb(struct sctp_tcb *stcb, struct sctp_nets *net,int so_locked
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+ 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:
+ 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 = 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++;
+}
+
+void
+sctp_add_stream_reset_out(struct sctp_tmit_chunk *chk,
+ int number_entries, uint16_t * list,
+ 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;
+
+ 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? */
+ len = (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);
+ if (number_entries) {
+ for (i = 0; i < number_entries; i++) {
+ req_out->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_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;
+}
+
+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 = (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_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_str_reset_req(struct sctp_tcb *stcb,
+ uint16_t number_entries, uint16_t *list,
+ uint8_t send_out_req,
+ 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;
+ 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_out_req == 0) && (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_out_req || send_in_req)) {
+ /* error, can't do that */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ return (EINVAL);
+ }
+ 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 (send_out_req) {
+ sctp_add_stream_reset_out(chk, number_entries, list,
+ seq, (stcb->asoc.str_reset_seq_in - 1), (stcb->asoc.sending_seq - 1));
+ asoc->stream_reset_out_is_outstanding = 1;
+ 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_sequence_send = oldstream[i].next_sequence_send;
+ stcb->asoc.strmout[i].last_msg_incomplete = oldstream[i].last_msg_incomplete;
+ stcb->asoc.strmout[i].stream_no = i;
+ stcb->asoc.ss_functions.sctp_ss_init_stream(&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 move assoc pointers too */
+ if (stcb->asoc.last_out_stream == &oldstream[i]) {
+ stcb->asoc.last_out_stream = &stcb->asoc.strmout[i];
+ }
+ if (stcb->asoc.locked_on_sending == &oldstream[i]) {
+ stcb->asoc.locked_on_sending = &stcb->asoc.strmout[i];
+ }
+ }
+ /* 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_sequence_send = 0x0;
+ stcb->asoc.strmout[i].stream_no = i;
+ stcb->asoc.strmout[i].last_msg_incomplete = 0;
+ stcb->asoc.ss_functions.sctp_ss_init_stream(&stcb->asoc.strmout[i], NULL);
+ }
+ 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++;
+ 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__)
+ uint8_t mflowtype, uint32_t mflowid,
+#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__)
+ mflowtype, mflowid,
+#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__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ sctp_send_resp_msg(src, dst, sh, vtag, SCTP_OPERATION_ERROR, cause,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ return;
+}
+
+static struct mbuf *
+sctp_copy_resume(struct uio *uio,
+ int max_send_len,
+#if defined(__FreeBSD__) && __FreeBSD_version > 602000
+ int user_marks_eor,
+#endif
+ int *error,
+ uint32_t *sndout,
+ struct mbuf **new_tail)
+{
+#if defined(__Panda__)
+ struct mbuf *m;
+
+ m = m_uiotombuf(uio, M_WAITOK, max_send_len, 0,
+ (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);
+#elif defined(__FreeBSD__) && __FreeBSD_version > 602000
+ 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__)
+#if defined(APPLE_LEOPARD)
+ left = min(uio->uio_resid, max_send_len);
+#else
+ left = min(uio_resid(uio), max_send_len);
+#endif
+#else
+ left = 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 = 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 = 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)
+{
+ int left;
+#if defined(__Panda__)
+ left = sp->length;
+ 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);
+
+#elif defined(__FreeBSD__) && __FreeBSD_version > 602000
+ left = sp->length;
+ 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 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 = 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 = 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,
+ int 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(asoc) == SCTP_STATE_SHUTDOWN_SENT) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) ||
+ (SCTP_GET_STATE(asoc) == 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;
+ (void)SCTP_GETTIME_TIMEVAL(&sp->ts);
+
+ sp->stream = srcv->sinfo_stream;
+#if defined(__APPLE__)
+#if defined(APPLE_LEOPARD)
+ sp->length = min(uio->uio_resid, max_send_len);
+#else
+ sp->length = min(uio_resid(uio), max_send_len);
+#endif
+#else
+ sp->length = min(uio->uio_resid, max_send_len);
+#endif
+#if defined(__APPLE__)
+#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 = sizeof(struct sctp_data_chunk);
+ sp->data = sp->tail_mbuf = NULL;
+ if (sp->length == 0) {
+ *error = 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__)
+ SCTP_SOCKET_UNLOCK(SCTP_INP_SO(stcb->sctp_ep), 0);
+#endif
+ *error = sctp_copy_one(sp, uio, resv_in_first);
+#if defined(__APPLE__)
+ SCTP_SOCKET_LOCK(SCTP_INP_SO(stcb->sctp_ep), 0);
+#endif
+ skip_copy:
+ if (*error) {
+ sctp_free_a_strmoq(stcb, sp, SCTP_SO_LOCKED);
+ 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,
+#ifdef __Panda__
+ pakhandle_type top,
+ pakhandle_type icontrol,
+#else
+ struct mbuf *top,
+ struct mbuf *control,
+#endif
+#if defined(__APPLE__) || defined(__Panda__)
+ int flags
+#else
+ int flags,
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ struct thread *p
+#elif defined(__Windows__)
+ 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
+)
+{
+#ifdef __Panda__
+ struct mbuf *control = NULL;
+#endif
+#if defined(__APPLE__)
+ 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__)
+ SCTP_SOCKET_LOCK(so, 1);
+#endif
+#ifdef __Panda__
+ control = SCTP_HEADER_TO_CHAIN(icontrol);
+#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,
+#ifdef __Panda__
+ icontrol,
+#else
+ control,
+#endif
+ flags,
+ use_sndinfo ? &sndrcvninfo: NULL
+#if !(defined(__Panda__) || defined(__Userspace__))
+ , p
+#endif
+ );
+#if defined(__APPLE__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (error);
+}
+
+
+int
+sctp_lower_sosend(struct socket *so,
+ struct sockaddr *addr,
+ struct uio *uio,
+#ifdef __Panda__
+ pakhandle_type i_pak,
+ pakhandle_type i_control,
+#else
+ struct mbuf *i_pak,
+ struct mbuf *control,
+#endif
+ int flags,
+ struct sctp_sndrcvinfo *srcv
+#if !(defined( __Panda__) || defined(__Userspace__))
+ ,
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ struct thread *p
+#elif defined(__Windows__)
+ PKTHREAD p
+#else
+ struct proc *p
+#endif
+#endif
+ )
+{
+ unsigned int sndlen = 0, max_len;
+ int error, len;
+ struct mbuf *top = NULL;
+#ifdef __Panda__
+ struct mbuf *control = NULL;
+#endif
+ 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;
+ uint32_t local_add_more, 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__)
+ 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__)
+#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__)
+#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);
+#ifdef __Panda__
+ /*-
+ * app len indicates the datalen, dgsize for cases
+ * of SCTP_EOF/ABORT will not have the right len
+ */
+ sndlen = SCTP_APP_DATA_LEN(i_pak);
+ /*-
+ * Set the particle len also to zero to match
+ * up with app len. We only have one particle
+ * if app len is zero for Panda. This is ensured
+ * in the socket lib
+ */
+ if (sndlen == 0) {
+ SCTP_BUF_LEN(top) = 0;
+ }
+ /*-
+ * We delink the chain from header, but keep
+ * the header around as we will need it in
+ * EAGAIN case
+ */
+ SCTP_DETACH_HEADER_FROM_CHAIN(i_pak);
+#else
+ sndlen = SCTP_HEADER_LEN(i_pak);
+#endif
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Send called addr:%p send length %d\n",
+ (void *)addr,
+ sndlen);
+#ifdef __Panda__
+ if (i_control) {
+ control = SCTP_HEADER_TO_CHAIN(i_control);
+ }
+#endif
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ (inp->sctp_socket->so_qlimit)) {
+ /* 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 (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, 0);
+ } 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 ((stcb == NULL) && (control != NULL) && (port > 0)) {
+ stcb = sctp_findassociation_cmsgs(&t_inp, port, control, &net, &error);
+ }
+ 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,
+#if !(defined( __Panda__) || defined(__Userspace__))
+ p
+#else
+ (struct proc *)NULL
+#endif
+ );
+ 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(asoc, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&asoc->time_entered);
+
+ /* initialize authentication params for the assoc */
+ sctp_initialize_auth_params(inp, stcb);
+
+ 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_7);
+ 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;
+ if (srcv->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 > 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__) && __FreeBSD_version >= 500000
+ || (flags & MSG_NBIO)
+#endif
+ ) {
+ non_blocking = 1;
+ }
+ /* would we block? */
+ if (non_blocking) {
+ 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 * sizeof(struct sctp_data_chunk));
+ if ((SCTP_SB_LIMIT_SND(so) < (sndlen + 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 > SCTP_SB_LIMIT_SND(so))
+ error = EMSGSIZE;
+ else
+ error = EWOULDBLOCK;
+ goto out_unlocked;
+ }
+ stcb->asoc.sb_send_resv += 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;
+ }
+ if (asoc->stream_reset_outstanding) {
+ /*
+ * Can't queue any data while stream reset is underway.
+ */
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EAGAIN);
+ error = EAGAIN;
+ goto out_unlocked;
+ }
+ if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(asoc) == 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(asoc) == SCTP_STATE_SHUTDOWN_SENT) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) ||
+ (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) {
+ if (srcv->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(__Panda__) || defined(__Windows__) || defined(__Userspace__))
+ if (p) {
+#if defined(__FreeBSD__) && __FreeBSD_version >= 603000
+ p->td_ru.ru_msgsnd++;
+#elif defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ p->td_proc->p_stats->p_ru.ru_msgsnd++;
+#else
+ p->p_stats->p_ru.ru_msgsnd++;
+#endif
+ }
+#endif
+ /* Are we aborting? */
+ if (srcv->sinfo_flags & SCTP_ABORT) {
+ struct mbuf *mm;
+ int tot_demand, tot_out = 0, max_out;
+
+ SCTP_STAT_INCR(sctps_sends_with_abort);
+ if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(asoc) == 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(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(sizeof(struct sctp_paramhdr) + tot_out);
+ ph++;
+ SCTP_BUF_LEN(mm) = tot_out + sizeof(struct sctp_paramhdr);
+ if (top == NULL) {
+#if defined(__APPLE__)
+ SCTP_SOCKET_UNLOCK(so, 0);
+#endif
+ error = uiomove((caddr_t)ph, (int)tot_out, uio);
+#if defined(__APPLE__)
+ 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 */
+ sctp_abort_an_association(stcb->sctp_ep, stcb, mm, SCTP_SO_LOCKED);
+ /* 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 * sizeof(struct sctp_data_chunk));
+ if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes) {
+ if (non_blocking) {
+ /* we already checked for non-blocking above. */
+ max_len = sndlen;
+ } else {
+ max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
+ }
+ } else {
+ max_len = 0;
+ }
+ if (hold_tcblock) {
+ SCTP_TCB_UNLOCK(stcb);
+ hold_tcblock = 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 == 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 > 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 = 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) &&
+ (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 * sizeof(struct sctp_data_chunk));
+ 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 + %d) || (%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(__Panda__) && !defined(__Windows__)
+ 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) {
+ goto out_unlocked;
+ }
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * sizeof(struct sctp_data_chunk));
+ }
+ 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__)
+ 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 (srcv->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 ((sp == NULL) || (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;
+ 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 (srcv->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__)
+#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;
+
+ if (SCTP_SB_LIMIT_SND(so) > stcb->asoc.total_output_queue_size)
+ max_len = SCTP_SB_LIMIT_SND(so) - stcb->asoc.total_output_queue_size;
+ else
+ max_len = 0;
+
+ if ((max_len > SCTP_BASE_SYSCTL(sctp_add_more_threshold)) ||
+ (max_len && (SCTP_SB_LIMIT_SND(so) < SCTP_BASE_SYSCTL(sctp_add_more_threshold))) ||
+#if defined(__APPLE__)
+#if defined(APPLE_LEOPARD)
+ (uio->uio_resid && (uio->uio_resid <= (int)max_len))) {
+#else
+ (uio_resid(uio) && (uio_resid(uio) <= (int)max_len))) {
+#endif
+#else
+ (uio->uio_resid && (uio->uio_resid <= (int)max_len))) {
+#endif
+ sndout = 0;
+ new_tail = NULL;
+ if (hold_tcblock) {
+ SCTP_TCB_UNLOCK(stcb);
+ hold_tcblock = 0;
+ }
+#if defined(__APPLE__)
+ SCTP_SOCKET_UNLOCK(so, 0);
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version > 602000
+ mm = sctp_copy_resume(uio, max_len, user_marks_eor, &error, &sndout, &new_tail);
+#else
+ mm = sctp_copy_resume(uio, max_len, &error, &sndout, &new_tail);
+#endif
+#if defined(__APPLE__)
+ 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;
+
+ /* Did we reach EOR? */
+#if defined(__APPLE__)
+#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) ||
+ (srcv->sinfo_flags & SCTP_EOF) ||
+ (user_marks_eor && (srcv->sinfo_flags & SCTP_EOR)))) {
+ sp->msg_is_complete = 1;
+ } else {
+ sp->msg_is_complete = 0;
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+#if defined(__APPLE__)
+#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, sndlen);
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * sizeof(struct sctp_data_chunk));
+ if (SCTP_SB_LIMIT_SND(so) > stcb->asoc.total_output_queue_size)
+ 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->asoc) == SCTP_STATE_OPEN) {
+ /* a collision took us forward? */
+ queue_only = 0;
+ } else {
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+ SCTP_SET_STATE(asoc, 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) +
+ (stcb->asoc.stream_queue_cnt * sizeof(struct sctp_data_chunk)));
+ 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 (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 (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 (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.
+ */
+ if (SCTP_SB_LIMIT_SND(so) <= (stcb->asoc.total_output_queue_size +
+ 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__)
+#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(__Panda__) && !defined(__Windows__)
+ stcb->block_entry = &be;
+#endif
+#if defined(__APPLE__)
+ 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__)
+ 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 (sp) {
+ if (sp->msg_is_complete == 0) {
+ strm->last_msg_incomplete = 1;
+ 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__)
+#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 (srcv->sinfo_flags & SCTP_EOF) {
+ /*
+ * This should only happen for Panda for the mbuf
+ * send case, which does NOT yet support EEOR mode.
+ * Thus, we can just set this flag to do the proper
+ * EOF handling.
+ */
+ got_all_of_the_send = 1;
+ }
+ }
+ if (error) {
+ goto out;
+ }
+dataless_eof:
+ /* EOF thing ? */
+ if ((srcv->sinfo_flags & SCTP_EOF) &&
+ (got_all_of_the_send == 1)) {
+ int cnt;
+ SCTP_STAT_INCR(sctps_sends_with_eof);
+ error = 0;
+ if (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ hold_tcblock = 1;
+ }
+ cnt = sctp_is_there_unsent_data(stcb, SCTP_SO_LOCKED);
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (cnt == 0)) {
+ if (asoc->locked_on_sending) {
+ goto abort_anyway;
+ }
+ /* there is nothing queued to send, so I'm done... */
+ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ struct sctp_nets *netp;
+
+ /* only send SHUTDOWN the first time through */
+ if (SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(asoc, SCTP_STATE_SHUTDOWN_SENT);
+ SCTP_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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,
+ asoc->primary_destination);
+ }
+ } 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(asoc) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ if (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ hold_tcblock = 1;
+ }
+ if (asoc->locked_on_sending) {
+ /* Locked to send out the data */
+ struct sctp_stream_queue_pending *sp;
+ sp = TAILQ_LAST(&asoc->locked_on_sending->outqueue, sctp_streamhead);
+ if (sp) {
+ if ((sp->length == 0) && (sp->msg_is_complete == 0))
+ asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT;
+ }
+ }
+ asoc->state |= SCTP_STATE_SHUTDOWN_PENDING;
+ if (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ abort_anyway:
+ if (free_cnt_applied) {
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ free_cnt_applied = 0;
+ }
+ sctp_abort_an_association(stcb->sctp_ep, stcb,
+ NULL, SCTP_SO_LOCKED);
+ /* 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,
+ asoc->primary_destination);
+ 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->asoc) == SCTP_STATE_OPEN) {
+ /* a collision took us forward? */
+ queue_only = 0;
+ } else {
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+ SCTP_SET_STATE(&stcb->asoc, 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) +
+ (stcb->asoc.stream_queue_cnt * sizeof(struct sctp_data_chunk)));
+ 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 ((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);
+ }
+ 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__)
+ 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(__APPLE__)
+ 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
+#ifdef __Panda__
+ /*
+ * Handle the EAGAIN/ENOMEM cases to reattach the pak header
+ * to particle when pak is passed in, so that caller
+ * can try again with this pak
+ *
+ * NOTE: For other cases, including success case,
+ * we simply want to return the header back to free
+ * pool
+ */
+ if (top) {
+ if ((error == EAGAIN) || (error == ENOMEM)) {
+ SCTP_ATTACH_CHAIN(i_pak, top, sndlen);
+ top = NULL;
+ } else {
+ (void)SCTP_RELEASE_HEADER(i_pak);
+ }
+ } else {
+ /* This is to handle cases when top has
+ * been reset to NULL but pak might not
+ * be freed
+ */
+ if (i_pak) {
+ (void)SCTP_RELEASE_HEADER(i_pak);
+ }
+ }
+#endif
+#ifdef INVARIANTS
+ if (inp) {
+ sctp_validate_no_locks(inp);
+ } else {
+ SCTP_PRINTF("Warning - inp is NULL so cant validate locks\n");
+ }
+#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 *);
+ bzero(auth, 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__)
+#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 (ro == NULL || ro->ro_rt == NULL || src6->sin6_family != AF_INET6)
+ return (0);
+
+ /* get prefix entry of address */
+ 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) {
+ 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 ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, ro->ro_rt->rt_gateway);
+ if (sctp_cmpaddr((struct sockaddr *)&gw6,
+ ro->ro_rt->rt_gateway)) {
+ SCTPDBG(SCTP_DEBUG_OUTPUT2, "pfxrouter is installed\n");
+ return (1);
+ }
+ }
+ 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 (ro == NULL || ro->ro_rt == NULL ||
+ 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);
+
+ sin = (struct sockaddr_in *)ro->ro_rt->rt_gateway;
+ gwnetaddr.s_addr = (sin->sin_addr.s_addr & mask->sin_addr.s_addr);
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "match_nexthop4: nexthop is ");
+ SCTPDBG_ADDR(SCTP_DEBUG_OUTPUT2, ro->ro_rt->rt_gateway);
+ 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..d03bead046
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_output.h
@@ -0,0 +1,262 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_output.h 272751 2014-10-08 15:30:59Z 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ );
+
+void
+sctp_send_initiate_ack(struct sctp_inpcb *, struct sctp_tcb *, struct mbuf *,
+ int, int,
+ struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, struct sctp_init_chunk *,
+#if defined(__FreeBSD__)
+ uint8_t, uint32_t,
+#endif
+ uint32_t, uint16_t, int);
+
+struct mbuf *
+sctp_arethere_unrecognized_parameters(struct mbuf *, int, int *,
+ struct sctp_chunkhdr *, int *);
+void sctp_queue_op_err(struct sctp_tcb *, struct mbuf *);
+
+int
+sctp_send_cookie_echo(struct mbuf *, 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__)
+ uint8_t, uint32_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);
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+int
+sctp_output(struct sctp_inpcb *, struct mbuf *, struct sockaddr *,
+ struct mbuf *, struct thread *, int);
+#elif defined(__Windows__)
+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 *,
+#if defined(__Panda__)
+ pakhandle_type,
+#else
+ struct mbuf *,
+#endif
+ struct sockaddr *,
+#if defined(__Panda__)
+ pakhandle_type,
+#else
+ struct mbuf *,
+#endif
+ struct proc *, int);
+#endif
+
+void sctp_chunk_output(struct sctp_inpcb *, struct sctp_tcb *, int, int
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ );
+void sctp_send_abort_tcb(struct sctp_tcb *, struct mbuf *, int
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ );
+
+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_out(struct sctp_tmit_chunk *,
+ int, uint16_t *, uint32_t, uint32_t, uint32_t);
+
+void
+sctp_add_stream_reset_result(struct sctp_tmit_chunk *, uint32_t, uint32_t);
+
+void
+sctp_add_stream_reset_result_tsn(struct sctp_tmit_chunk *,
+ uint32_t, uint32_t, uint32_t, uint32_t);
+
+int
+sctp_send_str_reset_req(struct sctp_tcb *, uint16_t , uint16_t *, uint8_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__)
+ uint8_t, uint32_t,
+#endif
+ uint32_t, uint16_t);
+
+void sctp_send_operr_to(struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, uint32_t, struct mbuf *,
+#if defined(__FreeBSD__)
+ uint8_t, uint32_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,
+#ifdef __Panda__
+ pakhandle_type top,
+ pakhandle_type control,
+#else
+ struct mbuf *top,
+ struct mbuf *control,
+#endif
+#if defined(__APPLE__) || defined(__Panda__)
+ int flags
+#else
+ int flags,
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ struct thread *p
+#elif defined(__Windows__)
+ 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..2970970250
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_pcb.c
@@ -0,0 +1,8072 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_pcb.c 280459 2015-03-24 21:12:45Z tuexen $");
+#endif
+
+#include <netinet/sctp_os.h>
+#ifdef __FreeBSD__
+#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(__FreeBSD__) && __FreeBSD_version >= 803000
+#include <netinet/sctp_dtrace_define.h>
+#endif
+#if defined(INET) || defined(INET6)
+#if !defined(__Userspace_os_Windows)
+#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__)
+#include <sys/sched.h>
+#include <sys/smp.h>
+#include <sys/unistd.h>
+#endif
+#if defined(__Userspace__)
+#include <user_socketvar.h>
+#if !defined(__Userspace_os_Windows)
+#include <netdb.h>
+#endif
+#endif
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 4
+#endif
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+VNET_DEFINE(struct sctp_base_info, system_base_info);
+#else
+struct sctp_base_info system_base_info;
+#endif
+
+#if defined(__Userspace__)
+#if defined(INET) || defined(INET6)
+struct ifaddrs *g_interfaces;
+#endif
+#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__)
+ 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);
+ SCTP_DEREGISTER_INTERFACE(sctp_ifnp->ifn_index,
+ sctp_ifnp->registered_af);
+ 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 unuseable\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 unuseable\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_REGISTER_INTERFACE(sctp_ifnp->ifn_index, ifa_af);
+ 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_DEREGISTER_INTERFACE(sctp_ifap->ifn_p->ifn_index, AF_INET6);
+ SCTP_REGISTER_INTERFACE(sctp_ifap->ifn_p->ifn_index, AF_INET);
+ 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_DEREGISTER_INTERFACE(sctp_ifap->ifn_p->ifn_index, AF_INET);
+ SCTP_REGISTER_INTERFACE(sctp_ifap->ifn_p->ifn_index, AF_INET6);
+ 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) {
+ snprintf(sctp_ifnp->ifn_name, SCTP_IFNAMSIZ, "%s", if_name);
+ } else {
+ 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_REGISTER_INTERFACE(ifn_index, 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();
+ bzero(wi, 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_WQ_ADDR_UNLOCK();
+
+ sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
+ (struct sctp_inpcb *)NULL,
+ (struct sctp_tcb *)NULL,
+ (struct sctp_nets *)NULL);
+ } 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. We do this especially for
+ * panda who might recycle indexes fast.
+ */
+ 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();
+ bzero(wi, 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_WQ_ADDR_UNLOCK();
+
+ sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
+ (struct sctp_inpcb *)NULL,
+ (struct sctp_tcb *)NULL,
+ (struct sctp_nets *)NULL);
+ }
+ 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__)
+ 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__)
+ 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__)
+ 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", __FUNCTION__);
+ 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_in6 *)remote)->sin6_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) && (inp->sctp_socket->so_qlimit)) {
+ /* 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__)
+ 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__)
+ 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",
+ __FUNCTION__);
+ 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__)
+ 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 (tinp->sctp_socket->so_qlimit) {
+ 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, parm_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, &parm_buf, sizeof(parm_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_parm, *p4;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&ip4_parm, min(plen, sizeof(ip4_parm)));
+ 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_parm, *p6;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&ip6_parm, min(plen,sizeof(ip6_parm)));
+ 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, &parm_buf,
+ sizeof(parm_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)
+{
+ int find_tcp_pool;
+ 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);
+ }
+ }
+
+ find_tcp_pool = 0;
+ if ((ch->chunk_type != SCTP_INITIATION) &&
+ (ch->chunk_type != SCTP_INITIATION_ACK) &&
+ (ch->chunk_type != SCTP_COOKIE_ACK) &&
+ (ch->chunk_type != SCTP_COOKIE_ECHO)) {
+ /* Other chunk types go to the tcp pool. */
+ find_tcp_pool = 1;
+ }
+ if (inp_p) {
+ stcb = sctp_findassociation_addr_sa(src, dst, inp_p, netp,
+ find_tcp_pool, vrf_id);
+ inp = *inp_p;
+ } else {
+ stcb = sctp_findassociation_addr_sa(src, dst, &inp, netp,
+ find_tcp_pool, 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 parm_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),
+ &parm_buf, sizeof(struct sctp_paramhdr));
+ if (phdr == NULL) {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s: failed to get asconf lookup addr\n",
+ __FUNCTION__);
+ 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));
+ if (p6 == NULL) {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s: failed to get asconf v6 lookup addr\n",
+ __FUNCTION__);
+ 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));
+ if (p4 == NULL) {
+ SCTPDBG(SCTP_DEBUG_INPUT3, "%s: failed to get asconf v4 lookup addr\n",
+ __FUNCTION__);
+ 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 */
+ bzero(inp, sizeof(*inp));
+
+ /* bump generations */
+#if defined(__APPLE__)
+ 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__)
+ inp->ip_inp.inp.inp_cred = crhold(so->so_cred);
+#endif
+#ifdef INET6
+#if !defined(__Userspace__) && !defined(__Windows__)
+ 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);
+#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__)
+ crfree(inp->ip_inp.inp.inp_cred);
+#endif
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return (ENOBUFS);
+ }
+#ifdef IPSEC
+#if !(defined(__APPLE__))
+ {
+ struct inpcbpolicy *pcb_sp = NULL;
+
+ error = ipsec_init_policy(so, &pcb_sp);
+ /* Arrange to share the policy */
+ inp->ip_inp.inp.inp_sp = pcb_sp;
+ ((struct in6pcb *)(&inp->ip_inp.inp))->in6p_sp = pcb_sp;
+ }
+#else
+ /* not sure what to do for openbsd here */
+ error = 0;
+#endif
+ if (error != 0) {
+#if defined(__FreeBSD__)
+ crfree(inp->ip_inp.inp.inp_cred);
+#endif
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return error;
+ }
+#endif /* IPSEC */
+ 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 defined(__FreeBSD__) && __FreeBSD_version < 803000
+ if ((SCTP_SO_TYPE(so) == SOCK_DGRAM) ||
+ (SCTP_SO_TYPE(so) == SOCK_SEQPACKET)) {
+#else
+ if (SCTP_SO_TYPE(so) == SOCK_SEQPACKET) {
+#endif
+ /* 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 */
+ SCTP_CLEAR_SO_NBIO(so);
+#if defined(__Panda__)
+ } else if (SCTP_SO_TYPE(so) == SOCK_FASTSEQPACKET) {
+ inp->sctp_flags = (SCTP_PCB_FLAGS_UDPTYPE |
+ SCTP_PCB_FLAGS_UNBOUND);
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE);
+ } else if (SCTP_SO_TYPE(so) == SOCK_FASTSTREAM) {
+ inp->sctp_flags = (SCTP_PCB_FLAGS_TCPTYPE |
+ SCTP_PCB_FLAGS_UNBOUND);
+ sctp_feature_on(inp, SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE);
+#endif
+ } 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__)
+ 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__)
+ 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__)
+ 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__)
+#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;
+#if defined(__FreeBSD__)
+ crfree(inp->ip_inp.inp.inp_cred);
+#endif
+ 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__)
+ 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__)
+ 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] = SEC_TO_TICKS(SCTP_SEND_SEC); /* needed ? */
+ m->sctp_timeoutticks[SCTP_TIMER_INIT] = SEC_TO_TICKS(SCTP_INIT_SEC); /* needed ? */
+ m->sctp_timeoutticks[SCTP_TIMER_RECV] = MSEC_TO_TICKS(SCTP_BASE_SYSCTL(sctp_delayed_sack_time_default));
+ m->sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = MSEC_TO_TICKS(SCTP_BASE_SYSCTL(sctp_heartbeat_interval_default));
+ m->sctp_timeoutticks[SCTP_TIMER_PMTU] = SEC_TO_TICKS(SCTP_BASE_SYSCTL(sctp_pmtu_raise_time_default));
+ m->sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN] = SEC_TO_TICKS(SCTP_BASE_SYSCTL(sctp_shutdown_guard_time_default));
+ m->sctp_timeoutticks[SCTP_TIMER_SIGNATURE] = SEC_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);
+
+ /* 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 = MSEC_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();
+ bzero(laddr, 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.delayed_event_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);
+}
+
+
+#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__) && __FreeBSD_version >= 500000
+sctp_inpcb_bind(struct socket *so, struct sockaddr *addr,
+ struct sctp_ifa *sctp_ifap, struct thread *p)
+#elif defined(__Windows__)
+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(INET) || (defined(INET6) && defined(__APPLE__)) || defined(__FreeBSD__) || defined(__APPLE__)
+ 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(INET) || (defined(INET6) && defined(__APPLE__)) || defined(__FreeBSD__) || defined(__APPLE__)
+ 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__) && __FreeBSD_version >= 500000
+#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(ip_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__) && __FreeBSD_version >= 800000
+ /*
+ * 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__) && __FreeBSD_version >= 800000
+ /*
+ * 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__)
+#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__)
+ 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(__Windows__)
+ if (ntohs(lport) < IPPORT_RESERVED) {
+ if (p && (error =
+#ifdef __FreeBSD__
+#if __FreeBSD_version > 602000
+ priv_check(p, PRIV_NETINET_RESERVEDPORT)
+#elif __FreeBSD_version >= 500000
+ suser_cred(p->td_ucred, 0)
+#else
+ suser(p)
+#endif
+#elif defined(__APPLE__)
+ suser(p->p_ucred, &p->p_acflag)
+#elif defined(__Userspace__) /* must be true to use raw socket */
+ 1
+#else
+ suser(p, 0)
+#endif
+ )) {
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return (error);
+ }
+#if defined(__Panda__)
+ if (!SCTP_IS_PRIVILEDGED(so)) {
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EACCES);
+ return (EACCES);
+ }
+#endif
+ }
+#endif /* __Windows__ */
+ 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(__Windows__)
+ first = 1;
+ last = 0xffff;
+#else
+#if defined(__Userspace__)
+ /* TODO ensure uid is 0, etc... */
+#elif defined(__FreeBSD__) || defined(__APPLE__)
+ if (ip_inp->inp_flags & INP_HIGHPORT) {
+ first = MODULE_GLOBAL(ipport_hifirstauto);
+ last = MODULE_GLOBAL(ipport_hilastauto);
+ } else if (ip_inp->inp_flags & INP_LOWPORT) {
+ if (p && (error =
+#ifdef __FreeBSD__
+#if __FreeBSD_version > 602000
+ priv_check(p, PRIV_NETINET_RESERVEDPORT)
+#elif __FreeBSD_version >= 500000
+ suser_cred(p->td_ucred, 0)
+#else
+ suser(p)
+#endif
+#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__)
+ }
+#endif
+#endif /* __Windows__ */
+ 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 (Panda).
+ */
+ 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__) && __FreeBSD_version >= 801000
+ 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__) && __FreeBSD_version >= 801000
+ 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(__Panda__) && !defined(__Userspace__)
+#if !defined(__FreeBSD__) || __FreeBSD_version < 500000
+ sctp_rtentry_t *rt;
+#endif
+#endif
+ int cnt;
+ sctp_sharedkey_t *shared_key, *nshared_key;
+
+
+#if defined(__APPLE__)
+ 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.
+ */
+ asoc->asoc.state &= ~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->asoc) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(&asoc->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;
+ asoc->asoc.state |= 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->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&asoc->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.locked_on_sending) {
+ goto abort_anyway;
+ }
+ if ((SCTP_GET_STATE(&asoc->asoc) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(&asoc->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->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(&asoc->asoc, SCTP_STATE_SHUTDOWN_SENT);
+ SCTP_CLEAR_SUBSTATE(&asoc->asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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,
+ asoc->asoc.primary_destination);
+ sctp_chunk_output(inp, asoc, SCTP_OUTPUT_FROM_SHUT_TMR, SCTP_SO_LOCKED);
+ }
+ } else {
+ /* mark into shutdown pending */
+ struct sctp_stream_queue_pending *sp;
+
+ asoc->asoc.state |= SCTP_STATE_SHUTDOWN_PENDING;
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, asoc->sctp_ep, asoc,
+ asoc->asoc.primary_destination);
+ if (asoc->asoc.locked_on_sending) {
+ sp = TAILQ_LAST(&((asoc->asoc.locked_on_sending)->outqueue),
+ sctp_streamhead);
+ if (sp == NULL) {
+ SCTP_PRINTF("Error, sp is NULL, locked on sending is %p strm:%d\n",
+ (void *)asoc->asoc.locked_on_sending,
+ asoc->asoc.locked_on_sending->stream_no);
+ } else {
+ if ((sp->length == 0) && (sp->msg_is_complete == 0))
+ asoc->asoc.state |= 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->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&asoc->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) {
+ asoc->asoc.state &= ~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->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->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&asoc->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 */
+ (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer);
+#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)) {
+ (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer);
+#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(__Panda__) || defined(__Windows__) || defined(__Userspace__))
+#if !defined(__FreeBSD__) || __FreeBSD_version < 500000
+ rt = ip_pcb->inp_route.ro_rt;
+#endif
+#endif
+
+#if defined(__Panda__)
+ if (inp->pak_to_read) {
+ (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.zero_copy_timer.timer);
+ SCTP_RELEASE_PKT(inp->pak_to_read);
+ inp->pak_to_read = NULL;
+ }
+ if (inp->pak_to_read_sendq) {
+ (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.zero_copy_sendq_timer.timer);
+ SCTP_RELEASE_PKT(inp->pak_to_read_sendq);
+ inp->pak_to_read_sendq = NULL;
+ }
+#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_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), sq);
+ SCTP_DECR_READQ_COUNT();
+ }
+ /* 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 (so) {
+#ifdef IPSEC
+ ipsec_delete_pcbpolicy(ip_pcb);
+#endif /* IPSEC */
+
+ /* Unlocks not needed since the socket is gone now */
+ }
+#ifndef __Panda__
+ if (ip_pcb->inp_options) {
+ (void)sctp_m_free(ip_pcb->inp_options);
+ ip_pcb->inp_options = 0;
+ }
+#endif
+
+#if !(defined(__Panda__) || defined(__Windows__) || defined(__Userspace__))
+#if !defined(__FreeBSD__) || __FreeBSD_version < 500000
+ if (rt) {
+ RTFREE(rt);
+ ip_pcb->inp_route.ro_rt = 0;
+ }
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version < 803000
+#ifdef INET
+ if (ip_pcb->inp_moptions) {
+ inp_freemoptions(ip_pcb->inp_moptions);
+ ip_pcb->inp_moptions = 0;
+ }
+#endif
+#endif
+#endif
+
+#ifdef INET6
+#if !(defined(__Panda__) || defined(__Windows__) || defined(__Userspace__))
+#if defined(__FreeBSD__) || defined(__APPLE__)
+ if (ip_pcb->inp_vflag & INP_IPV6) {
+#else
+ if (inp->inp_vflag & INP_IPV6) {
+#endif
+ struct in6pcb *in6p;
+
+ in6p = (struct in6pcb *)inp;
+ ip6_freepcbopts(in6p->in6p_outputopts);
+ }
+#endif
+#endif /* INET6 */
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+ inp->inp_vflag = 0;
+#else
+ ip_pcb->inp_vflag = 0;
+#endif
+ /* 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__)
+ 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__)
+ 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__)
+ 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)
+{
+#ifdef __Panda__
+ return (0);
+#else
+ 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);
+ }
+#endif
+}
+
+/*
+ * 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, 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 bzero 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) {
+#ifdef SCTP_DONT_DO_PRIVADDR_SCOPE
+ stcb->asoc.scope.ipv4_local_scope = 1;
+#else
+ if (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) {
+ stcb->asoc.scope.ipv4_local_scope = 1;
+ }
+#endif /* SCTP_DONT_DO_PRIVADDR_SCOPE */
+ } 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();
+ bzero(net, 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 = stcb->asoc.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__)
+#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);
+
+#if defined(__Userspace__)
+ net->src_addr_selected = 0;
+#else
+ 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 (net->ro._s_addr != NULL) {
+ net->src_addr_selected = 1;
+ /* Now get the interface MTU */
+ if (net->ro._s_addr->ifn_p != NULL) {
+ net->mtu = SCTP_GATHER_MTU_FROM_INTFC(net->ro._s_addr->ifn_p);
+ }
+ } else {
+ net->src_addr_selected = 0;
+ }
+ if (net->mtu > 0) {
+ uint32_t rmtu;
+
+ rmtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, net->ro.ro_rt);
+ 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);
+ } else {
+ /* we take the route mtu over the interface, since
+ * the route may be leading out the loopback, or
+ * a different interface.
+ */
+ net->mtu = rmtu;
+ }
+ }
+ } else {
+ net->src_addr_selected = 0;
+ }
+#endif
+ if (net->mtu == 0) {
+ 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) {
+ stcb->asoc.smallest_mtu = 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__)
+ /* Choose an initial flowid. */
+ net->flowid = stcb->asoc.my_vtag ^
+ ntohs(stcb->rport) ^
+ ntohs(stcb->sctp_ep->sctp_lport);
+ net->flowtype = M_HASHTYPE_OPAQUE;
+#endif
+ if (netp) {
+ *netp = net;
+ }
+ netfirst = TAILQ_FIRST(&stcb->asoc.nets);
+ if (net->ro.ro_rt == NULL) {
+ /* 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);
+ } else if (netfirst->ro.ro_rt == NULL) {
+ /*
+ * First one has NO route. Place this one ahead of the first
+ * one.
+ */
+ TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next);
+#ifndef __Panda__
+ } else if (net->ro.ro_rt->rt_ifp != netfirst->ro.ro_rt->rt_ifp) {
+ /*
+ * 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);
+#endif
+ } 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;
+ } else if (netlook->ro.ro_rt == NULL) {
+ /* next one has NO route */
+ TAILQ_INSERT_BEFORE(netfirst, net, sctp_next);
+ break;
+ }
+#ifndef __Panda__
+ else if (netlook->ro.ro_rt->rt_ifp != net->ro.ro_rt->rt_ifp)
+#else
+ else
+#endif
+ {
+ TAILQ_INSERT_AFTER(&stcb->asoc.nets, netlook,
+ net, sctp_next);
+ break;
+ }
+#ifndef __Panda__
+ /* Shift forward */
+ netfirst = netlook;
+#endif
+ } while (netlook != NULL);
+ }
+
+ /* got to have a primary set */
+ if (stcb->asoc.primary_destination == 0) {
+ stcb->asoc.primary_destination = net;
+ } else if ((stcb->asoc.primary_destination->ro.ro_rt == NULL) &&
+ (net->ro.ro_rt) &&
+ ((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;
+
+ SCTP_INP_WLOCK(inp);
+ try_again:
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ /* TSNH */
+ SCTP_INP_WUNLOCK(inp);
+ 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;
+ SCTP_INP_WUNLOCK(inp);
+ 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,
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ struct thread *p
+#elif defined(__Windows__)
+ PKTHREAD p
+#else
+#if defined(__Userspace__)
+ /* __Userspace__ NULL proc is going to be passed here. See sctp_lower_sosend */
+#endif
+ struct proc *p
+#endif
+)
+{
+ /* 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))) {
+ /* 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)) {
+ /* 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)) {
+ /* 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,
+ (struct sockaddr *)NULL,
+ (struct sctp_ifa *)NULL,
+#ifndef __Panda__
+ p
+#else
+ (struct proc *)NULL
+#endif
+ ))) {
+ /* 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();
+
+ bzero(stcb, sizeof(*stcb));
+ asoc = &stcb->asoc;
+
+ asoc->assoc_id = sctp_aloc_a_assoc_id(inp, stcb);
+ 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))) {
+ /* failed */
+ 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_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);
+ LIST_REMOVE(stcb, sctp_tcbasocidhash);
+ 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);
+
+ /* 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, 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->delayed_event_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);
+ }
+ 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_association *asoc;
+
+ 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_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) {
+#ifdef INVARIANTS
+ panic("Can not alloc tagblock");
+#endif
+ 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;
+ }
+}
+
+
+#ifdef __Panda__
+void panda_wakeup_socket(struct socket *so);
+#endif
+
+/*-
+ * 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__)
+ 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__) /* TEMP: moved to below */
+ /* 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 */
+ (void)SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer);
+ asoc->dack_timer.self = NULL;
+ (void)SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer);
+ /*-
+ * For stream reset we don't blast this unless
+ * it is a str-reset timer, it might be the
+ * free-asoc timer which we DON'T want to
+ * disturb.
+ */
+ if (asoc->strreset_timer.type == SCTP_TIMER_TYPE_STRRESET)
+ asoc->strreset_timer.self = NULL;
+ (void)SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer);
+ asoc->asconf_timer.self = NULL;
+ (void)SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer);
+ asoc->autoclose_timer.self = NULL;
+ (void)SCTP_OS_TIMER_STOP(&asoc->shut_guard_timer.timer);
+ asoc->shut_guard_timer.self = NULL;
+ (void)SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer);
+ asoc->delayed_event_timer.self = NULL;
+ /* Mobility adaptation */
+ (void)SCTP_OS_TIMER_STOP(&asoc->delete_prim_timer.timer);
+ asoc->delete_prim_timer.self = NULL;
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ (void)SCTP_OS_TIMER_STOP(&net->rxt_timer.timer);
+ net->rxt_timer.self = NULL;
+ (void)SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer);
+ net->pmtu_timer.self = NULL;
+ (void)SCTP_OS_TIMER_STOP(&net->hb_timer.timer);
+ net->hb_timer.self = NULL;
+ }
+ /* Now the read queue needs to be cleaned up (only once) */
+ if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0) {
+ stcb->asoc.state |= 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->sinfo_ssn;
+ 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)) {
+ stcb->asoc.state &= ~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) {
+ SOCK_LOCK(so);
+ if (so->so_rcv.sb_cc == 0) {
+ so->so_state &= ~(SS_ISCONNECTING |
+ SS_ISDISCONNECTING |
+ SS_ISCONFIRMING |
+ SS_ISCONNECTED);
+ }
+#if defined(__APPLE__)
+ socantrcvmore(so);
+#else
+ socantrcvmore_locked(so);
+#endif
+ 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) {
+ stcb->asoc.state &= ~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!
+ */
+ (void)SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->shut_guard_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer);
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ (void)SCTP_OS_TIMER_STOP(&net->rxt_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&net->hb_timer.timer);
+ }
+
+ asoc->strreset_timer.type = SCTP_TIMER_TYPE_NONE;
+ /*
+ * The chunk lists and such SHOULD be empty but we check them just
+ * in case.
+ */
+ /* anything on the wheel needs to be removed */
+ for (i = 0; i < asoc->streamoutcnt; i++) {
+ struct sctp_stream_out *outs;
+
+ outs = &asoc->strmout[i];
+ /* now clean up any chunks here */
+ TAILQ_FOREACH_SAFE(sp, &outs->outqueue, next, nsp) {
+ TAILQ_REMOVE(&outs->outqueue, sp, next);
+ sctp_free_spbufspace(stcb, asoc, sp);
+ if (sp->data) {
+ if (so) {
+ /* Still an open socket - report */
+ sctp_ulp_notify(SCTP_NOTIFY_SPECIAL_SP_FAIL, stcb,
+ 0, (void *)sp, SCTP_SO_LOCKED);
+ }
+ if (sp->data) {
+ sctp_m_freem(sp->data);
+ sp->data = NULL;
+ sp->tail_mbuf = NULL;
+ sp->length = 0;
+ }
+ }
+ if (sp->net) {
+ sctp_free_remote_addr(sp->net);
+ sp->net = NULL;
+ }
+ sctp_free_a_strmoq(stcb, sp, SCTP_SO_LOCKED);
+ }
+ }
+ /*sa_ignore FREED_MEMORY*/
+ TAILQ_FOREACH_SAFE(strrst, &asoc->resetHead, next_resp, nstrrst) {
+ TAILQ_REMOVE(&asoc->resetHead, strrst, next_resp);
+ SCTP_FREE(strrst, SCTP_M_STRESET);
+ }
+ TAILQ_FOREACH_SAFE(sq, &asoc->pending_reply_queue, next, nsq) {
+ TAILQ_REMOVE(&asoc->pending_reply_queue, sq, next);
+ if (sq->data) {
+ sctp_m_freem(sq->data);
+ sq->data = NULL;
+ }
+ sctp_free_remote_addr(sq->whoFrom);
+ sq->whoFrom = NULL;
+ sq->stcb = NULL;
+ /* Free the ctl entry */
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), sq);
+ SCTP_DECR_READQ_COUNT();
+ /*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.stream_number].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.stream_number].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.stream_number);
+#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.stream_number].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.stream_number].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.stream_number);
+#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*/
+ }
+ TAILQ_FOREACH_SAFE(chk, &asoc->reasmqueue, sctp_next, nchk) {
+ TAILQ_REMOVE(&asoc->reasmqueue, 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) {
+ struct sctp_queued_to_read *ctl, *nctl;
+
+ for (i = 0; i < asoc->streamincnt; i++) {
+ TAILQ_FOREACH_SAFE(ctl, &asoc->strmin[i].inqueue, next, nctl) {
+ TAILQ_REMOVE(&asoc->strmin[i].inqueue, ctl, next);
+ sctp_free_remote_addr(ctl->whoFrom);
+ if (ctl->data) {
+ sctp_m_freem(ctl->data);
+ ctl->data = NULL;
+ }
+ /*
+ * We don't free the address here
+ * since all the net's were freed
+ * above.
+ */
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), ctl);
+ SCTP_DECR_READQ_COUNT();
+ }
+ }
+ 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__) /* 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);
+ goto out_of;
+ } else {
+ /* The socket is still open. */
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ if (from_inpcbfree == SCTP_NORMAL_PROC) {
+ SCTP_INP_RUNLOCK(inp);
+ }
+ out_of:
+ /* 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:
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+ answer = inp->inp_vflag & INP_IPV6;
+#else
+ answer = inp->ip_inp.inp.inp_vflag & INP_IPV6;
+#endif
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+ answer = inp->inp_vflag & INP_IPV4;
+#else
+ answer = inp->ip_inp.inp.inp_vflag & INP_IPV4;
+#endif
+ 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 */
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+ inp->inp_vflag = 0;
+#else
+ inp->ip_inp.inp.inp_vflag = 0;
+#endif
+ /* 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",
+ __FUNCTION__);
+ continue;
+ }
+
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
+ continue;
+ }
+ switch (laddr->ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+ inp->inp_vflag |= INP_IPV6;
+#else
+ inp->ip_inp.inp.inp_vflag |= INP_IPV6;
+#endif
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+ inp->inp_vflag |= INP_IPV4;
+#else
+ inp->ip_inp.inp.inp_vflag |= INP_IPV4;
+#endif
+ 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;
+ 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:
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+ inp->inp_vflag |= INP_IPV6;
+#else
+ inp->ip_inp.inp.inp_vflag |= INP_IPV6;
+#endif
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+ inp->inp_vflag |= INP_IPV4;
+#else
+ inp->ip_inp.inp.inp_vflag |= INP_IPV4;
+#endif
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ inp->ip_inp.inp.inp_vflag |= INP_CONN;
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ 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 &&
+ (net->ro._s_addr->ifa == laddr->ifa)) {
+ /* Yep, purge src address selected */
+ sctp_rtentry_t *rt;
+
+ /* delete this address if cached */
+ rt = net->ro.ro_rt;
+ if (rt != NULL) {
+ RTFREE(rt);
+ net->ro.ro_rt = NULL;
+ }
+ 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;
+}
+
+/*
+ * insert an laddr entry with the given ifa for the desired list
+ */
+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();
+ bzero(laddr, 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)
+ */
+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();
+}
+
+/*
+ * 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__)
+/*
+ * Temporarily remove for __APPLE__ until we use the Tiger equivalents
+ */
+/* 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 /* FreeBSD || APPLE */
+
+
+
+#if defined(__FreeBSD__) && 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;
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ qent->vn = curvnet;
+#endif
+ 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);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_SET(qent->vn);
+#endif
+ 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);
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ 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) {
+#if __FreeBSD_version <= 701000
+ (void)kthread_create(sctp_mcore_thread,
+ (void *)&sctp_mcore_workers[cpu],
+ &sctp_mcore_workers[cpu].thread_proc,
+ RFPROC,
+ SCTP_KTHREAD_PAGES,
+ SCTP_MCORE_NAME);
+
+#else
+ (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__) && __FreeBSD_cc_version >= 1200000
+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;
+ m->m_flags |= M_FLOWID;
+ return (m);
+}
+#endif
+
+void
+sctp_pcb_init()
+{
+ /*
+ * SCTP initialization for the PCB structures should be called by
+ * the sctp_init() funciton.
+ */
+ 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_LOCAL_TRACE_BUF)
+#if defined(__Windows__)
+ if (SCTP_BASE_SYSCTL(sctp_log) != NULL) {
+ bzero(SCTP_BASE_SYSCTL(sctp_log), sizeof(struct sctp_log));
+ }
+#else
+ bzero(&SCTP_BASE_SYSCTL(sctp_log), sizeof(struct sctp_log));
+#endif
+#endif
+#if defined(__FreeBSD__) && defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ SCTP_MALLOC(SCTP_BASE_STATS, struct sctpstat *,
+ ((mp_maxid+1) * sizeof(struct sctpstat)),
+ SCTP_M_MCORE);
+#endif
+ (void)SCTP_GETTIME_TIMEVAL(&tv);
+#if defined(__FreeBSD__) && defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ bzero(SCTP_BASE_STATS, (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
+ bzero(&SCTP_BASE_STATS, 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__)
+ 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__)
+#if defined(__FreeBSD_cc_version) && __FreeBSD_cc_version >= 440000
+ 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));
+#else
+ TUNABLE_INT_FETCH("net.inet.sctp.tcbhashsize", SCTP_TCBHASHSIZE,
+ SCTP_BASE_SYSCTL(sctp_hashtblsize));
+ TUNABLE_INT_FETCH("net.inet.sctp.pcbhashsize", SCTP_PCBHASHSIZE,
+ SCTP_BASE_SYSCTL(sctp_pcbtblsize));
+ TUNABLE_INT_FETCH("net.inet.sctp.chunkscale", SCTP_CHUNKQUEUE_SCALE,
+ SCTP_BASE_SYSCTL(sctp_chunkscale));
+#endif
+#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(__Userspace_os_Windows)
+ 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(SCTP_MCORE_INPUT) && defined(SMP)
+ sctp_startup_mcore_threads();
+#endif
+
+#ifndef __Panda__
+ /*
+ * 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);
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_cc_version >= 1200000
+ 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_init(NULL);
+ atomic_init();
+#if defined(INET) || defined(INET6)
+ 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 !defined(__FreeBSD__)
+ /* 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__)
+#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(__Windows__)
+ 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_it_ctl.thread_proc) {
+#if defined(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ DeleteConditionVariable(&sctp_it_ctl.iterator_wakeup);
+#else
+ pthread_cond_destroy(&sctp_it_ctl.iterator_wakeup);
+#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.
+ */
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+ TAILQ_FOREACH_SAFE(it, &sctp_it_ctl.iteratorhead, sctp_nxt_itr, nit) {
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ 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__) && __FreeBSD_version >= 801000
+ 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__)
+ SCTP_IPI_ITERATOR_WQ_DESTROY();
+ SCTP_ITERATOR_LOCK_DESTROY();
+#endif
+ SCTP_OS_TIMER_STOP(&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));
+#if defined(__Userspace__) && !defined(__Userspace_os_Windows)
+ /* free memory allocated by getifaddrs call */
+#if defined(INET) || defined(INET6)
+ freeifaddrs(g_interfaces);
+#endif
+#endif
+
+ /* 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__)
+ SCTP_TIMERQ_LOCK_DESTROY();
+#endif
+#ifdef SCTP_PACKET_LOGGING
+ SCTP_IP_PKTLOG_DESTROY();
+#endif
+ SCTP_IPI_ADDR_DESTROY();
+#if defined(__APPLE__)
+ SCTP_IPI_COUNT_DESTROY();
+#endif
+ SCTP_STATLOG_DESTROY();
+ SCTP_INP_INFO_LOCK_DESTROY();
+
+ SCTP_WQ_ADDR_DESTROY();
+
+#if defined(__APPLE__)
+#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
+#if defined(__Windows__) || 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
+ /* Get rid of other stuff to */
+ 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(__FreeBSD__) && defined(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ SCTP_FREE(SCTP_BASE_STATS, SCTP_M_MCORE);
+#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)
+{
+ /*
+ * 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, parm_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;
+#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_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, 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, 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, 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, &parm_buf, sizeof(parm_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 == 0) {
+ 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, 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->asoc) & SCTP_STATE_COOKIE_WAIT) {
+ /* in setup state we abort this guy */
+ sctp_abort_an_association(stcb_tmp->sctp_ep,
+ stcb_tmp, NULL, 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, 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->asoc) & SCTP_STATE_COOKIE_WAIT) {
+ /* in setup state we abort this guy */
+ sctp_abort_an_association(stcb_tmp->sctp_ep,
+ stcb_tmp, NULL, 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);
+ }
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&lstore,
+ min(plen,sizeof(lstore)));
+ 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;
+
+ phdr = sctp_get_next_param(m, offset,
+ (struct sctp_paramhdr *)&local_store, min(sizeof(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;
+ 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,
+ min(sizeof(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,
+ min(plen,sizeof(hmacs_store)));
+ 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,
+ min(plen,sizeof(chunks_store)));
+ 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, &parm_buf,
+ sizeof(parm_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.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;
+ bcopy(p_random, new_key->key, keylen);
+ }
+ /* append in the AUTH chunks */
+ if (chunks != NULL) {
+ bcopy(chunks, new_key->key + keylen,
+ sizeof(*chunks) + num_chunks);
+ keylen += sizeof(*chunks) + num_chunks;
+ }
+ /* append in the HMACs */
+ if (hmacs != NULL) {
+ bcopy(hmacs, new_key->key + keylen,
+ 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 *ctl, *nctl;
+ 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;
+ /* First look in the re-assembly queue */
+ TAILQ_FOREACH_SAFE(chk, &asoc->reasmqueue, sctp_next, nchk) {
+ if (SCTP_TSN_GT(chk->rec.data.TSN_seq, cumulative_tsn_p1)) {
+ /* Yep it is above cum-ack */
+ cnt++;
+ SCTP_CALC_TSN_TO_GAP(gap, chk->rec.data.TSN_seq, asoc->mapping_array_base_tsn);
+ asoc->size_on_reasm_queue = sctp_sbspace_sub(asoc->size_on_reasm_queue, chk->send_size);
+ sctp_ucount_decr(asoc->cnt_on_reasm_queue);
+ SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
+ TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next);
+ if (chk->data) {
+ sctp_m_freem(chk->data);
+ chk->data = NULL;
+ }
+ sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
+ }
+ }
+ /* Ok that was fun, now we will drain all the inbound streams? */
+ for (strmat = 0; strmat < asoc->streamincnt; strmat++) {
+ TAILQ_FOREACH_SAFE(ctl, &asoc->strmin[strmat].inqueue, next, nctl) {
+ if (SCTP_TSN_GT(ctl->sinfo_tsn, cumulative_tsn_p1)) {
+ /* Yep it is above cum-ack */
+ cnt++;
+ SCTP_CALC_TSN_TO_GAP(gap, ctl->sinfo_tsn, asoc->mapping_array_base_tsn);
+ asoc->size_on_all_streams = sctp_sbspace_sub(asoc->size_on_all_streams, ctl->length);
+ sctp_ucount_decr(asoc->cnt_on_all_streams);
+ SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
+ TAILQ_REMOVE(&asoc->strmin[strmat].inqueue, ctl, next);
+ if (ctl->data) {
+ sctp_m_freem(ctl->data);
+ ctl->data = NULL;
+ }
+ sctp_free_remote_addr(ctl->whoFrom);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), ctl);
+ SCTP_DECR_READQ_COUNT();
+ }
+ }
+ }
+ 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;
+ (void)SCTP_OS_TIMER_STOP(&stcb->asoc.dack_timer.timer);
+ /*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__) && __FreeBSD_version >= 801000
+ 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__) && __FreeBSD_version >= 801000
+ VNET_LIST_RLOCK_NOSLEEP();
+ VNET_FOREACH(vnet_iter) {
+ CURVNET_SET(vnet_iter);
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+#endif
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ 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__) && __FreeBSD_version >= 801000
+ 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);
+ }
+ 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 (ENOMEM);
+ }
+ 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__) && __FreeBSD_version >= 801000
+ 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();
+
+ 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..cab584bb47
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_pcb.h
@@ -0,0 +1,880 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_pcb.h 279859 2015-03-10 19:49:25Z 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 refering 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__)
+#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__)
+ 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__) && __FreeBSD_version >= 503000
+#if __FreeBSD_version <= 602000
+ struct mtx ipi_ep_mtx;
+#else
+ struct rwlock ipi_ep_mtx;
+#endif
+ struct mtx ipi_iterator_wq_mtx;
+#if __FreeBSD_version <= 602000
+ struct mtx ipi_addr_mtx;
+#else
+ struct rwlock ipi_addr_mtx;
+#endif
+ 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__)
+#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(__Windows__)
+ struct rwlock ipi_ep_lock;
+ struct rwlock ipi_addr_lock;
+ struct spinlock ipi_pktlog_mtx;
+ struct rwlock wq_addr_mtx;
+#elif defined(__Userspace__)
+ /* TODO decide on __Userspace__ locks */
+#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(SMP) && defined(SCTP_USE_PERCPU_STAT)
+ struct sctpstat *sctpstat;
+#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(__APPLE__)
+ int sctp_main_timer_ticks;
+#endif
+#if defined(__Userspace__)
+ userland_mutex_t timer_mtx;
+ userland_thread_t timer_thread;
+ uint8_t timer_thread_should_exit;
+#if !defined(__Userspace_os_Windows)
+#if defined(INET) || defined(INET6)
+ int userspace_route;
+ userland_thread_t recvthreadroute;
+#endif
+#endif
+#ifdef INET
+ int userspace_rawsctp;
+ int userspace_udpsctp;
+ userland_thread_t recvthreadraw;
+ userland_thread_t recvthreadudp;
+#endif
+#ifdef INET6
+ int userspace_rawsctp6;
+ int userspace_udpsctp6;
+ 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, ...);
+#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;
+
+ unsigned int sctp_timeoutticks[SCTP_NUM_TMRS];
+ unsigned int sctp_minrto;
+ unsigned int sctp_maxrto;
+ unsigned int initial_rto;
+ int 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;
+
+ /* 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;
+
+ /* Zero copy full buffer timer */
+ struct sctp_timer zero_copy_timer;
+ /* Zero copy app to transport (sendq) read repulse timer */
+ struct sctp_timer zero_copy_sendq_timer;
+ uint32_t def_cookie_life;
+ /* defaults to 0 */
+ int 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 compatability with all the other stuff.
+ */
+ union {
+ struct inpcb inp;
+ char align[(sizeof(struct in6pcb) + SCTP_ALIGNM1) &
+ ~SCTP_ALIGNM1];
+ } ip_inp;
+
+#if defined(__APPLE__)
+ /* 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 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.
+ */
+#ifdef __Panda__
+ pakhandle_type pak_to_read;
+ pakhandle_type pak_to_read_sendq;
+#endif
+ struct mbuf *pkt, *pkt_last;
+ struct mbuf *control;
+#if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__))
+#ifndef INP_IPV6
+#define INP_IPV6 0x1
+#endif
+#ifndef INP_IPV4
+#define INP_IPV4 0x2
+#endif
+ uint8_t inp_vflag;
+ /* TODO __Userspace__ where is our inp_vlag going to be? */
+ uint8_t inp_ip_ttl;
+ uint8_t inp_ip_tos; /* defined as macro in user_inpcb.h */
+ uint8_t inp_ip_resv;
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version >= 503000
+ 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__)
+#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(__Windows__)
+ struct rwlock inp_lock;
+ struct spinlock inp_create_lock;
+ struct spinlock inp_rdata_lock;
+ int32_t refcount;
+#elif defined(__Userspace__)
+ /* TODO decide on __Userspace__ locks */
+ int32_t refcount;
+#endif
+#if defined(__APPLE__)
+ 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;
+#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 *);
+
+#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__) && __FreeBSD_version >= 503000
+ 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__)
+ lck_mtx_t* tcb_mtx;
+ lck_mtx_t* tcb_send_mtx;
+#elif defined(__Windows__)
+ struct spinlock tcb_lock;
+ struct spinlock tcb_send_lock;
+#elif defined(__Userspace__)
+ /* TODO decide on __Userspace__ locks */
+#endif
+#if defined(__APPLE__)
+ uint32_t caller1;
+ uint32_t caller2;
+ uint32_t caller3;
+#endif
+};
+
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 503000
+
+#include <netinet/sctp_lock_bsd.h>
+
+#elif defined(__APPLE__)
+/*
+ * 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(__Windows__)
+
+#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
+
+/* TODO where to put non-_KERNEL things for __Userspace__? */
+#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__) && __FreeBSD_version >= 801000
+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__) && __FreeBSD_version >= 500000
+int sctp_inpcb_bind(struct socket *, struct sockaddr *,
+ struct sctp_ifa *,struct thread *);
+#elif defined(__Windows__)
+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);
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+struct sctp_tcb *
+sctp_aloc_assoc(struct sctp_inpcb *, struct sockaddr *,
+ int *, uint32_t, uint32_t, struct thread *);
+#elif defined(__Windows__)
+struct sctp_tcb *
+sctp_aloc_assoc(struct sctp_inpcb *, struct sockaddr *,
+ int *, uint32_t, uint32_t, PKTHREAD);
+#else
+/* proc will be NULL for __Userspace__ */
+struct sctp_tcb *
+sctp_aloc_assoc(struct sctp_inpcb *, struct sockaddr *,
+ int *, uint32_t, uint32_t, struct proc *);
+#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);
+
+int sctp_insert_laddr(struct sctpladdr *, struct sctp_ifa *, uint32_t);
+
+void sctp_remove_laddr(struct sctp_laddr *);
+
+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 **, int, int);
+
+void sctp_remove_net(struct sctp_tcb *, struct sctp_nets *);
+
+int sctp_del_remote_addr(struct sctp_tcb *, struct sockaddr *);
+
+void sctp_pcb_init(void);
+
+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 *);
+
+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 *);
+
+
+int sctp_destination_is_reachable(struct sctp_tcb *, struct sockaddr *);
+
+int sctp_swap_inpcb_for_listen(struct sctp_inpcb *inp);
+
+/*-
+ * 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(SCTP_MCORE_INPUT) && defined(SMP)
+void
+sctp_queue_to_mcore(struct mbuf *m, int off, int cpu_to_use);
+
+#endif
+
+#ifdef INVARIANTS
+void
+sctp_validate_no_locks(struct sctp_inpcb *inp);
+#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..7f21f86f98
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_peeloff.c
@@ -0,0 +1,326 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_peeloff.c 279859 2015-03-10 19:49:25Z 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>
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 5
+#endif
+
+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->asoc));
+ 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->asoc));
+ 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__)
+ 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)
+{
+#if defined(__Userspace__)
+ /* if __Userspace__ chooses to originally not support peeloff, put it here... */
+#endif
+#if defined(__Panda__)
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+#else
+ 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__) && __FreeBSD_version >= 801000
+ CURVNET_SET(head->so_vnet);
+#endif
+ newso = sonewconn(head, SS_ISCONNECTED
+#if defined(__APPLE__)
+ , NULL
+#elif defined(__Panda__)
+ /* place this socket in the assoc's vrf id */
+ , NULL, stcb->asoc.vrf_id
+#endif
+ );
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ 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__)
+ 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. */
+ SCTP_CLEAR_SO_NBIO(newso);
+ newso->so_state |= SS_ISCONNECTED;
+ /* We remove it right away */
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+#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);
+#else
+ newso = TAILQ_FIRST(&head->so_q);
+ if (soqremque(newso, 1) == 0) {
+ SCTP_PRINTF("soremque failed, peeloff-fails (invarients would panic)\n");
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PEELOFF, ENOTCONN);
+ *error = ENOTCONN;
+ return (NULL);
+
+ }
+#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);
+ /*
+ * 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__)
+ 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
+}
+#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..69f48065cd
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_peeloff.h
@@ -0,0 +1,68 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_peeloff.h 243516 2012-11-25 14:25:08Z 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(__Windows__)
+ int s;
+#else
+ HANDLE s;
+#endif
+ sctp_assoc_t assoc_id;
+#if !defined(__Windows__)
+ 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..1d109857a2
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_process_lock.h
@@ -0,0 +1,642 @@
+/*-
+ * 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(__Userspace_os_Windows)
+/* 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(_inep)
+#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(__Userspace_os_Windows)
+#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_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_ASOC_CREATE_LOCK_INIT(_inp) \
+ InitializeCriticalSection(&(_inp)->inp_create_mtx)
+
+#define SCTP_INP_LOCK_DESTROY(_inp) \
+ DeleteCriticalSection(&(_inp)->inp_mtx)
+
+#define SCTP_ASOC_CREATE_LOCK_DESTROY(_inp) \
+ DeleteCriticalSection(&(_inp)->inp_create_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, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_INP);\
+ EnterCriticalSection(&(_inp)->inp_mtx); \
+} while (0)
+
+#define SCTP_INP_WLOCK(_inp) do { \
+ sctp_log_lock(_inp, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_INP);\
+ EnterCriticalSection(&(_inp)->inp_mtx); \
+} while (0)
+#else
+
+#define SCTP_INP_RLOCK(_inp) do { \
+ EnterCriticalSection(&(_inp)->inp_mtx); \
+} while (0)
+
+#define SCTP_INP_WLOCK(_inp) do { \
+ EnterCriticalSection(&(_inp)->inp_mtx); \
+} while (0)
+#endif
+
+
+#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) do { \
+ EnterCriticalSection(&(_tcb)->tcb_send_mtx); \
+} while (0)
+
+#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)
+
+#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, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_CREATE); \
+ EnterCriticalSection(&(_inp)->inp_create_mtx); \
+} while (0)
+#else
+#define SCTP_ASOC_CREATE_LOCK(_inp) do { \
+ EnterCriticalSection(&(_inp)->inp_create_mtx); \
+} while (0)
+#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) do { \
+ EnterCriticalSection(&(_tcb)->tcb_mtx); \
+} while (0)
+#endif
+
+#define SCTP_TCB_TRYLOCK(_tcb) ((TryEnterCriticalSection(&(_tcb)->tcb_mtx)))
+
+#define SCTP_TCB_UNLOCK(_tcb) do { \
+ LeaveCriticalSection(&(_tcb)->tcb_mtx); \
+} while (0)
+
+#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), NULL)
+#define SCTP_WQ_ADDR_DESTROY() \
+ (void)pthread_mutex_destroy(&SCTP_BASE_INFO(wq_addr_mtx))
+#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))
+
+
+#define SCTP_INP_INFO_LOCK_INIT() \
+ (void)pthread_mutex_init(&SCTP_BASE_INFO(ipi_ep_mtx), NULL)
+#define SCTP_INP_INFO_LOCK_DESTROY() \
+ (void)pthread_mutex_destroy(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_RLOCK() \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_TRYLOCK() \
+ (!(pthread_mutex_trylock(&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))
+
+#define SCTP_IP_PKTLOG_INIT() \
+ (void)pthread_mutex_init(&SCTP_BASE_INFO(ipi_pktlog_mtx), NULL)
+#define SCTP_IP_PKTLOG_DESTROY() \
+ (void)pthread_mutex_destroy(&SCTP_BASE_INFO(ipi_pktlog_mtx))
+#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))
+
+
+
+/*
+ * 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, NULL)
+
+#define SCTP_INP_READ_DESTROY(_inp) \
+ (void)pthread_mutex_destroy(&(_inp)->inp_rdata_mtx)
+
+#define SCTP_INP_READ_LOCK(_inp) do { \
+ (void)pthread_mutex_lock(&(_inp)->inp_rdata_mtx); \
+} while (0)
+
+
+#define SCTP_INP_READ_UNLOCK(_inp) \
+ (void)pthread_mutex_unlock(&(_inp)->inp_rdata_mtx)
+
+#define SCTP_INP_LOCK_INIT(_inp) \
+ (void)pthread_mutex_init(&(_inp)->inp_mtx, NULL)
+
+#define SCTP_ASOC_CREATE_LOCK_INIT(_inp) \
+ (void)pthread_mutex_init(&(_inp)->inp_create_mtx, NULL)
+
+#define SCTP_INP_LOCK_DESTROY(_inp) \
+ (void)pthread_mutex_destroy(&(_inp)->inp_mtx)
+
+#define SCTP_ASOC_CREATE_LOCK_DESTROY(_inp) \
+ (void)pthread_mutex_destroy(&(_inp)->inp_create_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, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_INP);\
+ (void)pthread_mutex_lock(&(_inp)->inp_mtx); \
+} while (0)
+
+#define SCTP_INP_WLOCK(_inp) do { \
+ sctp_log_lock(_inp, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_INP);\
+ (void)pthread_mutex_lock(&(_inp)->inp_mtx); \
+} while (0)
+
+#else
+
+#define SCTP_INP_RLOCK(_inp) do { \
+ (void)pthread_mutex_lock(&(_inp)->inp_mtx); \
+} while (0)
+
+#define SCTP_INP_WLOCK(_inp) do { \
+ (void)pthread_mutex_lock(&(_inp)->inp_mtx); \
+} while (0)
+#endif
+
+
+#define SCTP_TCB_SEND_LOCK_INIT(_tcb) \
+ (void)pthread_mutex_init(&(_tcb)->tcb_send_mtx, NULL)
+
+#define SCTP_TCB_SEND_LOCK_DESTROY(_tcb) \
+ (void)pthread_mutex_destroy(&(_tcb)->tcb_send_mtx)
+
+#define SCTP_TCB_SEND_LOCK(_tcb) do { \
+ (void)pthread_mutex_lock(&(_tcb)->tcb_send_mtx); \
+} while (0)
+
+#define SCTP_TCB_SEND_UNLOCK(_tcb) \
+ (void)pthread_mutex_unlock(&(_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)
+
+#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, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_CREATE); \
+ (void)pthread_mutex_lock(&(_inp)->inp_create_mtx); \
+} while (0)
+#else
+#define SCTP_ASOC_CREATE_LOCK(_inp) do { \
+ (void)pthread_mutex_lock(&(_inp)->inp_create_mtx); \
+} while (0)
+#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)
+#define SCTP_ASOC_CREATE_UNLOCK(_inp) \
+ (void)pthread_mutex_unlock(&(_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) \
+ (void)pthread_mutex_init(&(_tcb)->tcb_mtx, NULL)
+
+#define SCTP_TCB_LOCK_DESTROY(_tcb) \
+ (void)pthread_mutex_destroy(&(_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); \
+ (void)pthread_mutex_lock(&(_tcb)->tcb_mtx); \
+} while (0)
+
+#else
+#define SCTP_TCB_LOCK(_tcb) do { \
+ (void)pthread_mutex_lock(&(_tcb)->tcb_mtx); \
+} while (0)
+#endif
+
+#define SCTP_TCB_TRYLOCK(_tcb) (!(pthread_mutex_trylock(&(_tcb)->tcb_mtx)))
+
+#define SCTP_TCB_UNLOCK(_tcb) (void)pthread_mutex_unlock(&(_tcb)->tcb_mtx)
+
+#define SCTP_TCB_LOCK_ASSERT(_tcb)
+#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(__Userspace__)
+#if defined(__Userspace_os_Windows)
+#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__))
+#define SOCKBUF_LOCK(_so_buf) pthread_mutex_lock(SOCKBUF_MTX(_so_buf))
+#define SOCKBUF_UNLOCK(_so_buf) pthread_mutex_unlock(SOCKBUF_MTX(_so_buf))
+#define SOCK_LOCK(_so) SOCKBUF_LOCK(&(_so)->so_rcv)
+#define SOCK_UNLOCK(_so) SOCKBUF_UNLOCK(&(_so)->so_rcv)
+#endif
+#else
+#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
+
+#define SCTP_STATLOG_INIT_LOCK()
+#define SCTP_STATLOG_LOCK()
+#define SCTP_STATLOG_UNLOCK()
+#define SCTP_STATLOG_DESTROY()
+
+#if defined(__Userspace_os_Windows)
+/* 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() \
+ do { \
+ EnterCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx)); \
+ } while (0)
+#define SCTP_IPI_ADDR_RUNLOCK() \
+ LeaveCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx))
+
+#define SCTP_IPI_ADDR_WLOCK() \
+ do { \
+ EnterCriticalSection(&SCTP_BASE_INFO(ipi_addr_mtx)); \
+ } while (0)
+#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() \
+ do { \
+ EnterCriticalSection(&sctp_it_ctl.it_mtx); \
+ } while (0)
+
+#define SCTP_ITERATOR_UNLOCK() \
+ LeaveCriticalSection(&sctp_it_ctl.it_mtx)
+
+#define SCTP_ITERATOR_LOCK_DESTROY() \
+ DeleteCriticalSection(&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() \
+ do { \
+ EnterCriticalSection(&sctp_it_ctl.ipi_iterator_wq_mtx); \
+ } while (0)
+
+#define SCTP_IPI_ITERATOR_WQ_UNLOCK() \
+ LeaveCriticalSection(&sctp_it_ctl.ipi_iterator_wq_mtx)
+
+#else /* end of __Userspace_os_Windows */
+/* address list locks */
+#define SCTP_IPI_ADDR_INIT() \
+ (void)pthread_mutex_init(&SCTP_BASE_INFO(ipi_addr_mtx), NULL)
+#define SCTP_IPI_ADDR_DESTROY() \
+ (void)pthread_mutex_destroy(&SCTP_BASE_INFO(ipi_addr_mtx))
+
+#define SCTP_IPI_ADDR_RLOCK() \
+ do { \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(ipi_addr_mtx)); \
+ } while (0)
+#define SCTP_IPI_ADDR_RUNLOCK() \
+ (void)pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_addr_mtx))
+
+#define SCTP_IPI_ADDR_WLOCK() \
+ do { \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(ipi_addr_mtx)); \
+ } while (0)
+#define SCTP_IPI_ADDR_WUNLOCK() \
+ (void)pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_addr_mtx))
+
+
+/* iterator locks */
+#define SCTP_ITERATOR_LOCK_INIT() \
+ (void)pthread_mutex_init(&sctp_it_ctl.it_mtx, NULL)
+
+#define SCTP_ITERATOR_LOCK() \
+ do { \
+ (void)pthread_mutex_lock(&sctp_it_ctl.it_mtx); \
+ } while (0)
+
+#define SCTP_ITERATOR_UNLOCK() \
+ (void)pthread_mutex_unlock(&sctp_it_ctl.it_mtx)
+
+#define SCTP_ITERATOR_LOCK_DESTROY() \
+ (void)pthread_mutex_destroy(&sctp_it_ctl.it_mtx)
+
+
+#define SCTP_IPI_ITERATOR_WQ_INIT() \
+ (void)pthread_mutex_init(&sctp_it_ctl.ipi_iterator_wq_mtx, NULL)
+
+#define SCTP_IPI_ITERATOR_WQ_DESTROY() \
+ (void)pthread_mutex_destroy(&sctp_it_ctl.ipi_iterator_wq_mtx)
+
+#define SCTP_IPI_ITERATOR_WQ_LOCK() \
+ do { \
+ (void)pthread_mutex_lock(&sctp_it_ctl.ipi_iterator_wq_mtx); \
+ } while (0)
+
+#define SCTP_IPI_ITERATOR_WQ_UNLOCK() \
+ (void)pthread_mutex_unlock(&sctp_it_ctl.ipi_iterator_wq_mtx)
+#endif
+
+#define SCTP_INCR_EP_COUNT() \
+ do { \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_ep), 1); \
+ } while (0)
+
+#define SCTP_DECR_EP_COUNT() \
+ do { \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_ep), 1); \
+ } while (0)
+
+#define SCTP_INCR_ASOC_COUNT() \
+ do { \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_asoc), 1); \
+ } while (0)
+
+#define SCTP_DECR_ASOC_COUNT() \
+ do { \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_asoc), 1); \
+ } while (0)
+
+#define SCTP_INCR_LADDR_COUNT() \
+ do { \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_laddr), 1); \
+ } while (0)
+
+#define SCTP_DECR_LADDR_COUNT() \
+ do { \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_laddr), 1); \
+ } while (0)
+
+#define SCTP_INCR_RADDR_COUNT() \
+ do { \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_raddr), 1); \
+ } while (0)
+
+#define SCTP_DECR_RADDR_COUNT() \
+ do { \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_raddr), 1); \
+ } while (0)
+
+#define SCTP_INCR_CHK_COUNT() \
+ do { \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_chunk), 1); \
+ } while (0)
+
+#define SCTP_DECR_CHK_COUNT() \
+ do { \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_chunk), 1); \
+ } while (0)
+
+#define SCTP_INCR_READQ_COUNT() \
+ do { \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_readq), 1); \
+ } while (0)
+
+#define SCTP_DECR_READQ_COUNT() \
+ do { \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_readq), 1); \
+ } while (0)
+
+#define SCTP_INCR_STRMOQ_COUNT() \
+ do { \
+ atomic_add_int(&SCTP_BASE_INFO(ipi_count_strmoq), 1); \
+ } while (0)
+
+#define SCTP_DECR_STRMOQ_COUNT() \
+ do { \
+ atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_strmoq), 1); \
+ } while (0)
+
+#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..c86517f91c
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sha1.c
@@ -0,0 +1,327 @@
+/*-
+ * 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(__Userspace_os_Windows)
+#include <winsock2.h>
+#elif !defined(__Windows__)
+#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..01e3c2edc4
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sha1.h
@@ -0,0 +1,97 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#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)
+#if defined(__Userspace_os_Darwin)
+/* The NSS sources require __APPLE__ to be defined.
+ * XXX: Remove this ugly hack once the platform defines have been cleaned up.
+ */
+#define __APPLE__
+#endif
+#include <pk11pub.h>
+#if defined(__Userspace_os_Darwin)
+#undef __APPLE__
+#endif
+#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(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..d6ccd933a9
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_ss_functions.c
@@ -0,0 +1,999 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_ss_functions.c 235828 2012-05-23 11:26:28Z 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;
+
+ 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, holds_lock);
+ }
+ 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 = TAILQ_FIRST(&asoc->ss_data.out_wheel);
+ TAILQ_REMOVE(&asoc->ss_data.out_wheel, TAILQ_FIRST(&asoc->ss_data.out_wheel), ss_params.rr.next_spoke);
+ strq->ss_params.rr.next_spoke.tqe_next = NULL;
+ strq->ss_params.rr.next_spoke.tqe_prev = NULL;
+ }
+ asoc->last_out_stream = NULL;
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static void
+sctp_ss_default_init_stream(struct sctp_stream_out *strq, struct sctp_stream_out *with_strq SCTP_UNUSED)
+{
+ 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->last_out_stream == strq) {
+ asoc->last_out_stream = TAILQ_PREV(asoc->last_out_stream,
+ sctpwheel_listhead,
+ ss_params.rr.next_spoke);
+ if (asoc->last_out_stream == NULL) {
+ asoc->last_out_stream = TAILQ_LAST(&asoc->ss_data.out_wheel,
+ sctpwheel_listhead);
+ }
+ if (asoc->last_out_stream == strq) {
+ asoc->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;
+
+ strqt = asoc->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->last_out_stream) {
+ return (NULL);
+ } else {
+ strqt = strq;
+ goto default_again;
+ }
+ }
+ }
+ return (strq);
+}
+
+static void
+sctp_ss_default_scheduled(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED,
+ struct sctp_association *asoc SCTP_UNUSED,
+ struct sctp_stream_out *strq, int moved_how_much SCTP_UNUSED)
+{
+ asoc->last_out_stream = strq;
+ 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);
+}
+
+/*
+ * 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->stream_no < strq->stream_no)) {
+ 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->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->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->last_out_stream) {
+ strq = NULL;
+ } else {
+ strqt = strq;
+ goto rrp_again;
+ }
+ }
+ }
+ asoc->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 = TAILQ_FIRST(&asoc->ss_data.out_wheel);
+ if (clear_values) {
+ strq->ss_params.prio.priority = 0;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out_wheel, TAILQ_FIRST(&asoc->ss_data.out_wheel), ss_params.prio.next_spoke);
+ strq->ss_params.prio.next_spoke.tqe_next = NULL;
+ strq->ss_params.prio.next_spoke.tqe_prev = NULL;
+
+ }
+ asoc->last_out_stream = NULL;
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static void
+sctp_ss_prio_init_stream(struct sctp_stream_out *strq, struct sctp_stream_out *with_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->last_out_stream == strq) {
+ asoc->last_out_stream = TAILQ_PREV(asoc->last_out_stream, sctpwheel_listhead,
+ ss_params.prio.next_spoke);
+ if (asoc->last_out_stream == NULL) {
+ asoc->last_out_stream = TAILQ_LAST(&asoc->ss_data.out_wheel,
+ sctpwheel_listhead);
+ }
+ if (asoc->last_out_stream == strq) {
+ asoc->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;
+
+ strqt = asoc->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->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 = TAILQ_FIRST(&asoc->ss_data.out_wheel);
+ if (clear_values) {
+ strq->ss_params.fb.rounds = -1;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out_wheel, TAILQ_FIRST(&asoc->ss_data.out_wheel), ss_params.fb.next_spoke);
+ strq->ss_params.fb.next_spoke.tqe_next = NULL;
+ strq->ss_params.fb.next_spoke.tqe_prev = NULL;
+ }
+ asoc->last_out_stream = NULL;
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static void
+sctp_ss_fb_init_stream(struct sctp_stream_out *strq, struct sctp_stream_out *with_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->last_out_stream == strq) {
+ asoc->last_out_stream = TAILQ_PREV(asoc->last_out_stream, sctpwheel_listhead,
+ ss_params.fb.next_spoke);
+ if (asoc->last_out_stream == NULL) {
+ asoc->last_out_stream = TAILQ_LAST(&asoc->ss_data.out_wheel,
+ sctpwheel_listhead);
+ }
+ if (asoc->last_out_stream == strq) {
+ asoc->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->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->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 SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED,
+ struct sctp_association *asoc, struct sctp_stream_out *strq,
+ int moved_how_much SCTP_UNUSED)
+{
+ struct sctp_stream_out *strqt;
+ int subtract;
+
+ 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->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, 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;
+
+ 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, holds_lock);
+ add_more = 1;
+ }
+ }
+ n++;
+ }
+ return;
+}
+
+static void
+sctp_ss_fcfs_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ int clear_values, int holds_lock)
+{
+ if (clear_values) {
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ while (!TAILQ_EMPTY(&asoc->ss_data.out_list)) {
+ TAILQ_REMOVE(&asoc->ss_data.out_list, TAILQ_FIRST(&asoc->ss_data.out_list), ss_next);
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ }
+ return;
+}
+
+static void
+sctp_ss_fcfs_init_stream(struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_out *with_strq SCTP_UNUSED)
+{
+ /* Nothing to be done here */
+ 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);
+ }
+ 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;
+
+ sp = TAILQ_FIRST(&asoc->ss_data.out_list);
+default_again:
+ if (sp != NULL) {
+ strq = &asoc->strmout[sp->stream];
+ } 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);
+}
+
+struct sctp_ss_functions sctp_ss_functions[] = {
+/* SCTP_SS_DEFAULT */
+{
+#if defined(__Windows__) || defined(__Userspace_os_Windows)
+ 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
+#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
+#endif
+},
+/* SCTP_SS_ROUND_ROBIN */
+{
+#if defined(__Windows__) || defined(__Userspace_os_Windows)
+ 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
+#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
+#endif
+},
+/* SCTP_SS_ROUND_ROBIN_PACKET */
+{
+#if defined(__Windows__) || defined(__Userspace_os_Windows)
+ 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
+#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
+#endif
+},
+/* SCTP_SS_PRIORITY */
+{
+#if defined(__Windows__) || defined(__Userspace_os_Windows)
+ 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
+#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
+#endif
+},
+/* SCTP_SS_FAIR_BANDWITH */
+{
+#if defined(__Windows__) || defined(__Userspace_os_Windows)
+ 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
+#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
+#endif
+},
+/* SCTP_SS_FIRST_COME */
+{
+#if defined(__Windows__) || defined(__Userspace_os_Windows)
+ 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
+#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
+#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..7047b41886
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_structs.h
@@ -0,0 +1,1275 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_structs.h 279859 2015-03-10 19:49:25Z 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__) && __FreeBSD_version >= 800000
+ 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 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(SCTP_MCORE_INPUT) && defined(SMP)
+/* whats on the mcore control struct */
+struct sctp_mcore_queue {
+ TAILQ_ENTRY(sctp_mcore_queue) next;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ struct vnet *vn;
+#endif
+ 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
+
+
+struct sctp_iterator {
+ TAILQ_ENTRY(sctp_iterator) sctp_nxt_itr;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ 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;
+ int sndlen;
+ int cnt_sent;
+ int cnt_failed;
+};
+
+struct sctp_asconf_iterator {
+ struct sctpladdr list_of_work;
+ int cnt;
+};
+
+struct iterator_control {
+#if defined(__FreeBSD__)
+ struct mtx ipi_iterator_wq_mtx;
+ struct mtx it_mtx;
+#elif defined(__APPLE__)
+ 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(__Windows__)
+ 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(__Windows__)
+#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__)
+#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 {
+ sctp_rtentry_t *ro_rt;
+#if defined(__FreeBSD__)
+#if __FreeBSD_version >= 800000
+ void *ro_lle;
+#endif
+#if __FreeBSD_version >= 900000
+ void *ro_ia;
+ int ro_flags;
+#endif
+#endif
+#if defined(__APPLE__)
+#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 */
+ unsigned int 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__)
+ uint32_t flowid;
+ uint8_t flowtype;
+#endif
+};
+
+
+struct sctp_data_chunkrec {
+ uint32_t TSN_seq; /* the TSN of this transmit */
+ uint16_t stream_seq; /* the stream sequence number of this transmit */
+ uint16_t stream_number; /* the stream number of this guy */
+ uint32_t payloadtype;
+ 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 */
+ 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;
+};
+
+/*
+ * The first part of this structure MUST be the entire sinfo structure. Maybe
+ * I should have made it a sub structure... we can circle back later and do
+ * that if we want.
+ */
+struct sctp_queued_to_read { /* sinfo structure Pluse more */
+ uint16_t sinfo_stream; /* off the wire */
+ uint16_t sinfo_ssn; /* 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 length; /* length of data */
+ uint32_t held_length; /* length held in sb */
+ 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;
+ 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 some_taken;
+};
+
+/* 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 length;
+ uint32_t timetolive;
+ uint32_t ppid;
+ uint32_t context;
+ uint16_t sinfo_flags;
+ uint16_t stream;
+ 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;
+ uint16_t stream_no;
+ uint16_t last_sequence_delivered; /* used for re-order */
+ uint8_t delivery_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.
+ */
+union scheduling_data {
+ struct sctpwheel_listhead out_wheel;
+ struct sctplist_listhead out_list;
+};
+
+/*
+ * 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;
+};
+
+/* 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;
+#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
+ uint16_t stream_no;
+ uint16_t next_sequence_send; /* next one I expect to send out */
+ uint8_t last_msg_incomplete;
+};
+
+/* 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;
+ uint16_t strm;
+ uint16_t seq;
+ uint16_t sz;
+ uint16_t flgs;
+ uint16_t in_pos;
+ uint16_t in_out;
+};
+
+#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_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);
+};
+
+/* 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 delayed_event_timer; /* timer for delayed events */
+ 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;
+
+ /* re-assembly queue for fragmented chunks on the inbound path */
+ struct sctpchunk_listhead reasmqueue;
+
+ /* Scheduling queues */
+ union scheduling_data ss_data;
+
+ /* This pointer will be set to NULL
+ * most of the time. But when we have
+ * a fragmented message, where we could
+ * not get out all of the message at
+ * the last send then this will point
+ * to the stream to go get data from.
+ */
+ struct sctp_stream_out *locked_on_sending;
+
+ /* 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;
+
+ /* circular looking for output selection */
+ struct sctp_stream_out *last_out_stream;
+
+ /*
+ * 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 efficent. 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 */
+ unsigned int 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 */
+ unsigned int 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];
+ unsigned int initial_init_rto_max; /* initial RTO for INIT's */
+ unsigned int initial_rto; /* initial send RTO */
+ unsigned int minrto; /* per assoc RTO-MIN */
+ unsigned int 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;
+ 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 */
+
+ /*
+ * 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;
+
+ /* 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_onsack;
+ 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..43f59313e1
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sysctl.c
@@ -0,0 +1,1706 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_sysctl.c 277424 2015-01-20 19:08:55Z 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>
+#ifdef __FreeBSD__
+#include <sys/smp.h>
+#include <sys/sysctl.h>
+#endif
+#if defined(__APPLE__)
+#include <netinet/sctp_bsd_addr.h>
+#endif
+
+#ifdef __FreeBSD__
+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;
+ SCTP_BASE_SYSCTL(sctp_strict_sacks) = SCTPCTL_STRICT_SACKS_DEFAULT;
+#if !(defined(__FreeBSD__) && __FreeBSD_version >= 800000)
+#if !defined(SCTP_WITH_NO_CSUM)
+ SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback) = SCTPCTL_LOOPBACK_NOCSUM_DEFAULT;
+#endif
+#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;
+ SCTP_BASE_SYSCTL(sctp_hashtblsize) = SCTPCTL_TCBHASHSIZE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_pcbtblsize) = SCTPCTL_PCBHASHSIZE_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_min_split_point) = SCTPCTL_MIN_SPLIT_POINT_DEFAULT;
+ SCTP_BASE_SYSCTL(sctp_chunkscale) = SCTPCTL_CHUNKSCALE_DEFAULT;
+ 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_strict_data_order) = SCTPCTL_STRICT_DATA_ORDER_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_diag_info_code) = SCTPCTL_DIAG_INFO_CODE_DEFAULT;
+#if defined(SCTP_LOCAL_TRACE_BUF)
+#if defined(__Windows__)
+ /* 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__)
+ 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(SCTP_SO_LOCK_TESTING)
+ SCTP_BASE_SYSCTL(sctp_output_unlocked) = SCTPCTL_OUTPUT_UNLOCKED_DEFAULT;
+#endif
+}
+
+#if defined(__Windows__)
+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(__APPLE__) || defined(__FreeBSD__) || defined(__Windows__)
+/* 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__)
+ 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;
+
+#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__)
+ 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
+ /* bad link local address */
+ if (sa6_recoverscope(sin6) != 0)
+ continue;
+#else
+ lsa6 = *sin6;
+ /* bad link local address */
+ 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;
+ } 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__)
+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__)
+ 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(__Windows__)
+ req->oldidx = (n + n / 8);
+#else
+ req->dataidx = (n + n / 8);
+#endif
+ return (0);
+ }
+#if defined(__APPLE__)
+ 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);
+ }
+ 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;
+#if defined(__FreeBSD__) && __FreeBSD_version < 1000048
+ xinpcb.features = (uint32_t)inp->sctp_features;
+#else
+ xinpcb.features = inp->sctp_features;
+#endif
+ 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;
+ so = inp->sctp_socket;
+ if ((so == NULL) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) {
+ xinpcb.qlen = 0;
+ xinpcb.maxqlen = 0;
+ } else {
+ xinpcb.qlen = so->so_qlen;
+ xinpcb.maxqlen = so->so_qlimit;
+ }
+ 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 = SCTP_GET_STATE(&stcb->asoc); /* FIXME */
+#if defined(__FreeBSD__)
+#if __FreeBSD_version >= 800000
+ /* 7.0 does not support these */
+ xstcb.assoc_id = sctp_get_associd(stcb);
+ xstcb.peers_rwnd = stcb->asoc.peers_rwnd;
+#endif
+#else
+ xstcb.assoc_id = sctp_get_associd(stcb);
+ xstcb.peers_rwnd = stcb->asoc.peers_rwnd;
+#endif
+ 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;
+#if defined(__FreeBSD__)
+#if __FreeBSD_version >= 800000
+ xraddr.rtt = net->rtt / 1000;
+ xraddr.heartbeat_interval = net->heart_beat_delay;
+#endif
+#else
+ xraddr.rtt = net->rtt / 1000;
+ xraddr.heartbeat_interval = net->heart_beat_delay;
+#endif
+ 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__)
+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;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800056 && __FreeBSD_version < 1000100
+#ifdef VIMAGE
+ error = vnet_sysctl_handle_int(oidp, &new, 0, req);
+#else
+ error = sysctl_handle_int(oidp, &new, 0, req);
+#endif
+#else
+ error = sysctl_handle_int(oidp, &new, 0, req);
+#endif
+ if ((error == 0) &&
+#if defined (__APPLE__)
+ (req->newptr != USER_ADDR_NULL)) {
+#else
+ (req->newptr != NULL)) {
+#endif
+#if defined(__Windows__)
+ 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__)
+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__)
+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);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800056 && __FreeBSD_version < 1000100
+#ifdef VIMAGE
+ error = vnet_sysctl_handle_int(oidp, &new, 0, req);
+#else
+ error = sysctl_handle_int(oidp, &new, 0, req);
+#endif
+#else
+ error = sysctl_handle_int(oidp, &new, 0, req);
+#endif
+ if ((error == 0) &&
+#if defined (__APPLE__)
+ (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__)
+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);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800056 && __FreeBSD_version < 1000100
+#ifdef VIMAGE
+ error = vnet_sysctl_handle_int(oidp, &new, 0, req);
+#else
+ error = sysctl_handle_int(oidp, &new, 0, req);
+#endif
+#else
+ error = sysctl_handle_int(oidp, &new, 0, req);
+#endif
+ if ((error == 0) &&
+#if defined (__APPLE__)
+ (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__)
+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__)
+#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__)
+ if ((req->newptr != USER_ADDR_NULL) &&
+#else
+ if ((req->newptr != NULL) &&
+#endif
+ (req->newlen != sizeof(struct sctpstat))) {
+ return (EINVAL);
+ }
+#if defined(__FreeBSD__)
+ 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_recvnocrc += sarry->sctps_recvnocrc;
+ 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_sendnocrc += sarry->sctps_sendnocrc;
+ 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__)
+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(__Windows__)
+ 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__)
+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(__Windows__)
+ 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__)
+#if defined(__FreeBSD__)
+#if __FreeBSD_version >= 800056 && __FreeBSD_version < 1000100
+#ifdef VIMAGE
+#define SCTP_UINT_SYSCTL(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 = vnet_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, \
+ 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(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);
+#endif
+#else
+#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);
+#endif
+#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)
+SCTP_UINT_SYSCTL(strict_sacks, sctp_strict_sacks, SCTPCTL_STRICT_SACKS)
+#if defined(__APPLE__)
+#if !defined(SCTP_WITH_NO_CSUM)
+SCTP_UINT_SYSCTL(loopback_nocsum, sctp_no_csum_on_loopback, SCTPCTL_LOOPBACK_NOCSUM)
+#endif
+#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(strict_data_order, sctp_strict_data_order, SCTPCTL_STRICT_DATA_ORDER)
+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(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__)
+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(SCTP_SO_LOCK_TESTING)
+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(__Windows__)
+
+#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_strict_sacks), SCTPCTL_STRICT_SACKS_MIN, SCTPCTL_STRICT_SACKS_MAX);
+#if !defined(SCTP_WITH_NO_CSUM)
+ RANGECHK(SCTP_BASE_SYSCTL(sctp_no_csum_on_loopback), SCTPCTL_LOOPBACK_NOCSUM_MIN, SCTPCTL_LOOPBACK_NOCSUM_MAX);
+#endif
+ 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_strict_data_order), SCTPCTL_STRICT_DATA_ORDER_MIN, SCTPCTL_STRICT_DATA_ORDER_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_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, "strict_sacks", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_strict_sacks), 0, sctp_sysctl_handle_int,
+ SCTPCTL_STRICT_SACKS_DESC);
+
+#if !defined(SCTP_WITH_NO_CSUM)
+ 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);
+#endif
+
+ 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, "strict_data_order", CTLTYPE_INT|CTLFLAG_RW,
+ &SCTP_BASE_SYSCTL(sctp_strict_data_order), 0, sctp_sysctl_handle_int,
+ SCTPCTL_STRICT_DATA_ORDER_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, "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..3a33b71f9b
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sysctl.h
@@ -0,0 +1,631 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_sysctl.h 271204 2014-09-06 19:12:14Z 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;
+ uint32_t sctp_strict_sacks;
+#if !(defined(__FreeBSD__) && __FreeBSD_version >= 800000)
+#if !defined(SCTP_WITH_NO_CSUM)
+ uint32_t sctp_no_csum_on_loopback;
+#endif
+#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_strict_data_order;
+ 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(__Windows__)
+ 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;
+#if defined(SCTP_DEBUG)
+ uint32_t sctp_debug_on;
+#endif
+#if defined(__APPLE__)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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
+#define SCTPCTL_MAXDGRAM_DEFAULT 262144 /* 256k */
+
+/* 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
+#define SCTPCTL_RECVSPACE_DEFAULT 262144 /* 256k */
+
+/* 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
+
+/* strict_sacks: Enable SCTP Strict SACK checking */
+#define SCTPCTL_STRICT_SACKS_DESC "Enable SCTP Strict SACK checking"
+#define SCTPCTL_STRICT_SACKS_MIN 0
+#define SCTPCTL_STRICT_SACKS_MAX 1
+#define SCTPCTL_STRICT_SACKS_DEFAULT 1
+
+/* 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 fr max burst for sctp endpoints"
+#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 "Default shutdown guard timer in seconds"
+#define SCTPCTL_SHUTDOWN_GUARD_TIME_MIN 0
+#define SCTPCTL_SHUTDOWN_GUARD_TIME_MAX 0xFFFFFFFF
+#define SCTPCTL_SHUTDOWN_GUARD_TIME_DEFAULT SCTP_DEF_MAX_SHUTDOWN_SEC
+
+/* 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 retransmission 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 maxburst */
+#define SCTPCTL_CWND_MAXBURST_DESC "Use a CWND adjusting maxburst"
+#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 "When one-2-one hits qlimit abort"
+#define SCTPCTL_ABORT_AT_LIMIT_MIN 0
+#define SCTPCTL_ABORT_AT_LIMIT_MAX 1
+#define SCTPCTL_ABORT_AT_LIMIT_DEFAULT 0
+
+/* strict_data_order: Enforce strict data ordering, abort if control inside data */
+#define SCTPCTL_STRICT_DATA_ORDER_DESC "Enforce strict data ordering, abort if control inside data"
+#define SCTPCTL_STRICT_DATA_ORDER_MIN 0
+#define SCTPCTL_STRICT_DATA_ORDER_MAX 1
+#define SCTPCTL_STRICT_DATA_ORDER_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 retran'd 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__)
+#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_MIN
+
+/* 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 MTU */
+#define SCTPCTL_INITIAL_CWND_DESC "Initial congestion window 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 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 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 "What to return when 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 "How many the sames it 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 for RTCC CC datacenter ECN"
+#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"
+#define SCTPCTL_BLACKHOLE_MIN 0
+#define SCTPCTL_BLACKHOLE_MAX 2
+#define SCTPCTL_BLACKHOLE_DEFAULT SCTPCTL_BLACKHOLE_MIN
+
+#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__)
+#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(SCTP_SO_LOCK_TESTING)
+#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__)
+#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(__Windows__)
+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..48ad459028
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_timer.c
@@ -0,0 +1,1603 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_timer.c 279841 2015-03-10 09:16:31Z tuexen $");
+#endif
+
+#define _IP_VHL
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_pcb.h>
+#ifdef INET6
+#if defined(__Userspace_os_FreeBSD)
+#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(__Userspace_os_Windows)
+#include <netinet/udp.h>
+#endif
+#endif
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 6
+#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);
+}
+
+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, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_TIMER + SCTP_LOC_3);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, 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_CAUSE_PROTOCOL_VIOLATION,
+ "Association error counter exceeded");
+ inp->last_abort_code = SCTP_FROM_SCTP_TIMER+SCTP_LOC_1;
+ 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 (alt->ro.ro_rt == NULL) {
+ 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) &&
+ (alt->ro.ro_rt != NULL) &&
+ (!(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) {
+ net->RTO = stcb->asoc.minrto;
+ }
+ 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_seq)) {
+ SCTP_PRINTF("Found chk:%p tsn:%x <= last_acked_seq:%x\n",
+ (void *)chk, chk->rec.data.TSN_seq, asoc->last_acked_seq);
+ if (chk->sent != SCTP_DATAGRAM_NR_ACKED) {
+ if (asoc->strmout[chk->rec.data.stream_number].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.stream_number].chunks_on_queues--;
+ }
+ }
+ 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_seq);
+ }
+}
+#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;
+#ifndef __FreeBSD__
+ 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_seq)) {
+ /* 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_seq);
+ 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_seq,
+ 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? */
+#ifndef __FreeBSD__
+ 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_seq;
+ }
+ tsnlast = chk->rec.data.TSN_seq;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_FR_LOGGING_ENABLE) {
+ sctp_log_fr(chk->rec.data.TSN_seq, 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,
+ (uintptr_t)chk->whoTo,
+ chk->rec.data.TSN_seq);
+ }
+ 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;
+ 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_seq;
+ }
+ }
+ /* 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:%ld\n",
+ num_mk, (u_long)stcb->asoc.peers_rwnd);
+ SCTPDBG(SCTP_DEBUG_TIMER1, "LAST TSN marked was %x\n",
+ tsnlast);
+ SCTPDBG(SCTP_DEBUG_TIMER1, "Num marked for retransmission was %d peer-rwd:%d\n",
+ num_mk,
+ (int)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;
+ 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,
+ (uintptr_t)chk->whoTo,
+ chk->rec.data.TSN_seq);
+ }
+
+ 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 (net->ro.ro_rt) {
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+
+ /* 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->asoc) == 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);
+ if (lchk) {
+ /* 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->asoc)) != 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->asoc) == SCTP_STATE_COOKIE_ECHOED) {
+ /* FOOBAR! */
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION,
+ "Cookie timer expired, but no cookie");
+ inp->last_abort_code = SCTP_FROM_SCTP_TIMER+SCTP_LOC_4;
+ 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->asoc));
+ 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 theshold management now lets backoff the address & 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;
+ /*
+ * 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 *net)
+{
+ struct sctp_nets *alt;
+ 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);
+ }
+ /* do threshold management */
+ if (sctp_threshold_management(inp, stcb, strrst->whoTo,
+ stcb->asoc.max_send_times)) {
+ /* Assoc is over */
+ return (1);
+ }
+ /*
+ * cleared theshold management now lets backoff the address & select
+ * an alternate
+ */
+ sctp_backoff_on_timeout(stcb, strrst->whoTo, 1, 0, 0);
+ alt = sctp_find_alternate_net(stcb, strrst->whoTo, 0);
+ sctp_free_remote_addr(strrst->whoTo);
+ 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;
+ 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);
+ }
+ /* mark the retran info */
+ if (strrst->sent != SCTP_DATAGRAM_RESEND)
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ strrst->sent = SCTP_DATAGRAM_RESEND;
+
+ /* restart the timer */
+ sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, inp, stcb, strrst->whoTo);
+ 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);
+ }
+ /* do threshold management */
+ if (sctp_threshold_management(inp, stcb, asconf->whoTo,
+ 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, net);
+ return (0);
+ }
+ /*
+ * cleared threshold management, so now backoff the net and
+ * select an alternate
+ */
+ sctp_backoff_on_timeout(stcb, asconf->whoTo, 1, 0, 0);
+ alt = sctp_find_alternate_net(stcb, asconf->whoTo, 0);
+ if (asconf->whoTo != alt) {
+ sctp_free_remote_addr(asconf->whoTo);
+ 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;
+ 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;
+ }
+ 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);
+ }
+ /* mark the retran info */
+ if (asconf->sent != SCTP_DATAGRAM_RESEND)
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ asconf->sent = SCTP_DATAGRAM_RESEND;
+
+ /* 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,
+ struct sctp_nets *net SCTP_UNUSED)
+{
+ 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 managment */
+ 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 managment */
+ 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)) {
+#ifdef __FreeBSD__
+ 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__)
+#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) {
+ mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._s_addr.sa, net->ro.ro_rt);
+#if defined(INET) || defined(INET6)
+ if (net->port) {
+ mtu -= sizeof(struct udphdr);
+ }
+#endif
+ if (mtu > next_mtu) {
+ net->mtu = next_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 sctp_nets *net)
+{
+ struct timeval tn, *tim_touse;
+ struct sctp_association *asoc;
+ int ticks_gone_by;
+
+ (void)SCTP_GETTIME_TIMEVAL(&tn);
+ if (stcb->asoc.sctp_autoclose_ticks &&
+ 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 = SEC_TO_TICKS(tn.tv_sec - tim_touse->tv_sec);
+ if ((ticks_gone_by > 0) &&
+ (ticks_gone_by >= (int)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(asoc) != SCTP_STATE_SHUTDOWN_SENT) {
+ /* only send SHUTDOWN 1st time thru */
+ struct sctp_nets *netp;
+
+ 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_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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,
+ netp);
+ }
+ }
+ } else {
+ /*
+ * No auto close at this time, reset t-o to check
+ * later
+ */
+ int 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,
+ net);
+ /* 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..3662bd2c7a
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_timer.h
@@ -0,0 +1,107 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_timer.h 235828 2012-05-23 11:26:28Z 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 mode);
+
+int
+sctp_threshold_management(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *, uint16_t);
+
+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 *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net);
+
+int
+sctp_asconf_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+void
+sctp_delete_prim_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *);
+
+void
+sctp_autoclose_timer(struct sctp_inpcb *, struct sctp_tcb *,
+ struct sctp_nets *net);
+
+void sctp_audit_retranmission_queue(struct sctp_association *);
+
+void sctp_iterator_timer(struct sctp_iterator *it);
+
+#if defined(__APPLE__)
+#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..7cf02ed252
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_uio.h
@@ -0,0 +1,1427 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_uio.h 269945 2014-08-13 15:50:16Z tuexen $");
+#endif
+
+#ifndef _NETINET_SCTP_UIO_H_
+#define _NETINET_SCTP_UIO_H_
+
+#if (defined(__APPLE__) && defined(KERNEL))
+#ifndef _KERNEL
+#define _KERNEL
+#endif
+#endif
+
+#if !(defined(__Windows__)) && !defined(__Userspace_os_Windows)
+#if ! defined(_KERNEL)
+#include <stdint.h>
+#endif
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+#if defined(__Windows__)
+#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 {
+#if defined(__FreeBSD__) && __FreeBSD_version < 800000
+ /* This is a bug. Not fixed for ABI compatibility */
+ uint32_t sinit_num_ostreams;
+ uint32_t sinit_max_instreams;
+#else
+ uint16_t sinit_num_ostreams;
+ uint16_t sinit_max_instreams;
+#endif
+ 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;
+#if defined(__FreeBSD__) && __FreeBSD_version < 800000
+ uint16_t sinfo_pr_policy;
+#endif
+ 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;
+#if defined(__FreeBSD__) && __FreeBSD_version < 800000
+ uint16_t sinfo_pr_policy;
+#endif
+ 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 sreinfo_next_flags;
+ uint16_t sreinfo_next_stream;
+ uint32_t sreinfo_next_aid;
+ uint32_t sreinfo_next_length;
+ uint32_t sreinfo_next_ppid;
+ uint16_t sinfo_keynumber;
+ uint16_t sinfo_keynumber_valid;
+ uint8_t __reserve_pad[SCTP_ALIGN_RESV_PAD_SHORT];
+};
+
+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_BUF 0x0002 /* Buffer based PR-SCTP */
+#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_MAX 0x05
+/*
+ * 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;
+};
+
+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];
+};
+
+struct sctp_getaddresses {
+ sctp_assoc_t sget_assoc_id;
+ /* addr is filled in for N * sockaddr_storage */
+ struct sockaddr addr[1];
+};
+
+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_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_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(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
+/* 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)
+
+#if defined(__Userspace__)
+union sctp_sockstore {
+#if defined(INET)
+ struct sockaddr_in sin;
+#endif
+#if defined(INET6)
+ struct sockaddr_in6 sin6;
+#endif
+ struct sockaddr_conn sconn;
+ struct sockaddr sa;
+};
+#else
+union sctp_sockstore {
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr sa;
+};
+#endif
+
+
+/***********************************/
+/* And something for us old timers */
+/***********************************/
+
+#ifndef __APPLE__
+#ifndef __Userspace__
+#ifndef ntohll
+#if defined(__Userspace_os_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(__Userspace_os_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;
+#if defined(__FreeBSD__) && __FreeBSD_version < 1000048
+ uint32_t features;
+#else
+ uint64_t features;
+#endif
+ uint32_t total_sends;
+ uint32_t total_recvs;
+ uint32_t total_nospaces;
+ uint32_t fragmentation_point;
+ uint16_t local_port;
+ uint16_t qlen;
+ uint16_t maxqlen;
+#if defined(__Windows__)
+ uint16_t padding;
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version < 1000048
+ uint32_t extra_padding[32]; /* future */
+#else
+ uint32_t extra_padding[31]; /* future */
+#endif
+};
+
+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 */
+#if defined(__FreeBSD__)
+#if __FreeBSD_version >= 800000
+ uint32_t peers_rwnd;
+ sctp_assoc_t assoc_id; /* sctpAssocEntry 1 */
+ uint32_t extra_padding[32]; /* future */
+#else
+#endif
+#else
+ uint32_t peers_rwnd;
+ sctp_assoc_t assoc_id; /* sctpAssocEntry 1 */
+ uint32_t extra_padding[32]; /* future */
+#endif
+};
+
+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 */
+#if defined(__FreeBSD__)
+#if __FreeBSD_version >= 800000
+ uint32_t rtt;
+ uint32_t heartbeat_interval;
+ uint32_t extra_padding[31]; /* future */
+#endif
+#else
+ uint32_t rtt;
+ uint32_t heartbeat_interval;
+ uint32_t extra_padding[31]; /* future */
+#endif
+};
+
+#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,
+#if defined(__Panda__)
+ pakhandle_type i_pak,
+ pakhandle_type i_control,
+#else
+ struct mbuf *i_pak,
+ struct mbuf *control,
+#endif
+ int flags,
+ struct sctp_sndrcvinfo *srcv
+#if !(defined(__Panda__) || defined(__Userspace__))
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ ,struct thread *p
+#elif defined(__Windows__)
+ , PKTHREAD p
+#else
+ ,struct proc *p
+#endif
+#endif
+);
+
+int
+sctp_sorecvmsg(struct socket *so,
+ struct uio *uio,
+#if defined(__Panda__)
+ particletype **mp,
+#else
+ struct mbuf **mp,
+#endif
+ 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
+#if defined(__FreeBSD__) && __FreeBSD_version < 902000
+int sctp_peeloff __P((int, sctp_assoc_t));
+int sctp_bindx __P((int, struct sockaddr *, int, int));
+int sctp_connectx __P((int, const struct sockaddr *, int, sctp_assoc_t *));
+int sctp_getaddrlen __P((sa_family_t));
+int sctp_getpaddrs __P((int, sctp_assoc_t, struct sockaddr **));
+void sctp_freepaddrs __P((struct sockaddr *));
+int sctp_getladdrs __P((int, sctp_assoc_t, struct sockaddr **));
+void sctp_freeladdrs __P((struct sockaddr *));
+int sctp_opt_info __P((int, sctp_assoc_t, int, void *, socklen_t *));
+
+/* deprecated */
+ssize_t sctp_sendmsg __P((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 __P((int, const void *, size_t,
+ const struct sctp_sndrcvinfo *, int));
+
+/* deprecated */
+ssize_t sctp_sendx __P((int, const void *, size_t, struct sockaddr *,
+ int, struct sctp_sndrcvinfo *, int));
+
+/* deprecated */
+ssize_t sctp_sendmsgx __P((int sd, const void *, size_t, struct sockaddr *,
+ int, uint32_t, uint32_t, uint16_t, uint32_t, uint32_t));
+
+sctp_assoc_t sctp_getassocid __P((int, struct sockaddr *));
+
+/* deprecated */
+ssize_t sctp_recvmsg __P((int, void *, size_t, struct sockaddr *, socklen_t *,
+ struct sctp_sndrcvinfo *, int *));
+
+ssize_t sctp_sendv __P((int, const struct iovec *, int, struct sockaddr *,
+ int, void *, socklen_t, unsigned int, int));
+
+ssize_t sctp_recvv __P((int, const struct iovec *, int, struct sockaddr *,
+ socklen_t *, void *, socklen_t *, unsigned int *, int *));
+#else
+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 *);
+#endif
+__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..7841a89e1e
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_userspace.c
@@ -0,0 +1,389 @@
+/*-
+ * 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>
+#pragma comment(lib, "IPHLPAPI.lib")
+#endif
+#include <netinet/sctp_os_userspace.h>
+
+#if !defined(_WIN32) && !defined(__Userspace_os_NaCl)
+int
+sctp_userspace_get_mtu_from_ifn(uint32_t if_index, int af)
+{
+ struct ifreq ifr;
+ int fd;
+
+ if_indextoname(if_index, ifr.ifr_name);
+ /* 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;
+}
+#endif
+
+#if defined(__Userspace_os_NaCl)
+int
+sctp_userspace_get_mtu_from_ifn(uint32_t if_index, int af)
+{
+ return 1280;
+}
+#endif
+
+#ifdef _WIN32
+int
+sctp_userspace_get_mtu_from_ifn(uint32_t if_index, int af)
+{
+ PIP_ADAPTER_ADDRESSES pAdapterAddrs, pAdapt;
+ DWORD AdapterAddrsSize, Err;
+
+ AdapterAddrsSize = 0;
+ 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);
+ return (-1);
+ }
+ }
+ if ((pAdapterAddrs = (PIP_ADAPTER_ADDRESSES) GlobalAlloc(GPTR, AdapterAddrsSize)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Memory allocation error!\n");
+ return (-1);
+ }
+ if ((Err = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAdapterAddrs, &AdapterAddrsSize)) != ERROR_SUCCESS) {
+ SCTPDBG(SCTP_DEBUG_USR, "GetAdaptersAddresses() failed with error code %d\n", Err);
+ return (-1);
+ }
+ for (pAdapt = pAdapterAddrs; pAdapt; pAdapt = pAdapt->Next) {
+ if (pAdapt->IfIndex == if_index)
+ return (pAdapt->Mtu);
+ }
+ return (0);
+}
+
+void
+getwintimeofday(struct timeval *tv)
+{
+ struct timeb tb;
+
+ ftime(&tb);
+ tv->tv_sec = (long)tb.time;
+ tv->tv_usec = (long)(tb.millitm) * 1000L;
+}
+
+int
+Win_getifaddrs(struct ifaddrs** interfaces)
+{
+#if defined(INET) || defined(INET6)
+ DWORD Err, AdapterAddrsSize;
+ int count;
+ PIP_ADAPTER_ADDRESSES pAdapterAddrs, pAdapt;
+ struct ifaddrs *ifa;
+#endif
+#if defined(INET)
+ struct sockaddr_in *addr;
+#endif
+#if defined(INET6)
+ struct sockaddr_in6 *addr6;
+#endif
+#if defined(INET) || defined(INET6)
+ count = 0;
+#endif
+#if defined(INET)
+ AdapterAddrsSize = 0;
+ if ((Err = GetAdaptersAddresses(AF_INET, 0, NULL, NULL, &AdapterAddrsSize)) != 0) {
+ if ((Err != ERROR_BUFFER_OVERFLOW) && (Err != ERROR_INSUFFICIENT_BUFFER)) {
+ SCTPDBG(SCTP_DEBUG_USR, "GetAdaptersV4Addresses() sizing failed with error code %d and AdapterAddrsSize = %d\n", Err, AdapterAddrsSize);
+ return (-1);
+ }
+ }
+ /* Allocate memory from sizing information */
+ if ((pAdapterAddrs = (PIP_ADAPTER_ADDRESSES) GlobalAlloc(GPTR, AdapterAddrsSize)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Memory allocation error!\n");
+ return (-1);
+ }
+ /* Get actual adapter information */
+ if ((Err = GetAdaptersAddresses(AF_INET, 0, NULL, pAdapterAddrs, &AdapterAddrsSize)) != ERROR_SUCCESS) {
+ SCTPDBG(SCTP_DEBUG_USR, "GetAdaptersV4Addresses() failed with error code %d\n", Err);
+ return (-1);
+ }
+ /* Enumerate through each returned adapter and save its information */
+ for (pAdapt = pAdapterAddrs, count; pAdapt; pAdapt = pAdapt->Next, count++) {
+ addr = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
+ ifa = (struct ifaddrs *)malloc(sizeof(struct ifaddrs));
+ if ((addr == NULL) || (ifa == NULL)) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't allocate memory\n");
+ return (-1);
+ }
+ ifa->ifa_name = _strdup(pAdapt->AdapterName);
+ ifa->ifa_flags = pAdapt->Flags;
+ ifa->ifa_addr = (struct sockaddr *)addr;
+ memcpy(addr, &pAdapt->FirstUnicastAddress->Address.lpSockaddr, sizeof(struct sockaddr_in));
+ interfaces[count] = ifa;
+ }
+#endif
+#if defined(INET6)
+ if (SCTP_BASE_VAR(userspace_rawsctp6) != -1) {
+ AdapterAddrsSize = 0;
+ if ((Err = GetAdaptersAddresses(AF_INET6, 0, NULL, NULL, &AdapterAddrsSize)) != 0) {
+ if ((Err != ERROR_BUFFER_OVERFLOW) && (Err != ERROR_INSUFFICIENT_BUFFER)) {
+ SCTPDBG(SCTP_DEBUG_USR, "GetAdaptersV6Addresses() sizing failed with error code %d AdapterAddrsSize = %d\n", Err, AdapterAddrsSize);
+ return (-1);
+ }
+ }
+ /* Allocate memory from sizing information */
+ if ((pAdapterAddrs = (PIP_ADAPTER_ADDRESSES) GlobalAlloc(GPTR, AdapterAddrsSize)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Memory allocation error!\n");
+ return (-1);
+ }
+ /* Get actual adapter information */
+ if ((Err = GetAdaptersAddresses(AF_INET6, 0, NULL, pAdapterAddrs, &AdapterAddrsSize)) != ERROR_SUCCESS) {
+ SCTPDBG(SCTP_DEBUG_USR, "GetAdaptersV6Addresses() failed with error code %d\n", Err);
+ return (-1);
+ }
+ /* Enumerate through each returned adapter and save its information */
+ for (pAdapt = pAdapterAddrs, count; pAdapt; pAdapt = pAdapt->Next, count++) {
+ addr6 = (struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6));
+ ifa = (struct ifaddrs *)malloc(sizeof(struct ifaddrs));
+ if ((addr6 == NULL) || (ifa == NULL)) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't allocate memory\n");
+ return (-1);
+ }
+ ifa->ifa_name = _strdup(pAdapt->AdapterName);
+ ifa->ifa_flags = pAdapt->Flags;
+ ifa->ifa_addr = (struct sockaddr *)addr6;
+ memcpy(addr6, &pAdapt->FirstUnicastAddress->Address.lpSockaddr, sizeof(struct sockaddr_in6));
+ interfaces[count] = ifa;
+ }
+ }
+#endif
+ return (0);
+}
+
+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..d24a21815e
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_usrreq.c
@@ -0,0 +1,8845 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_usrreq.c 280459 2015-03-24 21:12:45Z tuexen $");
+#endif
+
+#include <netinet/sctp_os.h>
+#ifdef __FreeBSD__
+#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(HAVE_SCTP_PEELOFF_SOCKOPT)
+#include <netinet/sctp_peeloff.h>
+#endif /* HAVE_SCTP_PEELOFF_SOCKOPT */
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 7
+#endif
+
+extern struct sctp_cc_functions sctp_cc_functions[];
+extern 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, ...))
+#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(__Panda__) && !defined(__Userspace__)
+ u_long sb_max_adj;
+
+#endif
+#if defined(__Userspace__)
+#if defined(__Userspace_os_Windows)
+#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_cond_init(&accept_cond, NULL);
+ pthread_mutex_init(&accept_mtx, NULL);
+#endif
+#endif
+ /* Initialize and modify the sysctled variables */
+ sctp_init_sysctls();
+#if defined(__Userspace__)
+#if defined(__Userspace_os_Windows) || defined(__Userspace_os_NaCl)
+ srand((unsigned int)time(NULL));
+#else
+ srandom(getpid()); /* so inp->sctp_ep.random_numbers are truly random... */
+#endif
+#endif
+#if defined(__Panda__)
+ sctp_sendspace = SB_MAX;
+ sctp_recvspace = SB_MAX;
+
+#elif defined(__Userspace__)
+ SCTP_BASE_SYSCTL(sctp_sendspace) = SB_MAX;
+ SCTP_BASE_SYSCTL(sctp_recvspace) = SB_RAW;
+ SCTP_BASE_SYSCTL(sctp_udp_tunneling_port) = port;
+#else
+#if !defined(__APPLE__)
+ if ((nmbclusters / 8) > SCTP_ASOC_MAX_CHUNKS_ON_QUEUE)
+ SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue) = (nmbclusters / 8);
+#endif
+ /*
+ * 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.
+ */
+#if defined(__APPLE__)
+ sb_max_adj = (u_long)((u_quad_t) (sb_max) * MCLBYTES / (MSIZE + MCLBYTES));
+#else
+ sb_max_adj = (u_long)((u_quad_t) (SB_MAX) * MCLBYTES / (MSIZE + MCLBYTES));
+#endif
+#if defined(__APPLE__)
+ SCTP_BASE_SYSCTL(sctp_sendspace) = sb_max_adj;
+#else
+ 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(__Userspace_os_Windows)
+#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;
+#endif
+ sctp_pcb_init();
+#if defined(__Userspace__)
+ sctp_start_timer();
+#endif
+#if defined(SCTP_PACKET_LOGGING)
+ SCTP_BASE_VAR(packet_log_writers) = 0;
+ SCTP_BASE_VAR(packet_log_end) = 0;
+ bzero(&SCTP_BASE_VAR(packet_log_buffer), SCTP_PACKET_LOG_SIZE);
+#endif
+#if defined(__APPLE__)
+ SCTP_BASE_VAR(sctp_main_timer_ticks) = 0;
+ sctp_start_main_timer();
+ timeout(sctp_delayed_startup, NULL, 1);
+#endif
+}
+
+void
+sctp_finish(void)
+{
+#if defined(__APPLE__)
+ 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
+#if !defined(__Userspace_os_Windows)
+#if defined(INET) || defined(INET6)
+ if (SCTP_BASE_VAR(userspace_route) != -1) {
+ pthread_join(SCTP_BASE_VAR(recvthreadroute), NULL);
+ }
+#endif
+#endif
+#ifdef INET
+ if (SCTP_BASE_VAR(userspace_rawsctp) != -1) {
+#if defined(__Userspace_os_Windows)
+ WaitForSingleObject(SCTP_BASE_VAR(recvthreadraw), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(recvthreadraw));
+#else
+ pthread_join(SCTP_BASE_VAR(recvthreadraw), NULL);
+#endif
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp) != -1) {
+#if defined(__Userspace_os_Windows)
+ WaitForSingleObject(SCTP_BASE_VAR(recvthreadudp), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(recvthreadudp));
+#else
+ pthread_join(SCTP_BASE_VAR(recvthreadudp), NULL);
+#endif
+ }
+#endif
+#ifdef INET6
+ if (SCTP_BASE_VAR(userspace_rawsctp6) != -1) {
+#if defined(__Userspace_os_Windows)
+ WaitForSingleObject(SCTP_BASE_VAR(recvthreadraw6), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(recvthreadraw6));
+#else
+ pthread_join(SCTP_BASE_VAR(recvthreadraw6), NULL);
+#endif
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp6) != -1) {
+#if defined(__Userspace_os_Windows)
+ WaitForSingleObject(SCTP_BASE_VAR(recvthreadudp6), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(recvthreadudp6));
+#else
+ pthread_join(SCTP_BASE_VAR(recvthreadudp6), NULL);
+#endif
+ }
+#endif
+ SCTP_BASE_VAR(timer_thread_should_exit) = 1;
+#if defined(__Userspace_os_Windows)
+ WaitForSingleObject(SCTP_BASE_VAR(timer_thread), INFINITE);
+ CloseHandle(SCTP_BASE_VAR(timer_thread));
+#else
+ pthread_join(SCTP_BASE_VAR(timer_thread), NULL);
+#endif
+#endif
+ sctp_pcb_finish();
+#if defined(__Userspace__)
+#if defined(__Userspace_os_Windows)
+ DeleteConditionVariable(&accept_cond);
+ DeleteCriticalSection(&accept_mtx);
+#else
+ pthread_cond_destroy(&accept_cond);
+ pthread_mutex_destroy(&accept_mtx);
+#endif
+#endif
+#if defined(__Windows__)
+ sctp_finish_sysctls();
+#if defined(INET) || defined(INET6)
+ WSACleanup();
+#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;
+ 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,
+ (uintptr_t)chk->whoTo,
+ chk->rec.data.TSN_seq);
+ }
+ /* Clear any time so NO RTT is being done */
+ chk->do_rtt = 0;
+ }
+ }
+ }
+}
+
+#ifdef INET
+#if !defined(__Userspace__)
+#if defined(__Panda__) || defined(__Windows__)
+void
+#else
+static void
+#endif
+sctp_notify_mbuf(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net,
+ struct ip *ip,
+ struct sctphdr *sh)
+{
+ struct icmp *icmph;
+ int totsz, tmr_stopped = 0;
+ uint16_t nxtsz;
+
+ /* protection */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL) ||
+ (ip == NULL) || (sh == NULL)) {
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ return;
+ }
+ /* First job is to verify the vtag matches what I would send */
+ if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ icmph = (struct icmp *)((caddr_t)ip - (sizeof(struct icmp) -
+ sizeof(struct ip)));
+ if (icmph->icmp_type != ICMP_UNREACH) {
+ /* We only care about unreachable */
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ if (icmph->icmp_code != ICMP_UNREACH_NEEDFRAG) {
+ /* not a unreachable message due to frag. */
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 1000000
+ totsz = ntohs(ip->ip_len);
+#else
+ totsz = ip->ip_len;
+#endif
+
+ nxtsz = ntohs(icmph->icmp_nextmtu);
+ if (nxtsz == 0) {
+ /*
+ * old type router that does not tell us what the next size
+ * mtu is. Rats we will have to guess (in a educated fashion
+ * of course)
+ */
+ nxtsz = sctp_get_prev_mtu(totsz);
+ }
+ /* Stop any PMTU timer */
+ if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) {
+ tmr_stopped = 1;
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
+ SCTP_FROM_SCTP_USRREQ+SCTP_LOC_1);
+ }
+ /* Adjust destination size limit */
+ if (net->mtu > nxtsz) {
+ net->mtu = nxtsz;
+ if (net->port) {
+ net->mtu -= sizeof(struct udphdr);
+ }
+ }
+ /* now what about the ep? */
+ if (stcb->asoc.smallest_mtu > nxtsz) {
+ sctp_pathmtu_adjustment(stcb, nxtsz);
+ }
+ if (tmr_stopped)
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net);
+
+ SCTP_TCB_UNLOCK(stcb);
+}
+
+void
+sctp_notify(struct sctp_inpcb *inp,
+ struct ip *ip,
+ struct sctphdr *sh,
+ struct sockaddr *to,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+
+#endif
+ struct icmp *icmph;
+
+ /* protection */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL) ||
+ (sh == NULL) || (to == NULL)) {
+ if (stcb)
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ /* First job is to verify the vtag matches what I would send */
+ if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+
+ icmph = (struct icmp *)((caddr_t)ip - (sizeof(struct icmp) -
+ sizeof(struct ip)));
+ if (icmph->icmp_type != ICMP_UNREACH) {
+ /* We only care about unreachable */
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ if ((icmph->icmp_code == ICMP_UNREACH_NET) ||
+ (icmph->icmp_code == ICMP_UNREACH_HOST) ||
+ (icmph->icmp_code == ICMP_UNREACH_NET_UNKNOWN) ||
+ (icmph->icmp_code == ICMP_UNREACH_HOST_UNKNOWN) ||
+ (icmph->icmp_code == ICMP_UNREACH_ISOLATED) ||
+ (icmph->icmp_code == ICMP_UNREACH_NET_PROHIB) ||
+ (icmph->icmp_code == ICMP_UNREACH_HOST_PROHIB) ||
+#if defined(__Panda__)
+ (icmph->icmp_code == ICMP_UNREACH_ADMIN)) {
+#elif defined(__Userspace_os_NetBSD)
+ (icmph->icmp_code == ICMP_UNREACH_ADMIN_PROHIBIT)) {
+#else
+ (icmph->icmp_code == ICMP_UNREACH_FILTER_PROHIB)) {
+#endif
+
+ /*
+ * Hmm reachablity problems we must examine closely. If its
+ * not reachable, we may have lost a network. Or if there is
+ * NO protocol at the other end named SCTP. well we consider
+ * it a OOTB abort.
+ */
+ 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 ((icmph->icmp_code == ICMP_UNREACH_PROTOCOL) ||
+ (icmph->icmp_code == ICMP_UNREACH_PORT)) {
+ /*
+ * Here the peer is either playing tricks on us,
+ * including an address that belongs to someone who
+ * does not support SCTP OR was a userland
+ * implementation that shutdown and now is dead. In
+ * either case treat it like a OOTB abort with no
+ * TCB
+ */
+ sctp_abort_notification(stcb, 1, 0, NULL, SCTP_SO_NOT_LOCKED);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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 {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+}
+#endif
+#endif
+
+#ifdef INET
+#if !defined(__Panda__) && !defined(__Userspace__)
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+void
+#else
+void *
+#endif
+sctp_ctlinput(cmd, sa, vip)
+ int cmd;
+ struct sockaddr *sa;
+ void *vip;
+{
+ struct ip *ip = vip;
+ struct sctphdr *sh;
+ uint32_t vrf_id;
+ /* FIX, for non-bsd is this right? */
+ vrf_id = SCTP_DEFAULT_VRFID;
+ if (sa->sa_family != AF_INET ||
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr == INADDR_ANY) {
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ return;
+#else
+ return (NULL);
+#endif
+ }
+ if (PRC_IS_REDIRECT(cmd)) {
+ ip = 0;
+ } else if ((unsigned)cmd >= PRC_NCMDS || inetctlerrmap[cmd] == 0) {
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ return;
+#else
+ return (NULL);
+#endif
+ }
+ if (ip) {
+ struct sctp_inpcb *inp = NULL;
+ struct sctp_tcb *stcb = NULL;
+ struct sctp_nets *net = NULL;
+ struct sockaddr_in to, from;
+
+ sh = (struct sctphdr *)((caddr_t)ip + (ip->ip_hl << 2));
+ bzero(&to, sizeof(to));
+ bzero(&from, sizeof(from));
+ from.sin_family = to.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ from.sin_len = to.sin_len = sizeof(to);
+#endif
+ from.sin_port = sh->src_port;
+ from.sin_addr = ip->ip_src;
+ to.sin_port = sh->dest_port;
+ to.sin_addr = ip->ip_dst;
+
+ /*
+ * 'to' holds the dest of the packet that failed to be sent.
+ * 'from' holds our local endpoint address. Thus we reverse
+ * the to and the from in the lookup.
+ */
+ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&to,
+ (struct sockaddr *)&from,
+ &inp, &net, 1, vrf_id);
+ if (stcb != NULL && inp && (inp->sctp_socket != NULL)) {
+ if (cmd != PRC_MSGSIZE) {
+ sctp_notify(inp, ip, sh,
+ (struct sockaddr *)&to, stcb,
+ net);
+ } else {
+ /* handle possible ICMP size messages */
+ sctp_notify_mbuf(inp, stcb, net, ip, sh);
+ }
+ } else {
+#if defined(__FreeBSD__) && __FreeBSD_version < 500000
+ /*
+ * XXX must be fixed for 5.x and higher, leave for
+ * 4.x
+ */
+ if (PRC_IS_REDIRECT(cmd) && inp) {
+ in_rtchange((struct inpcb *)inp,
+ inetctlerrmap[cmd]);
+ }
+#endif
+ 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);
+ }
+ }
+ }
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ return;
+#else
+ return (NULL);
+#endif
+}
+#endif
+#endif
+
+#if defined(__FreeBSD__)
+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;
+
+#if __FreeBSD_version > 602000
+ error = priv_check(req->td, PRIV_NETINET_GETCRED);
+
+#elif __FreeBSD_version >= 500000
+ error = suser(req->td);
+#else
+ error = suser(req->p);
+#endif
+ 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,
+ 0, 0, sctp_getcred, "S,ucred", "Get the ucred of a SCTP connection");
+#endif /* #if defined(__FreeBSD__) */
+
+
+#ifdef INET
+#if defined(__Panda__) || defined(__Windows__) || defined(__Userspace__)
+int
+#elif defined(__FreeBSD__) && __FreeBSD_version > 690000
+static void
+#else
+static int
+#endif
+sctp_abort(struct socket *so)
+{
+ struct sctp_inpcb *inp;
+ uint32_t flags;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+#if defined(__FreeBSD__) && __FreeBSD_version > 690000
+ return;
+#else
+ SCTP_LTRACE_ERR_RET(NULL, 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)))) {
+#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__)
+ 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__) && __FreeBSD_version > 690000
+ return;
+#else
+ return (0);
+#endif
+}
+
+#if defined(__Panda__) || defined(__Userspace__)
+int
+#else
+static int
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+sctp_attach(struct socket *so, int proto SCTP_UNUSED, struct thread *p SCTP_UNUSED)
+#elif defined(__Panda__) || defined(__Userspace__)
+sctp_attach(struct socket *so, int proto SCTP_UNUSED, uint32_t vrf_id)
+#elif defined(__Windows__)
+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(__Panda__) && !defined(__Userspace__)
+ uint32_t vrf_id = SCTP_DEFAULT_VRFID;
+#endif
+#ifdef IPSEC
+ uint32_t flags;
+#endif
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp != 0) {
+ 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);
+#ifdef IPSEC
+#if !(defined(__APPLE__))
+ error = ipsec_init_policy(so, &ip_inp->inp_sp);
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 17);
+#endif
+ if (error != 0) {
+ try_again:
+ flags = inp->sctp_flags;
+ 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, 15);
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+ } else {
+ flags = inp->sctp_flags;
+ if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) {
+ goto try_again;
+ } else {
+ SCTP_INP_WUNLOCK(inp);
+ }
+ }
+ return (error);
+ }
+#endif
+#endif /* IPSEC */
+ SCTP_INP_WUNLOCK(inp);
+ return (0);
+}
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+static int
+sctp_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+#elif defined(__FreeBSD__) || defined(__APPLE__)
+static int
+sctp_bind(struct socket *so, struct sockaddr *addr, struct proc *p) {
+#elif defined(__Panda__) || defined(__Userspace__)
+int
+sctp_bind(struct socket *so, struct sockaddr *addr) {
+ void *p = NULL;
+#elif defined(__Windows__)
+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__) && __FreeBSD_version > 690000) || defined(__Windows__) || defined(__Userspace__)
+void
+sctp_close(struct socket *so)
+{
+ 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.
+ */
+ 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__)
+ /* 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;
+ }
+ }
+ 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__) && __FreeBSD_version > 690000
+ 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__)
+ /* 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__) && __FreeBSD_version > 690000
+ return;
+#else
+ return (0);
+#endif
+}
+#endif
+
+#if defined(__Userspace__)
+/* __Userspace__ is not calling sctp_sendm */
+#endif
+#if !(defined(__Panda__) || defined(__Windows__))
+int
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+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__) && __FreeBSD_version >= 500000
+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_PRINTF("huh? control set?\n");
+ 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__)
+ /* 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.
+ */
+ int ret;
+
+ ret = sctp_output(inp, inp->pkt, addr, inp->control, p, flags);
+ 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 {
+ 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(__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(asoc) !=
+ SCTP_STATE_COOKIE_WAIT) {
+ /* Left with Data unread */
+ struct mbuf *err;
+
+ err = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_NOWAIT, 1, MT_DATA);
+ if (err) {
+ /*
+ * Fill in the user
+ * initiated abort
+ */
+ struct sctp_paramhdr *ph;
+
+ ph = mtod(err, struct sctp_paramhdr *);
+ SCTP_BUF_LEN(err) = sizeof(struct sctp_paramhdr);
+ ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT);
+ ph->param_length = htons(SCTP_BUF_LEN(err));
+ }
+ sctp_send_abort_tcb(stcb, err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&stcb->asoc) == 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 */
+ 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->locked_on_sending) {
+ goto abort_anyway;
+ }
+ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ /* only send SHUTDOWN 1st time thru */
+ struct sctp_nets *netp;
+
+ 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_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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, netp);
+ 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
+ */
+ struct sctp_nets *netp;
+ if (stcb->asoc.alternate) {
+ netp = stcb->asoc.alternate;
+ } else {
+ netp = stcb->asoc.primary_destination;
+ }
+
+ asoc->state |= SCTP_STATE_SHUTDOWN_PENDING;
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb,
+ netp);
+ if (asoc->locked_on_sending) {
+ /* Locked to send out the data */
+ struct sctp_stream_queue_pending *sp;
+ sp = TAILQ_LAST(&asoc->locked_on_sending->outqueue, sctp_streamhead);
+ if (sp == NULL) {
+ SCTP_PRINTF("Error, sp is NULL, locked on sending is non-null strm:%d\n",
+ asoc->locked_on_sending->stream_no);
+ } else {
+ if ((sp->length == 0) && (sp->msg_is_complete == 0))
+ asoc->state |= 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->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&stcb->asoc) == 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);
+ return (0);
+ } else {
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CLOSING, SCTP_SO_LOCKED);
+ }
+ }
+ soisdisconnecting(so);
+ 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(__Windows__) || 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__) && __FreeBSD_version >= 502115) || defined(__Windows__)
+ SOCKBUF_LOCK(&so->so_rcv);
+ so->so_rcv.sb_state &= ~SBS_CANTRCVMORE;
+ SOCKBUF_UNLOCK(&so->so_rcv);
+#else
+ so->so_state &= ~SS_CANTRCVMORE;
+#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);
+ }
+ /*
+ * 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.
+ */
+ {
+ struct sctp_tcb *stcb;
+ struct sctp_association *asoc;
+
+ 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 (TAILQ_EMPTY(&asoc->send_queue) &&
+ TAILQ_EMPTY(&asoc->sent_queue) &&
+ (asoc->stream_queue_cnt == 0)) {
+ if (asoc->locked_on_sending) {
+ goto abort_anyway;
+ }
+ /* there is nothing queued to send, so I'm done... */
+ if (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) {
+ /* only send SHUTDOWN the first time through */
+ struct sctp_nets *netp;
+
+ 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_CLEAR_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ 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, netp);
+ 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
+ */
+ struct sctp_nets *netp;
+ if (stcb->asoc.alternate) {
+ netp = stcb->asoc.alternate;
+ } else {
+ netp = stcb->asoc.primary_destination;
+ }
+
+ asoc->state |= SCTP_STATE_SHUTDOWN_PENDING;
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb,
+ netp);
+
+ if (asoc->locked_on_sending) {
+ /* Locked to send out the data */
+ struct sctp_stream_queue_pending *sp;
+ sp = TAILQ_LAST(&asoc->locked_on_sending->outqueue, sctp_streamhead);
+ if (sp == NULL) {
+ SCTP_PRINTF("Error, sp is NULL, locked on sending is non-null strm:%d\n",
+ asoc->locked_on_sending->stream_no);
+ } else {
+ if ((sp->length == 0) && (sp-> msg_is_complete == 0)) {
+ asoc->state |= 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_abort_an_association(stcb->sctp_ep, stcb,
+ op_err, SCTP_SO_LOCKED);
+ goto skip_unlock;
+ } else {
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CLOSING, SCTP_SO_LOCKED);
+ }
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ skip_unlock:
+ SCTP_INP_RUNLOCK(inp);
+ 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_storage *ss, struct sockaddr *sa)
+{
+#ifdef INET6
+#if defined(SCTP_EMBEDDED_V6_SCOPE)
+ struct sockaddr_in6 lsa6;
+
+ sa = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)sa,
+ &lsa6);
+#endif
+#endif
+#ifdef HAVE_SA_LEN
+ memcpy(ss, sa, sa->sa_len);
+#else
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ memcpy(ss, sa, sizeof(struct sockaddr_in));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ memcpy(ss, sa, sizeof(struct sockaddr_in6));
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ memcpy(ss, sa, 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_storage *sas,
+ 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__)
+ 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)) {
+ in6_sin_2_v4mapsin6(sin, (struct sockaddr_in6 *)sas);
+ ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport;
+ sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(struct sockaddr_in6));
+ actual += sizeof(struct sockaddr_in6);
+ } else {
+#endif
+ memcpy(sas, sin, sizeof(*sin));
+ ((struct sockaddr_in *)sas)->sin_port = inp->sctp_lport;
+ sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(*sin));
+ actual += sizeof(*sin);
+#ifdef INET6
+ }
+#endif
+ if (actual >= limit) {
+ return (actual);
+ }
+ } 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__)
+ 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;
+ }
+ memcpy(sas, sin6, sizeof(*sin6));
+ ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport;
+ sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(*sin6));
+ actual += sizeof(*sin6);
+ if (actual >= limit) {
+ return (actual);
+ }
+ } else {
+ continue;
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (conn_addr_legal) {
+ memcpy(sas, &sctp_ifa->address.sconn, sizeof(struct sockaddr_conn));
+ ((struct sockaddr_conn *)sas)->sconn_port = inp->sctp_lport;
+ sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(struct sockaddr_conn));
+ actual += sizeof(struct sockaddr_conn);
+ if (actual >= limit) {
+ return (actual);
+ }
+ } else {
+ continue;
+ }
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ }
+ }
+ } else {
+ struct sctp_laddr *laddr;
+#ifndef HAVE_SA_LEN
+ uint32_t sa_len = 0;
+#endif
+
+ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+ if (stcb) {
+ if (sctp_is_addr_restricted(stcb, laddr->ifa)) {
+ continue;
+ }
+ }
+ if (sctp_fill_user_address(sas, &laddr->ifa->address.sa))
+ continue;
+ switch (laddr->ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ ((struct sockaddr_in *)sas)->sin_port = inp->sctp_lport;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ ((struct sockaddr_conn *)sas)->sconn_port = inp->sctp_lport;
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+#ifdef HAVE_SA_LEN
+ sas = (struct sockaddr_storage *)((caddr_t)sas +
+ laddr->ifa->address.sa.sa_len);
+ actual += 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 */
+ break;
+ }
+ sas = (struct sockaddr_storage *)((caddr_t)sas + sa_len);
+ actual += sa_len;
+#endif
+ if (actual >= limit) {
+ return (actual);
+ }
+ }
+ }
+ return (actual);
+}
+
+static size_t
+sctp_fill_up_addresses(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ size_t limit,
+ struct sockaddr_storage *sas)
+{
+ 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, sas,
+ inp->m_vrf_ids[id]);
+ sas = (struct sockaddr_storage *)((caddr_t)sas + size);
+ }
+#else
+ /* fill up addresses for the endpoint's default vrf */
+ size = sctp_fill_up_addresses_vrf(inp, stcb, limit, sas,
+ 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 = 0;
+ int creat_lock_on = 0;
+ struct sctp_tcb *stcb = NULL;
+ struct sockaddr *sa;
+ int num_v6 = 0, num_v4 = 0, *totaddrp, totaddr;
+ uint32_t vrf_id;
+ int bad_addresses = 0;
+ 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 = (int *)optval;
+ totaddr = *totaddrp;
+ sa = (struct sockaddr *)(totaddrp + 1);
+ stcb = sctp_connectx_helper_find(inp, sa, &totaddr, &num_v4, &num_v6, &error, (optsize - sizeof(int)), &bad_addresses);
+ if ((stcb != NULL) || bad_addresses) {
+ /* Already have or am bring up an association */
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ creat_lock_on = 0;
+ if (stcb)
+ SCTP_TCB_UNLOCK(stcb);
+ if (bad_addresses == 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ }
+ 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)) {
+ struct in6pcb *inp6;
+
+ inp6 = (struct in6pcb *)inp;
+ if (SCTP_IPV6_V6ONLY(inp6)) {
+ /*
+ * 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,
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ (struct thread *)p
+#elif defined(__Windows__)
+ (PKTHREAD)p
+#else
+ (struct proc *)p
+#endif
+ );
+ 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->asoc, 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) {
+ (void)sctp_free_assoc(inp, stcb, SCTP_PCBFREE_FORCE, SCTP_FROM_SCTP_USRREQ+SCTP_LOC_6);
+ goto out_now;
+ }
+ a_id = (sctp_assoc_t *)optval;
+ *a_id = sctp_get_associd(stcb);
+
+ /* initialize authentication parameters for the assoc */
+ sctp_initialize_auth_params(inp, 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);
+ 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);
+ }
+ 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(__Panda__) || 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 = TICKS_TO_SEC(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_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) ||
+ (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) ||
+ (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) ||
+ (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);
+ cnt = 0;
+ SCTP_INP_RLOCK(inp);
+ 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;
+ unsigned int at, limit;
+
+ SCTP_CHECK_AND_CAST(ids, optval, struct sctp_assoc_ids, *optsize);
+ at = 0;
+ limit = (*optsize-sizeof(uint32_t))/ sizeof(sctp_assoc_t);
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ if (at < limit) {
+ ids->gaids_assoc_id[at++] = sctp_get_associd(stcb);
+ } 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) ||
+ (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;
+ *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) ||
+ (sack->sack_assoc_id == SCTP_FUTURE_ASSOC)) {
+ SCTP_INP_RLOCK(inp);
+ sack->sack_delay = TICKS_TO_MSEC(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:
+ {
+#if defined(__FreeBSD__) && __FreeBSD_version < 900000
+ uint8_t *value;
+
+ SCTP_CHECK_AND_CAST(value, optval, uint8_t, *optsize);
+
+ SCTP_INP_RLOCK(inp);
+ if (inp->sctp_ep.max_burst < 256) {
+ *value = inp->sctp_ep.max_burst;
+ } else {
+ *value = 255;
+ }
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(uint8_t);
+#else
+ 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) ||
+ (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);
+ }
+#endif
+ 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) ||
+ (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_storage *sas;
+ 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) - sizeof(struct sctp_getaddresses);
+ *optsize = sizeof(struct sctp_getaddresses);
+ sas = (struct sockaddr_storage *)&saddr->addr[0];
+
+ 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 *)sas);
+ } else {
+ memcpy(sas, &net->ro._l_addr, cpsz);
+ }
+#else
+ memcpy(sas, &net->ro._l_addr, cpsz);
+#endif
+ ((struct sockaddr_in *)sas)->sin_port = stcb->rport;
+
+ sas = (struct sockaddr_storage *)((caddr_t)sas + 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 sockaddr_storage *sas;
+ struct sctp_getaddresses *saddr;
+
+ SCTP_CHECK_AND_CAST(saddr, optval, struct sctp_getaddresses, *optsize);
+ SCTP_FIND_STCB(inp, stcb, saddr->sget_assoc_id);
+
+ sas = (struct sockaddr_storage *)&saddr->addr[0];
+ limit = *optsize - sizeof(sctp_assoc_t);
+ actual = sctp_fill_up_addresses(inp, stcb, limit, sas);
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ *optsize = sizeof(struct sockaddr_storage) + 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) {
+ int ovh;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ ovh = SCTP_MED_OVERHEAD;
+ } else {
+ ovh = SCTP_MED_V4_OVERHEAD;
+ }
+
+ paddrp->spp_hbinterval = net->heart_beat_delay;
+ paddrp->spp_pathmaxrxt = net->failure_threshold;
+ paddrp->spp_pathmtu = net->mtu - ovh;
+ /* 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_ENABLE;
+ } else {
+ paddrp->spp_flags |= SPP_PMTUD_DISABLE;
+ }
+ 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 = sctp_get_frag_point(stcb, &stcb->asoc);
+ 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) ||
+ (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 = TICKS_TO_MSEC(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
+ /* can't return this */
+ paddrp->spp_pathmtu = 0;
+
+ 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;
+ 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;
+ }
+ /*
+ * I think passing the state is fine since
+ * sctp_constants.h will be available to the user
+ * land.
+ */
+ sstat->sstat_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);
+#ifdef HAVE_SA_LEN
+ memcpy(&sstat->sstat_primary.spinfo_address,
+ &stcb->asoc.primary_destination->ro._l_addr,
+ ((struct sockaddr *)(&stcb->asoc.primary_destination->ro._l_addr))->sa_len);
+#else
+ if (stcb->asoc.primary_destination->ro._l_addr.sa.sa_family == AF_INET) {
+ memcpy(&sstat->sstat_primary.spinfo_address,
+ &stcb->asoc.primary_destination->ro._l_addr,
+ sizeof(struct sockaddr_in));
+ } else {
+ memcpy(&sstat->sstat_primary.spinfo_address,
+ &stcb->asoc.primary_destination->ro._l_addr,
+ sizeof(struct sockaddr_in6));
+ }
+#endif
+ net = stcb->asoc.primary_destination;
+ ((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;
+ 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) ||
+ (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 = TICKS_TO_MSEC(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) ||
+ (sasoc->sasoc_assoc_id == SCTP_FUTURE_ASSOC)) {
+ SCTP_INP_RLOCK(inp);
+ sasoc->sasoc_cookie_life = TICKS_TO_MSEC(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) ||
+ (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) ||
+ (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) ||
+ (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);
+ SCTP_TCB_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ (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 (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) ||
+ (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) ||
+ (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;
+ } else {
+ thlds->spt_pathmaxrxt = stcb->asoc.def_net_failure;
+ thlds->spt_pathpfthld = stcb->asoc.def_net_pf_threshold;
+ }
+ 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) ||
+ (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;
+ 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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_prstatus);
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ }
+ 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];
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ *optsize = sizeof(struct sctp_prstatus);
+ } 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;
+
+ 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) ||
+ (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(__Panda__) || 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_PRINTF("optval is NULL\n");
+ 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_PRINTF("inp is NULL?\n");
+ 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 = SEC_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_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) ||
+ (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 ((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) ||
+ (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 ((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 (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) {
+ 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_UNLOCK(stcb);
+ } else {
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
+ (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 ((av->assoc_id == SCTP_CURRENT_ASSOC) ||
+ (av->assoc_id == SCTP_ALL_ASSOC)) {
+ SCTP_INP_RLOCK(inp);
+ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
+ SCTP_TCB_LOCK(stcb);
+ stcb->asoc.ss_functions.sctp_ss_clear(stcb, &stcb->asoc, 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_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 (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) ||
+ (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 ((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)
+ sack->sack_delay = SCTP_MAX_SACK_DELAY;
+ if (MSEC_TO_TICKS(sack->sack_delay) < 1) {
+ sack->sack_delay = TICKS_TO_MSEC(1);
+ }
+ }
+ 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) ||
+ (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] = MSEC_TO_TICKS(sack->sack_delay);
+ }
+ if (sack->sack_freq) {
+ inp->sctp_ep.sctp_sack_freq = sack->sack_freq;
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if ((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;
+ }
+ 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) ||
+ (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 ((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) ||
+ (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 ((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) ||
+ (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 ((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) ||
+ (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 ((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) ||
+ (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 ((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 (sizeof(struct sctp_reset_streams) +
+ strrst->srs_number_streams * sizeof(uint16_t) > optsize) {
+ 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 (strrst->srs_flags & SCTP_STREAM_RESET_INCOMING) {
+ send_in = 1;
+ }
+ if (strrst->srs_flags & SCTP_STREAM_RESET_OUTGOING) {
+ send_out = 1;
+ }
+ 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;
+ }
+ error = sctp_send_str_reset_req(stcb, strrst->srs_number_streams,
+ strrst->srs_stream_list,
+ send_out, send_in, 0, 0, 0, 0, 0);
+
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_REQ, SCTP_SO_LOCKED);
+ 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 (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, 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:
+ {
+ 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 (stcb->asoc.stream_reset_outstanding) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EALREADY);
+ error = EALREADY;
+ SCTP_TCB_UNLOCK(stcb);
+ break;
+ }
+ error = sctp_send_str_reset_req(stcb, 0, NULL, 0, 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_9);
+ 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:
+ {
+#if defined(__FreeBSD__) && __FreeBSD_version < 900000
+ uint8_t *burst;
+
+ SCTP_CHECK_AND_CAST(burst, optval, uint8_t, optsize);
+
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_ep.max_burst = *burst;
+ SCTP_INP_WUNLOCK(inp);
+#else
+ 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) ||
+ (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 ((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);
+ }
+ }
+#endif
+ 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) ||
+ (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) ||
+ (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 ((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 (stcb != NULL) {
+ /************************TCB SPECIFIC SET ******************/
+ /*
+ * do we change the timer for HB, we run
+ * only one?
+ */
+ int ovh = 0;
+
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ ovh = SCTP_MED_OVERHEAD;
+ } else {
+ ovh = SCTP_MED_V4_OVERHEAD;
+ }
+
+ /* network sets ? */
+ 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_10);
+ }
+ 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) {
+ /* on demand HB */
+ 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) && (paddrp->spp_pathmtu >= SCTP_SMALLEST_PMTU)) {
+ 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_10);
+ }
+ net->dest_state |= SCTP_ADDR_NO_PMTUD;
+ net->mtu = paddrp->spp_pathmtu + ovh;
+ 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_TIMER + SCTP_LOC_3);
+ 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_TIMER + SCTP_LOC_3);
+ 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_10);
+ 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_10);
+ }
+ }
+ }
+ sctp_stcb_feature_on(inp, stcb, SCTP_PCB_FLAGS_DONOT_HEARTBEAT);
+ }
+ if ((paddrp->spp_flags & SPP_PMTUD_DISABLE) && (paddrp->spp_pathmtu >= SCTP_SMALLEST_PMTU)) {
+ 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_10);
+ }
+ net->dest_state |= SCTP_ADDR_NO_PMTUD;
+ net->mtu = paddrp->spp_pathmtu + ovh;
+ if (net->mtu < stcb->asoc.smallest_mtu) {
+ sctp_pathmtu_adjustment(stcb, net->mtu);
+ }
+ }
+ 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;
+ }
+ 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) ||
+ (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] = MSEC_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] = MSEC_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) {
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_DO_NOT_PMTUD);
+ } else if (paddrp->spp_flags & SPP_PMTUD_DISABLE) {
+ 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) ||
+ (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 = MSEC_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) ||
+ (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 = MSEC_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) &&
+ (!(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;
+ }
+ 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(__Windows__) && !defined(__Userspace__)
+#if defined(__APPLE__)
+ struct proc *proc;
+#endif
+#ifdef __FreeBSD__
+#if __FreeBSD_version > 602000
+ error = priv_check(curthread,
+ PRIV_NETINET_RESERVEDPORT);
+#elif __FreeBSD_version >= 500000
+ error = suser((struct thread *)p);
+#else
+ error = suser(p);
+#endif
+#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
+#endif
+ if (error)
+ break;
+
+ 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",
+ __FUNCTION__);
+ 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__)
+ } 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;
+ }
+ 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 sctp_getaddresses *addrs;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ struct thread *td;
+
+ td = (struct thread *)p;
+#endif
+ SCTP_CHECK_AND_CAST(addrs, optval, struct sctp_getaddresses,
+ optsize);
+#ifdef INET
+ if (addrs->addr->sa_family == AF_INET) {
+ if (optsize < sizeof(struct sctp_getaddresses) - sizeof(struct sockaddr) + sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ if (td != NULL && (error = prison_local_ip4(td->td_ucred, &(((struct sockaddr_in *)(addrs->addr))->sin_addr)))) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+#endif
+ } else
+#endif
+#ifdef INET6
+ if (addrs->addr->sa_family == AF_INET6) {
+ if (optsize < sizeof(struct sctp_getaddresses) - sizeof(struct sockaddr) + sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ if (td != NULL && (error = prison_local_ip6(td->td_ucred, &(((struct sockaddr_in6 *)(addrs->addr))->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, addrs->addr,
+ addrs->sget_assoc_id, vrf_id,
+ &error, p);
+ break;
+ }
+ case SCTP_BINDX_REM_ADDR:
+ {
+ struct sctp_getaddresses *addrs;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ struct thread *td;
+ td = (struct thread *)p;
+
+#endif
+ SCTP_CHECK_AND_CAST(addrs, optval, struct sctp_getaddresses, optsize);
+#ifdef INET
+ if (addrs->addr->sa_family == AF_INET) {
+ if (optsize < sizeof(struct sctp_getaddresses) - sizeof(struct sockaddr) + sizeof(struct sockaddr_in)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ if (td != NULL && (error = prison_local_ip4(td->td_ucred, &(((struct sockaddr_in *)(addrs->addr))->sin_addr)))) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_USRREQ, error);
+ break;
+ }
+#endif
+ } else
+#endif
+#ifdef INET6
+ if (addrs->addr->sa_family == AF_INET6) {
+ if (optsize < sizeof(struct sctp_getaddresses) - sizeof(struct sockaddr) + sizeof(struct sockaddr_in6)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ break;
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ if (td != NULL &&
+ (error = prison_local_ip6(td->td_ucred,
+ &(((struct sockaddr_in6 *)(addrs->addr))->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, addrs->addr,
+ addrs->sget_assoc_id, vrf_id,
+ &error);
+ break;
+ }
+#ifdef __APPLE__
+ 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 /* __APPLE__ */
+ 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_TCPTYPE) == 0) &&
+ ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) &&
+ ((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) ||
+ (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 ((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);
+ }
+ }
+ }
+ 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) ||
+ (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 ((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) {
+ 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) ||
+ (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 ((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 (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_TIMER + SCTP_LOC_3);
+ 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_TIMER + SCTP_LOC_3);
+ 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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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) ||
+ (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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+int
+sctp_ctloutput(struct socket *so, struct sockopt *sopt)
+{
+ void *optval = NULL;
+ size_t optsize = 0;
+ void *p;
+ int error = 0;
+
+ 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_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__) && __FreeBSD_version >= 500000) || defined(__Windows__)
+ p = (void *)sopt->sopt_td;
+#else
+ p = (void *)sopt->sopt_p;
+#endif
+ if (sopt->sopt_dir == SOPT_SET) {
+ error = sctp_setopt(so, sopt->sopt_name, optval, optsize, p);
+ } 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(__FreeBSD__) && __FreeBSD_version >= 500000
+static int
+sctp_connect(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+#else
+#if defined(__FreeBSD__) || defined(__APPLE__)
+static int
+sctp_connect(struct socket *so, struct sockaddr *addr, struct proc *p)
+{
+#elif defined(__Panda__) || defined(__Userspace__)
+int
+sctp_connect(struct socket *so, struct sockaddr *addr)
+{
+ void *p = NULL;
+#elif defined(__Windows__)
+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
+#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(__Windows__) && !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows)
+ switch (addr->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ struct sockaddr_in6 *sin6p;
+
+#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__) && __FreeBSD_version >= 800000
+ sin6p = (struct sockaddr_in6 *)addr;
+ if (p != NULL && (error = prison_remote_ip6(p->td_ucred, &sin6p->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__) && __FreeBSD_version >= 800000
+ struct sockaddr_in *sinp;
+
+#endif
+#if !defined(__Userspace_os_Windows)
+ 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__) && __FreeBSD_version >= 800000
+ sinp = (struct sockaddr_in *)addr;
+ if (p != NULL && (error = prison_remote_ip4(p->td_ucred, &sinp->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 ((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, p);
+ 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->asoc, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+
+ /* initialize authentication parameters for the assoc */
+ sctp_initialize_auth_params(inp, stcb);
+
+ 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
+
+#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, p);
+ 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->asoc, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+
+ /* initialize authentication parameters for the assoc */
+ sctp_initialize_auth_params(inp, stcb);
+
+ 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(__FreeBSD__) && __FreeBSD_version >= 500000
+#if __FreeBSD_version >= 700000
+sctp_listen(struct socket *so, int backlog, struct thread *p)
+#else
+sctp_listen(struct socket *so, struct thread *p)
+#endif
+#elif defined(__Windows__)
+sctp_listen(struct socket *so, int backlog, PKTHREAD p)
+#elif defined(__Userspace__)
+sctp_listen(struct socket *so, int backlog, struct proc *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) &&
+ (tinp->sctp_socket->so_qlimit)) {
+ /* 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) &&
+ (tinp->sctp_socket->so_qlimit)) {
+ /* we have a listener already and its not this inp. */
+ SCTP_INP_DECR_REF(tinp);
+ return (EADDRINUSE);
+ } else if (tinp) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ }
+ }
+ 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
+ SOCK_LOCK(so);
+#if (defined(__FreeBSD__) && __FreeBSD_version > 500000) || defined(__Userspace__)
+ 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);
+ }
+ }
+ SOCK_LOCK(so);
+#if (defined(__FreeBSD__) && __FreeBSD_version > 500000) || defined(__Windows__) || defined(__Userspace__)
+#if __FreeBSD_version >= 700000 || defined(__Windows__) || defined(__Userspace__)
+ /* It appears for 7.0 and on, we must always call this. */
+ solisten_proto(so, backlog);
+#else
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) == 0) {
+ solisten_proto(so);
+ }
+#endif
+#endif
+ 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
+ }
+
+#if __FreeBSD_version >= 700000 || defined(__Windows__) || defined(__Userspace__)
+ if (backlog == 0) {
+ /* turning off listen */
+#if defined(__Userspace__)
+ so->so_options &= ~SCTP_SO_ACCEPTCONN;
+#else
+ so->so_options &= ~SO_ACCEPTCONN;
+#endif
+ }
+#endif
+ SOCK_UNLOCK(so);
+ return (error);
+}
+
+static int sctp_defered_wakeup_cnt = 0;
+
+int
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+sctp_accept(struct socket *so, struct sockaddr **addr)
+{
+#elif defined(__Panda__)
+sctp_accept(struct socket *so, struct sockaddr *addr, int *namelen,
+ void *accept_info, int *accept_info_len)
+{
+#else
+sctp_accept(struct socket *so, struct mbuf *nam)
+{
+ struct sockaddr *addr = mtod(nam, struct sockaddr *);
+#endif
+ struct sctp_tcb *stcb;
+ struct sctp_inpcb *inp;
+ union sctp_sockstore store;
+#ifdef INET6
+#ifdef SCTP_KAME
+ int error;
+#endif /* SCTP_KAME */
+#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_RLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) {
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ return (EOPNOTSUPP);
+ }
+ if (so->so_state & SS_ISDISCONNECTED) {
+ SCTP_INP_RUNLOCK(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_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (ECONNRESET);
+ }
+ SCTP_TCB_LOCK(stcb);
+ SCTP_INP_RUNLOCK(inp);
+ store = stcb->asoc.primary_destination->ro._l_addr;
+ stcb->asoc.state &= ~SCTP_STATE_IN_ACCEPT_QUEUE;
+ SCTP_TCB_UNLOCK(stcb);
+ switch (store.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin);
+ if (sin == NULL)
+ return (ENOMEM);
+#else
+ sin = (struct sockaddr_in *)addr;
+ bzero((caddr_t)sin, sizeof(*sin));
+#endif
+ 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;
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ *addr = (struct sockaddr *)sin;
+#elif !defined(__Panda__)
+ SCTP_BUF_LEN(nam) = sizeof(*sin);
+#endif
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6);
+ if (sin6 == NULL)
+ return (ENOMEM);
+#else
+ sin6 = (struct sockaddr_in6 *)addr;
+ bzero((caddr_t)sin6, sizeof(*sin6));
+#endif
+ 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 */
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ *addr = (struct sockaddr *)sin6;
+#elif !defined(__Panda__)
+ SCTP_BUF_LEN(nam) = sizeof(*sin6);
+#endif
+ 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;
+ }
+ /* Wake any delayed sleep action */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) {
+ SCTP_INP_WLOCK(inp);
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_DONT_WAKE;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_WAKEOUTPUT) {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEOUTPUT;
+ SCTP_INP_WUNLOCK(inp);
+ 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(__Windows__) || 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);
+ }
+ SCTP_INP_WLOCK(inp);
+ }
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_WAKEINPUT) {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEINPUT;
+ SCTP_INP_WUNLOCK(inp);
+ 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(__Windows__) || 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_WLOCK(inp);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ }
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SCTP_TCB_LOCK(stcb);
+ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_USRREQ+SCTP_LOC_7);
+ }
+ return (0);
+}
+
+#ifdef INET
+int
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+sctp_ingetaddr(struct socket *so, struct sockaddr **addr)
+{
+ struct sockaddr_in *sin;
+#elif defined(__Panda__)
+sctp_ingetaddr(struct socket *so, struct sockaddr *addr)
+{
+ struct sockaddr_in *sin = (struct sockaddr_in *)addr;
+#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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin);
+ if (sin == NULL)
+ return (ENOMEM);
+#elif defined(__Panda__)
+ bzero(sin, sizeof(*sin));
+#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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ (*addr) = (struct sockaddr *)sin;
+#endif
+ return (0);
+}
+
+int
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+sctp_peeraddr(struct socket *so, struct sockaddr **addr)
+{
+ struct sockaddr_in *sin;
+#elif defined(__Panda__)
+sctp_peeraddr(struct socket *so, struct sockaddr *addr)
+{
+ struct sockaddr_in *sin = (struct sockaddr_in *)addr;
+#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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin);
+ if (sin == NULL)
+ return (ENOMEM);
+#elif defined(__Panda__)
+ memset(sin, 0, sizeof(*sin));
+#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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ SCTP_FREE_SONAME(sin);
+#endif
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ return (ENOENT);
+ }
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ (*addr) = (struct sockaddr *)sin;
+#endif
+ return (0);
+}
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+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,
+#if __FreeBSD_version >= 690000
+ .pru_close = sctp_close,
+ .pru_detach = sctp_close,
+ .pru_sopoll = sopoll_generic,
+ .pru_flush = sctp_flush,
+#else
+ .pru_detach = sctp_detach,
+ .pru_sopoll = sopoll,
+#endif
+ .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(__Windows__)
+ 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(__Panda__) && !defined(__Userspace__)
+int
+sctp_usrreq(so, req, m, nam, control)
+ struct socket *so;
+ int req;
+ struct mbuf *m, *nam, *control;
+{
+ struct proc *p = curproc;
+ uint32_t vrf_id;
+ struct sctp_vrf *vrf;
+ 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);
+}
+#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..fcc954ebdc
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_var.h
@@ -0,0 +1,506 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_var.h 275427 2014-12-02 20:29:29Z tuexen $");
+#endif
+
+#ifndef _NETINET_SCTP_VAR_H_
+#define _NETINET_SCTP_VAR_H_
+
+#include <netinet/sctp_uio.h>
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+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
+ */
+
+#define sctp_free_a_readq(_stcb, _readq) { \
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), (_readq)); \
+ SCTP_DECR_READQ_COUNT(); \
+}
+
+#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__) && __FreeBSD_version > 500000
+
+#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); \
+ (void)SCTP_OS_TIMER_STOP(&(__net)->pmtu_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); \
+ } \
+ 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); \
+ (void)SCTP_OS_TIMER_STOP(&(__net)->pmtu_timer.timer); \
+ (void)SCTP_OS_TIMER_STOP(&(__net)->hb_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(); \
+ } \
+ } \
+}
+
+#if defined(__Panda__)
+#define sctp_sbfree(ctl, stcb, sb, m) { \
+ if ((sb)->sb_cc >= (uint32_t)SCTP_BUF_LEN((m))) { \
+ atomic_subtract_int(&(sb)->sb_cc, SCTP_BUF_LEN((m))); \
+ } else { \
+ (sb)->sb_cc = 0; \
+ } \
+ if (((ctl)->do_not_ref_stcb == 0) && stcb) { \
+ if ((stcb)->asoc.sb_cc >= (uint32_t)SCTP_BUF_LEN((m))) { \
+ atomic_subtract_int(&(stcb)->asoc.sb_cc, SCTP_BUF_LEN((m))); \
+ } else { \
+ (stcb)->asoc.sb_cc = 0; \
+ } \
+ } \
+}
+
+#define sctp_sballoc(stcb, sb, m) { \
+ atomic_add_int(&(sb)->sb_cc, SCTP_BUF_LEN((m))); \
+ if (stcb) { \
+ atomic_add_int(&(stcb)->asoc.sb_cc, SCTP_BUF_LEN((m))); \
+ } \
+}
+
+#else
+
+#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
+#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_seq; \
+ 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_seq; \
+ 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__) && __FreeBSD_version > 690000) || defined(__Windows__) || defined(__Userspace__)
+void sctp_close(struct socket *so);
+#else
+int sctp_detach(struct socket *so);
+#endif
+int sctp_disconnect(struct socket *so);
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+#if defined(__FreeBSD__) && __FreeBSD_version < 902000
+void sctp_ctlinput __P((int, struct sockaddr *, void *));
+int sctp_ctloutput __P((struct socket *, struct sockopt *));
+#ifdef INET
+void sctp_input_with_port __P((struct mbuf *, int, uint16_t));
+void sctp_input __P((struct mbuf *, int));
+#endif
+void sctp_pathmtu_adjustment __P((struct sctp_tcb *, uint16_t));
+#else
+void sctp_ctlinput(int, struct sockaddr *, void *);
+int sctp_ctloutput(struct socket *, struct sockopt *);
+#ifdef INET
+void sctp_input_with_port(struct mbuf *, int, uint16_t);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 1100020
+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);
+#endif
+#else
+#if defined(__Panda__)
+void sctp_input(pakhandle_type i_pak);
+#elif 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
+#if defined(__FreeBSD__) && __FreeBSD_version < 902000
+void sctp_drain __P((void));
+#else
+void sctp_drain(void);
+#endif
+#if defined(__Userspace__)
+void sctp_init(uint16_t,
+ int (*)(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df),
+ void (*)(const char *, ...));
+#elif defined(__FreeBSD__) && __FreeBSD_version < 902000
+void sctp_init __P((void));
+#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);
+#endif
+void sctp_finish(void);
+#if defined(__FreeBSD__) || defined(__Windows__) || defined(__Userspace__)
+int sctp_flush(struct socket *, int);
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version < 902000
+int sctp_shutdown __P((struct socket *));
+void sctp_notify __P((struct sctp_inpcb *, struct ip *ip, struct sctphdr *,
+ struct sockaddr *, struct sctp_tcb *,
+ struct sctp_nets *));
+#else
+int sctp_shutdown(struct socket *);
+void sctp_notify(struct sctp_inpcb *, struct ip *ip, struct sctphdr *,
+ struct sockaddr *, struct sctp_tcb *,
+ struct sctp_nets *);
+#endif
+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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+int sctp_ingetaddr(struct socket *, struct sockaddr **);
+#elif defined(__Panda__)
+int sctp_ingetaddr(struct socket *, struct sockaddr *);
+#else
+int sctp_ingetaddr(struct socket *, struct mbuf *);
+#endif
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+int sctp_peeraddr(struct socket *, struct sockaddr **);
+#elif defined(__Panda__)
+int sctp_peeraddr(struct socket *, struct sockaddr *);
+#else
+int sctp_peeraddr(struct socket *, struct mbuf *);
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+#if __FreeBSD_version >= 700000
+int sctp_listen(struct socket *, int, struct thread *);
+#else
+int sctp_listen(struct socket *, struct thread *);
+#endif
+#elif defined(__Windows__)
+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
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+int sctp_accept(struct socket *, struct sockaddr **);
+#elif defined(__Panda__)
+int sctp_accept(struct socket *, struct sockaddr *, int *, void *, int *);
+#else
+int sctp_accept(struct socket *, struct mbuf *);
+#endif
+
+#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..b72a20de37
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctputil.c
@@ -0,0 +1,7920 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctputil.c 280439 2015-03-24 14:51:46Z 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>/* for sctp_deliver_data() */
+#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__)
+#include <netinet/udp.h>
+#include <netinet/udp_var.h>
+#include <sys/proc.h>
+#endif
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 8
+#endif
+
+#if defined(__Windows__)
+#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 struct sctp_cc_functions sctp_cc_functions[];
+extern struct sctp_ss_functions sctp_ss_functions[];
+
+void
+sctp_sblog(struct sockbuf *sb, struct sctp_tcb *stcb, int from, int incr)
+{
+#if defined(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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__)
+ /* 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(__FreeBSD__) || 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 = control->sinfo_ssn;
+ 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 = poschk->sinfo_ssn;
+ } 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(__FreeBSD__) || 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
+}
+
+#ifndef __APPLE__
+void
+sctp_log_lock(struct sctp_inpcb *inp, struct sctp_tcb *stcb, uint8_t from)
+{
+#if defined(__FreeBSD__) || 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__) && __FreeBSD_version >= 503000) || (defined(__APPLE__))
+ 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;
+ }
+#if (defined(__FreeBSD__) && __FreeBSD_version <= 602000)
+ sctp_clog.x.lock.info_lock = mtx_owned(&SCTP_BASE_INFO(ipi_ep_mtx));
+#else
+ sctp_clog.x.lock.info_lock = rw_wowned(&SCTP_BASE_INFO(ipi_ep_mtx));
+#endif
+ 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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(__FreeBSD__) || 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, int sendlen)
+{
+#if defined(__FreeBSD__) || 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 = 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
+
+/*
+ * 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_association *asoc;
+ struct sctp_nets *net;
+
+ asoc = &stcb->asoc;
+
+ (void)SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer);
+ TAILQ_FOREACH(net, &asoc->nets, sctp_next) {
+ (void)SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer);
+ (void)SCTP_OS_TIMER_STOP(&net->hb_timer.timer);
+ }
+}
+
+/*
+ * a list of sizes based on typical mtu's, used only if next hop size not
+ * returned.
+ */
+static uint32_t sctp_mtu_sizes[] = {
+ 68,
+ 296,
+ 508,
+ 512,
+ 544,
+ 576,
+ 1006,
+ 1492,
+ 1500,
+ 1536,
+ 2002,
+ 2048,
+ 4352,
+ 4464,
+ 8166,
+ 17914,
+ 32000,
+ 65535
+};
+
+/*
+ * Return the largest MTU smaller than val. If there is no
+ * entry, just return val.
+ */
+uint32_t
+sctp_get_prev_mtu(uint32_t val)
+{
+ uint32_t i;
+
+ 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;
+ }
+ }
+ return (sctp_mtu_sizes[i - 1]);
+}
+
+/*
+ * Return the smallest MTU larger than val. If there is no
+ * entry, just return val.
+ */
+uint32_t
+sctp_get_next_mtu(uint32_t val)
+{
+ /* select another MTU that is just bigger than this one */
+ uint32_t i;
+
+ for (i = 0; i < (sizeof(sctp_mtu_sizes) / sizeof(uint32_t)); i++) {
+ if (val < sctp_mtu_sizes[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;
+ (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);
+ 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);
+}
+
+int
+sctp_init_asoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ uint32_t override_tag, uint32_t vrf_id)
+{
+ 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->asoc, 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 = TICKS_TO_MSEC(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->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->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 = TICKS_TO_MSEC(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->locked_on_sending = NULL;
+ 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 =
+ inp->sctp_ep.pre_open_stream_count;
+ 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_sequence_send = 0x0;
+ 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].stream_no = i;
+ asoc->strmout[i].last_msg_incomplete = 0;
+ asoc->ss_functions.sctp_ss_init_stream(&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->reasmqueue);
+ 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)
+{
+ int iteration_count = 0;
+ int inp_skip = 0;
+ int first_in = 1;
+ struct sctp_inpcb *tinp;
+
+ 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);
+ 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);
+ 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__)
+ 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;
+ }
+
+ /* 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);
+ }
+ if (it->inp == NULL) {
+ goto done_with_iterator;
+ }
+ goto select_a_new_ep;
+}
+
+void
+sctp_iterator_worker(void)
+{
+ struct sctp_iterator *it, *nit;
+
+ /* This function is called with the WQ lock in place */
+
+ sctp_it_ctl.iterator_running = 1;
+ TAILQ_FOREACH_SAFE(it, &sctp_it_ctl.iteratorhead, sctp_nxt_itr, nit) {
+ /* now lets work on this one */
+ TAILQ_REMOVE(&sctp_it_ctl.iteratorhead, it, sctp_nxt_itr);
+ SCTP_IPI_ITERATOR_WQ_UNLOCK();
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_SET(it->vn);
+#endif
+ sctp_iterator_work(it);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ SCTP_IPI_ITERATOR_WQ_LOCK();
+#if !defined(__FreeBSD__)
+ 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;
+
+ SCTP_WQ_ADDR_LOCK();
+ 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++;
+ }
+ SCTP_WQ_ADDR_UNLOCK();
+
+ if (asc->cnt == 0) {
+ SCTP_FREE(asc, SCTP_M_ASC_IT);
+ } else {
+ (void)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);
+ }
+}
+
+void
+sctp_timeout_handler(void *t)
+{
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+ struct sctp_timer *tmr;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+#endif
+ int did_output, type;
+
+ 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__) && __FreeBSD_version >= 801000
+ 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... */
+ if (tmr->self != (void *)tmr) {
+ /*
+ * SCTP_PRINTF("Stale SCTP timer fired (%p), ignoring...\n",
+ * (void *)tmr);
+ */
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ tmr->stopped_from = 0xa001;
+ if (!SCTP_IS_TIMER_TYPE_VALID(tmr->type)) {
+ /*
+ * SCTP_PRINTF("SCTP timer fired with invalid type: 0x%x\n",
+ * tmr->type);
+ */
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ tmr->stopped_from = 0xa002;
+ if ((tmr->type != SCTP_TIMER_TYPE_ADDR_WQ) && (inp == NULL)) {
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ /* if this is an iterator timeout, get the struct and clear inp */
+ tmr->stopped_from = 0xa003;
+ type = tmr->type;
+ if (inp) {
+ SCTP_INP_INCR_REF(inp);
+ if ((inp->sctp_socket == NULL) &&
+ ((tmr->type != SCTP_TIMER_TYPE_INPKILL) &&
+ (tmr->type != SCTP_TIMER_TYPE_INIT) &&
+ (tmr->type != SCTP_TIMER_TYPE_SEND) &&
+ (tmr->type != SCTP_TIMER_TYPE_RECV) &&
+ (tmr->type != SCTP_TIMER_TYPE_HEARTBEAT) &&
+ (tmr->type != SCTP_TIMER_TYPE_SHUTDOWN) &&
+ (tmr->type != SCTP_TIMER_TYPE_SHUTDOWNACK) &&
+ (tmr->type != SCTP_TIMER_TYPE_SHUTDOWNGUARD) &&
+ (tmr->type != SCTP_TIMER_TYPE_ASOCKILL))
+ ) {
+ SCTP_INP_DECR_REF(inp);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ }
+ tmr->stopped_from = 0xa004;
+ 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);
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ }
+ tmr->stopped_from = 0xa005;
+ SCTPDBG(SCTP_DEBUG_TIMER1, "Timer type %d goes off\n", tmr->type);
+ if (!SCTP_OS_TIMER_ACTIVE(&tmr->timer)) {
+ if (inp) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ if (stcb) {
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ tmr->stopped_from = 0xa006;
+
+ if (stcb) {
+ SCTP_TCB_LOCK(stcb);
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ if ((tmr->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);
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ }
+ /* record in stopped what t-o occured */
+ tmr->stopped_from = tmr->type;
+
+ /* 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);
+
+ /* call the handler for the appropriate timer type */
+ switch (tmr->type) {
+ case SCTP_TIMER_TYPE_ZERO_COPY:
+ if (inp == NULL) {
+ break;
+ }
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE)) {
+ SCTP_ZERO_COPY_EVENT(inp, inp->sctp_socket);
+ }
+ break;
+ case SCTP_TIMER_TYPE_ZCOPY_SENDQ:
+ if (inp == NULL) {
+ break;
+ }
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE)) {
+ SCTP_ZERO_COPY_SENDQ_EVENT(inp, inp->sctp_socket);
+ }
+ break;
+ case SCTP_TIMER_TYPE_ADDR_WQ:
+ sctp_handle_addr_wq();
+ break;
+ case SCTP_TIMER_TYPE_SEND:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ 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.
+ */
+ chk = TAILQ_FIRST(&stcb->asoc.sent_queue);
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb,
+ chk->whoTo);
+ }
+ break;
+ case SCTP_TIMER_TYPE_INIT:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ 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:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ 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, net);
+#endif
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SACK_TMR, SCTP_SO_NOT_LOCKED);
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWN:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ if (sctp_shutdown_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+ SCTP_STAT_INCR(sctps_timoshutdown);
+ stcb->asoc.timoshutdown++;
+#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:
+ if ((stcb == NULL) || (inp == NULL) || (net == NULL)) {
+ break;
+ }
+ 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:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+
+ if (sctp_cookie_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+ SCTP_STAT_INCR(sctps_timocookie);
+ stcb->asoc.timocookie++;
+#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:
+ {
+ struct timeval tv;
+ int i, secret;
+ if (inp == NULL) {
+ break;
+ }
+ SCTP_STAT_INCR(sctps_timosecret);
+ (void)SCTP_GETTIME_TIMEVAL(&tv);
+ SCTP_INP_WLOCK(inp);
+ 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_INP_WUNLOCK(inp);
+ sctp_timer_start(SCTP_TIMER_TYPE_NEWCOOKIE, inp, stcb, net);
+ }
+ did_output = 0;
+ break;
+ case SCTP_TIMER_TYPE_PATHMTURAISE:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ SCTP_STAT_INCR(sctps_timopathmtu);
+ sctp_pathmtu_timer(inp, stcb, net);
+ did_output = 0;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNACK:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ 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_SHUTDOWNGUARD:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ SCTP_STAT_INCR(sctps_timoshutdownguard);
+ sctp_abort_an_association(inp, stcb, NULL, SCTP_SO_NOT_LOCKED);
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+
+ case SCTP_TIMER_TYPE_STRRESET:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ if (sctp_strreset_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+ SCTP_STAT_INCR(sctps_timostrmrst);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_TMR, SCTP_SO_NOT_LOCKED);
+ break;
+ case SCTP_TIMER_TYPE_ASCONF:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ if (sctp_asconf_timer(inp, stcb, net)) {
+ /* no need to unlock on tcb its gone */
+ goto out_decr;
+ }
+ SCTP_STAT_INCR(sctps_timoasconf);
+#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_PRIM_DELETED:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ sctp_delete_prim_timer(inp, stcb, net);
+ SCTP_STAT_INCR(sctps_timodelprim);
+ break;
+
+ case SCTP_TIMER_TYPE_AUTOCLOSE:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ SCTP_STAT_INCR(sctps_timoautoclose);
+ sctp_autoclose_timer(inp, stcb, net);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_AUTOCLOSE_TMR, SCTP_SO_NOT_LOCKED);
+ did_output = 0;
+ break;
+ case SCTP_TIMER_TYPE_ASOCKILL:
+ if ((stcb == NULL) || (inp == NULL)) {
+ break;
+ }
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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_INPKILL:
+ SCTP_STAT_INCR(sctps_timoinpkill);
+ if (inp == NULL) {
+ break;
+ }
+ /*
+ * special case, take away our increment since WE are the
+ * killer
+ */
+ SCTP_INP_DECR_REF(inp);
+ sctp_timer_stop(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL, SCTP_FROM_SCTPUTIL+SCTP_LOC_3);
+#if defined(__APPLE__)
+ SCTP_SOCKET_LOCK(SCTP_INP_SO(inp), 1);
+#endif
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_FROM_INPKILL_TIMER);
+#if defined(__APPLE__)
+ SCTP_SOCKET_UNLOCK(SCTP_INP_SO(inp), 1);
+#endif
+ inp = NULL;
+ goto out_no_decr;
+ default:
+ SCTPDBG(SCTP_DEBUG_TIMER1, "sctp_timeout_handler:unknown timer %d\n",
+ tmr->type);
+ break;
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xF1, (uint8_t) tmr->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);
+ }
+
+out_decr:
+ if (inp) {
+ SCTP_INP_DECR_REF(inp);
+ }
+
+out_no_decr:
+ SCTPDBG(SCTP_DEBUG_TIMER1, "Timer now complete (type %d)\n",
+ type);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 801000
+ CURVNET_RESTORE();
+#endif
+}
+
+void
+sctp_timer_start(int t_type, struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+ uint32_t to_ticks;
+ struct sctp_timer *tmr;
+
+ if ((t_type != SCTP_TIMER_TYPE_ADDR_WQ) && (inp == NULL))
+ return;
+
+ tmr = NULL;
+ if (stcb) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ }
+ switch (t_type) {
+ case SCTP_TIMER_TYPE_ZERO_COPY:
+ tmr = &inp->sctp_ep.zero_copy_timer;
+ to_ticks = SCTP_ZERO_COPY_TICK_DELAY;
+ break;
+ case SCTP_TIMER_TYPE_ZCOPY_SENDQ:
+ tmr = &inp->sctp_ep.zero_copy_sendq_timer;
+ to_ticks = SCTP_ZERO_COPY_SENDQ_TICK_DELAY;
+ break;
+ case SCTP_TIMER_TYPE_ADDR_WQ:
+ /* Only 1 tick away :-) */
+ tmr = &SCTP_BASE_INFO(addr_wq_timer);
+ to_ticks = SCTP_ADDRESS_TICK_DELAY;
+ break;
+ case SCTP_TIMER_TYPE_SEND:
+ /* Here we use the RTO timer */
+ {
+ int rto_val;
+
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->rxt_timer;
+ if (net->RTO == 0) {
+ rto_val = stcb->asoc.initial_rto;
+ } else {
+ rto_val = net->RTO;
+ }
+ to_ticks = MSEC_TO_TICKS(rto_val);
+ }
+ break;
+ case SCTP_TIMER_TYPE_INIT:
+ /*
+ * Here we use the INIT timer default usually about 1
+ * minute.
+ */
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->rxt_timer;
+ if (net->RTO == 0) {
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = MSEC_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 (stcb == NULL) {
+ return;
+ }
+ tmr = &stcb->asoc.dack_timer;
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.delayed_ack);
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWN:
+ /* Here we use the RTO of the destination. */
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ if (net->RTO == 0) {
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = MSEC_TO_TICKS(net->RTO);
+ }
+ tmr = &net->rxt_timer;
+ 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 ((stcb == NULL) || (net == NULL)) {
+ return;
+ } else {
+ uint32_t rndval;
+ uint32_t jitter;
+
+ if ((net->dest_state & SCTP_ADDR_NOHB) &&
+ !(net->dest_state & SCTP_ADDR_UNCONFIRMED)) {
+ return;
+ }
+ 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 = MSEC_TO_TICKS(to_ticks);
+ tmr = &net->hb_timer;
+ }
+ break;
+ case SCTP_TIMER_TYPE_COOKIE:
+ /*
+ * Here we can use the RTO timer from the network since one
+ * RTT was compelete. If a retran happened then we will be
+ * using the RTO initial value.
+ */
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ if (net->RTO == 0) {
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = MSEC_TO_TICKS(net->RTO);
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_NEWCOOKIE:
+ /*
+ * nothing needed but the endpoint here ususually about 60
+ * minutes.
+ */
+ tmr = &inp->sctp_ep.signature_change;
+ to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_SIGNATURE];
+ break;
+ case SCTP_TIMER_TYPE_ASOCKILL:
+ if (stcb == NULL) {
+ return;
+ }
+ tmr = &stcb->asoc.strreset_timer;
+ to_ticks = MSEC_TO_TICKS(SCTP_ASOC_KILL_TIMEOUT);
+ 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.
+ */
+ tmr = &inp->sctp_ep.signature_change;
+ to_ticks = MSEC_TO_TICKS(SCTP_INP_KILL_TIMEOUT);
+ break;
+ case SCTP_TIMER_TYPE_PATHMTURAISE:
+ /*
+ * Here we use the value found in the EP for PMTU ususually
+ * about 10 minutes.
+ */
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ if (net->dest_state & SCTP_ADDR_NO_PMTUD) {
+ return;
+ }
+ to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_PMTU];
+ tmr = &net->pmtu_timer;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNACK:
+ /* Here we use the RTO of the destination */
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ if (net->RTO == 0) {
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = MSEC_TO_TICKS(net->RTO);
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNGUARD:
+ /*
+ * Here we use the endpoints shutdown guard timer usually
+ * about 3 minutes.
+ */
+ if (stcb == NULL) {
+ return;
+ }
+ to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN];
+ tmr = &stcb->asoc.shut_guard_timer;
+ break;
+ case SCTP_TIMER_TYPE_STRRESET:
+ /*
+ * Here the timer comes from the stcb but its value is from
+ * the net's RTO.
+ */
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ if (net->RTO == 0) {
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = MSEC_TO_TICKS(net->RTO);
+ }
+ tmr = &stcb->asoc.strreset_timer;
+ break;
+ case SCTP_TIMER_TYPE_ASCONF:
+ /*
+ * Here the timer comes from the stcb but its value is from
+ * the net's RTO.
+ */
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ if (net->RTO == 0) {
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto);
+ } else {
+ to_ticks = MSEC_TO_TICKS(net->RTO);
+ }
+ tmr = &stcb->asoc.asconf_timer;
+ break;
+ case SCTP_TIMER_TYPE_PRIM_DELETED:
+ if ((stcb == NULL) || (net != NULL)) {
+ return;
+ }
+ to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto);
+ tmr = &stcb->asoc.delete_prim_timer;
+ break;
+ case SCTP_TIMER_TYPE_AUTOCLOSE:
+ if (stcb == NULL) {
+ return;
+ }
+ if (stcb->asoc.sctp_autoclose_ticks == 0) {
+ /*
+ * Really an error since stcb is NOT set to
+ * autoclose
+ */
+ return;
+ }
+ to_ticks = stcb->asoc.sctp_autoclose_ticks;
+ tmr = &stcb->asoc.autoclose_timer;
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_TIMER1, "%s: Unknown timer type %d\n",
+ __FUNCTION__, t_type);
+ return;
+ break;
+ }
+ if ((to_ticks <= 0) || (tmr == NULL)) {
+ SCTPDBG(SCTP_DEBUG_TIMER1, "%s: %d:software error to_ticks:%d tmr:%p not set ??\n",
+ __FUNCTION__, t_type, to_ticks, (void *)tmr);
+ return;
+ }
+ 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
+ */
+ 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;
+ tmr->net = (void *)net;
+ tmr->self = (void *)tmr;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ tmr->vnet = (void *)curvnet;
+#endif
+#ifndef __Panda__
+ tmr->ticks = sctp_get_tick_count();
+#endif
+ (void)SCTP_OS_TIMER_START(&tmr->timer, to_ticks, sctp_timeout_handler, tmr);
+ return;
+}
+
+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;
+
+ if ((t_type != SCTP_TIMER_TYPE_ADDR_WQ) &&
+ (inp == NULL))
+ return;
+
+ tmr = NULL;
+ if (stcb) {
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ }
+ switch (t_type) {
+ case SCTP_TIMER_TYPE_ZERO_COPY:
+ tmr = &inp->sctp_ep.zero_copy_timer;
+ break;
+ case SCTP_TIMER_TYPE_ZCOPY_SENDQ:
+ tmr = &inp->sctp_ep.zero_copy_sendq_timer;
+ break;
+ case SCTP_TIMER_TYPE_ADDR_WQ:
+ tmr = &SCTP_BASE_INFO(addr_wq_timer);
+ break;
+ case SCTP_TIMER_TYPE_SEND:
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_INIT:
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_RECV:
+ if (stcb == NULL) {
+ return;
+ }
+ tmr = &stcb->asoc.dack_timer;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWN:
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_HEARTBEAT:
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->hb_timer;
+ break;
+ case SCTP_TIMER_TYPE_COOKIE:
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_NEWCOOKIE:
+ /* nothing needed but the endpoint here */
+ tmr = &inp->sctp_ep.signature_change;
+ /*
+ * We re-use the newcookie timer for the INP kill timer. We
+ * must assure that we do not kill it by accident.
+ */
+ break;
+ case SCTP_TIMER_TYPE_ASOCKILL:
+ /*
+ * Stop the asoc kill timer.
+ */
+ if (stcb == NULL) {
+ return;
+ }
+ 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.
+ */
+ tmr = &inp->sctp_ep.signature_change;
+ break;
+ case SCTP_TIMER_TYPE_PATHMTURAISE:
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->pmtu_timer;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNACK:
+ if ((stcb == NULL) || (net == NULL)) {
+ return;
+ }
+ tmr = &net->rxt_timer;
+ break;
+ case SCTP_TIMER_TYPE_SHUTDOWNGUARD:
+ if (stcb == NULL) {
+ return;
+ }
+ tmr = &stcb->asoc.shut_guard_timer;
+ break;
+ case SCTP_TIMER_TYPE_STRRESET:
+ if (stcb == NULL) {
+ return;
+ }
+ tmr = &stcb->asoc.strreset_timer;
+ break;
+ case SCTP_TIMER_TYPE_ASCONF:
+ if (stcb == NULL) {
+ return;
+ }
+ tmr = &stcb->asoc.asconf_timer;
+ break;
+ case SCTP_TIMER_TYPE_PRIM_DELETED:
+ if (stcb == NULL) {
+ return;
+ }
+ tmr = &stcb->asoc.delete_prim_timer;
+ break;
+ case SCTP_TIMER_TYPE_AUTOCLOSE:
+ if (stcb == NULL) {
+ return;
+ }
+ tmr = &stcb->asoc.autoclose_timer;
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_TIMER1, "%s: Unknown timer type %d\n",
+ __FUNCTION__, t_type);
+ break;
+ }
+ if (tmr == NULL) {
+ return;
+ }
+ if ((tmr->type != t_type) && tmr->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.
+ */
+ 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;
+ (void)SCTP_OS_TIMER_STOP(&tmr->timer);
+ 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 return
+ * RTO in number of msecs net should point to the current network
+ */
+
+uint32_t
+sctp_calculate_rto(struct sctp_tcb *stcb,
+ struct sctp_association *asoc,
+ struct sctp_nets *net,
+ struct timeval *told,
+ int safe, int rtt_from_sack)
+{
+ /*-
+ * given an association and the starting time of the current RTT
+ * period (in value1/value2) return RTO in number of msecs.
+ */
+ int32_t rtt; /* RTT in ms */
+ uint32_t new_rto;
+ int first_measure = 0;
+ struct timeval now, then, *old;
+
+ /* Copy it out for sparc64 */
+ if (safe == sctp_align_unsafe_makecopy) {
+ old = &then;
+ memcpy(&then, told, sizeof(struct timeval));
+ } else if (safe == sctp_align_safe_nocopy) {
+ old = told;
+ } else {
+ /* error */
+ SCTP_PRINTF("Huh, bad rto calc call\n");
+ return (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);
+ }
+ timevalsub(&now, old);
+ /* store the current RTT in us */
+ net->rtt = (uint64_t)1000000 * (uint64_t)now.tv_sec +
+ (uint64_t)now.tv_usec;
+ /* 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->lastsa >> 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;
+ }
+ /* we are now returning the RTO */
+ return (new_rto);
+}
+
+/*
+ * 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);
+ bcopy(mtod(m, caddr_t) + off, ptr, 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ struct mbuf *m_notify;
+ struct sctp_assoc_change *sac;
+ struct sctp_queued_to_read *control;
+ size_t notif_len, abort_len;
+ unsigned int i;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+#endif
+
+ if (sctp_stcb_is_feature_on(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_RECVASSOCEVNT)) {
+ notif_len = sizeof(struct sctp_assoc_change);
+ if (abort != NULL) {
+ abort_len = ntohs(abort->ch.chunk_length);
+ } 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 = 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;
+ }
+ 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);
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ control->spec_flags = M_NOTIFICATION;
+ 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->asoc) == 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->asoc) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(&stcb->asoc) == 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;
+ }
+ }
+ }
+ /* Wake ANY sleepers */
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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))) {
+#if defined(__APPLE__)
+ socantrcvmore(stcb->sctp_socket);
+#else
+ socantrcvmore_locked(stcb->sctp_socket);
+#endif
+ }
+ sorwakeup(stcb->sctp_socket);
+ sowwakeup(stcb->sctp_socket);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ struct mbuf *m_notify;
+ struct sctp_send_failed *ssf;
+ struct sctp_send_failed_event *ssfe;
+ struct sctp_queued_to_read *control;
+ int length;
+
+ 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)) {
+ length = sizeof(struct sctp_send_failed_event);
+ } else {
+ length = sizeof(struct sctp_send_failed);
+ }
+ m_notify = sctp_get_mbuf_for_msg(length, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL)
+ /* no space left */
+ return;
+ SCTP_BUF_LEN(m_notify) = 0;
+ 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, length);
+ ssfe->ssfe_type = SCTP_SEND_FAILED_EVENT;
+ if (sent) {
+ ssfe->ssfe_flags = SCTP_DATA_SENT;
+ } else {
+ ssfe->ssfe_flags = SCTP_DATA_UNSENT;
+ }
+ length += chk->send_size;
+ length -= sizeof(struct sctp_data_chunk);
+ ssfe->ssfe_length = length;
+ ssfe->ssfe_error = error;
+ /* not exactly what the user sent in, but should be close :) */
+ ssfe->ssfe_info.snd_sid = chk->rec.data.stream_number;
+ ssfe->ssfe_info.snd_flags = chk->rec.data.rcv_flags;
+ ssfe->ssfe_info.snd_ppid = chk->rec.data.payloadtype;
+ 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);
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_send_failed_event);
+ } else {
+ ssf = mtod(m_notify, struct sctp_send_failed *);
+ memset(ssf, 0, length);
+ ssf->ssf_type = SCTP_SEND_FAILED;
+ if (sent) {
+ ssf->ssf_flags = SCTP_DATA_SENT;
+ } else {
+ ssf->ssf_flags = SCTP_DATA_UNSENT;
+ }
+ length += chk->send_size;
+ length -= sizeof(struct sctp_data_chunk);
+ ssf->ssf_length = length;
+ ssf->ssf_error = error;
+ /* not exactly what the user sent in, but should be close :) */
+ bzero(&ssf->ssf_info, sizeof(ssf->ssf_info));
+ ssf->ssf_info.sinfo_stream = chk->rec.data.stream_number;
+ ssf->ssf_info.sinfo_ssn = chk->rec.data.stream_seq;
+ ssf->ssf_info.sinfo_flags = chk->rec.data.rcv_flags;
+ ssf->ssf_info.sinfo_ppid = chk->rec.data.payloadtype;
+ 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);
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_send_failed);
+ }
+ if (chk->data) {
+ /*
+ * trim off the sctp chunk header(it should
+ * be there)
+ */
+ if (chk->send_size >= sizeof(struct sctp_data_chunk)) {
+ m_adj(chk->data, sizeof(struct sctp_data_chunk));
+ sctp_mbuf_crush(chk->data);
+ chk->send_size -= sizeof(struct sctp_data_chunk);
+ }
+ }
+ 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->spec_flags = M_NOTIFICATION;
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ struct mbuf *m_notify;
+ struct sctp_send_failed *ssf;
+ struct sctp_send_failed_event *ssfe;
+ struct sctp_queued_to_read *control;
+ int length;
+
+ 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)) {
+ length = sizeof(struct sctp_send_failed_event);
+ } else {
+ length = sizeof(struct sctp_send_failed);
+ }
+ m_notify = sctp_get_mbuf_for_msg(length, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_notify == NULL) {
+ /* no space left */
+ return;
+ }
+ SCTP_BUF_LEN(m_notify) = 0;
+ 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, length);
+ ssfe->ssfe_type = SCTP_SEND_FAILED_EVENT;
+ ssfe->ssfe_flags = SCTP_DATA_UNSENT;
+ length += sp->length;
+ ssfe->ssfe_length = length;
+ ssfe->ssfe_error = error;
+ /* not exactly what the user sent in, but should be close :) */
+ ssfe->ssfe_info.snd_sid = sp->stream;
+ 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);
+ SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_send_failed_event);
+ } else {
+ ssf = mtod(m_notify, struct sctp_send_failed *);
+ memset(ssf, 0, length);
+ ssf->ssf_type = SCTP_SEND_FAILED;
+ ssf->ssf_flags = SCTP_DATA_UNSENT;
+ length += sp->length;
+ ssf->ssf_length = length;
+ ssf->ssf_error = error;
+ /* not exactly what the user sent in, but should be close :) */
+ ssf->ssf_info.sinfo_stream = sp->stream;
+ 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_LEN(m_notify) = sizeof(struct sctp_send_failed);
+ }
+ 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->spec_flags = M_NOTIFICATION;
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ 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->spec_flags = M_NOTIFICATION;
+ control->length = SCTP_BUF_LEN(m_notify);
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ control->held_length = 0;
+ control->length = 0;
+ 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);
+ }
+ atomic_add_int(&control->length, SCTP_BUF_LEN(m_notify));
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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->spec_flags = M_NOTIFICATION;
+ control->length = SCTP_BUF_LEN(m_notify);
+ /* 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ 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->spec_flags = M_NOTIFICATION;
+ control->length = SCTP_BUF_LEN(m_notify);
+ /* 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->spec_flags = M_NOTIFICATION;
+ control->length = SCTP_BUF_LEN(m_notify);
+ /* 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->spec_flags = M_NOTIFICATION;
+ control->length = SCTP_BUF_LEN(m_notify);
+ /* 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;
+ size_t notif_len, 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);
+ } else {
+ chunk_len = 0;
+ }
+ notif_len = 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 = 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);
+ /* not that we need this */
+ control->tail_mbuf = m_notify;
+ control->spec_flags = M_NOTIFICATION;
+ 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 !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ 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__) && __FreeBSD_version > 500000) || defined(__Windows__)
+ if (stcb->sctp_socket->so_rcv.sb_state & SBS_CANTRCVMORE) {
+#else
+ if (stcb->sctp_socket->so_state & SS_CANTRCVMORE) {
+#endif
+ return;
+ }
+#if defined(__APPLE__)
+ 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->asoc.state & SCTP_STATE_COOKIE_WAIT) ||
+ (stcb->asoc.state & 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 (((stcb->asoc.state & SCTP_STATE_MASK) == SCTP_STATE_COOKIE_WAIT) ||
+ ((stcb->asoc.state & SCTP_STATE_MASK) == 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 (((stcb->asoc.state & SCTP_STATE_MASK) == SCTP_STATE_COOKIE_WAIT) ||
+ ((stcb->asoc.state & SCTP_STATE_MASK) == 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",
+ __FUNCTION__, notification, notification);
+ break;
+ } /* end switch */
+}
+
+void
+sctp_report_all_outbound(struct sctp_tcb *stcb, uint16_t error, int holds_lock, int so_locked
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ 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__)
+ 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.stream_number].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.stream_number].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.stream_number);
+#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.stream_number].chunks_on_queues > 0) {
+ asoc->strmout[chk->rec.data.stream_number].chunks_on_queues--;
+#ifdef INVARIANTS
+ } else {
+ panic("No chunks on the queues for sid %u.", chk->rec.data.stream_number);
+#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 */
+ asoc->locked_on_sending = NULL;
+ TAILQ_FOREACH_SAFE(sp, &outs->outqueue, next, nsp) {
+ asoc->stream_queue_cnt--;
+ TAILQ_REMOVE(&outs->outqueue, sp, next);
+ 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 !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ if (stcb == NULL) {
+ return;
+ }
+#if defined(__APPLE__)
+ 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, 1, 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__)
+ uint8_t mflowtype, uint32_t mflowid,
+#endif
+ uint32_t vrf_id, uint16_t port)
+{
+ uint32_t vtag;
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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__)
+ mflowtype, mflowid,
+#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);
+ stcb->asoc.state |= SCTP_STATE_WAS_ABORTED;
+ /* Ok, now lets free it */
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&stcb->asoc) == 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+)
+{
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+#endif
+
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ so = SCTP_INP_SO(inp);
+#endif
+#if defined(__APPLE__)
+ 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__)
+ 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__)
+ if (!so_locked) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ }
+#endif
+ }
+ }
+ return;
+ } else {
+ stcb->asoc.state |= 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->asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(&stcb->asoc) == 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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__)
+ uint8_t mflowtype, uint32_t mflowid,
+#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__)
+ 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__)
+ 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__)
+ mflowtype, mflowid,
+#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__)
+ mflowtype, mflowid,
+#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__) && __FreeBSD_version >= 700000
+ 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__) && __FreeBSD_version >= 700000
+ 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;
+ int error = 0;
+
+ old_so = old_inp->sctp_socket;
+ new_so = new_inp->sctp_socket;
+ TAILQ_INIT(&tmp_queue);
+#if defined(__FreeBSD__) && __FreeBSD_version < 700000
+ SOCKBUF_LOCK(&(old_so->so_rcv));
+#endif
+#if defined(__FreeBSD__) || defined(__APPLE__)
+ error = sblock(&old_so->so_rcv, waitflags);
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version < 700000
+ SOCKBUF_UNLOCK(&(old_so->so_rcv));
+#endif
+ 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;
+ }
+ /* 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(__FreeBSD__) && __FreeBSD_version < 700000
+ SOCKBUF_LOCK(&(old_so->so_rcv));
+#endif
+#if defined(__APPLE__)
+ sbunlock(&old_so->so_rcv, 1);
+#endif
+
+#if defined(__FreeBSD__)
+ sbunlock(&old_so->so_rcv);
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version < 700000
+ SOCKBUF_UNLOCK(&(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_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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ /*
+ * 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__)
+ 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) {
+ sctp_free_remote_addr(control->whoFrom);
+ if (control->data) {
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ }
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), 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?? */
+ sctp_free_remote_addr(control->whoFrom);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_readq), control);
+ if (inp_read_lock_held == 0)
+ SCTP_INP_READ_UNLOCK(inp);
+ return;
+ }
+ if (end) {
+ control->end_added = 1;
+ }
+#if defined(__Userspace__)
+ if (inp->recv_callback) {
+ if (inp_read_lock_held == 0)
+ SCTP_INP_READ_UNLOCK(inp);
+ if (control->end_added == 1) {
+ struct socket *so;
+ struct mbuf *m;
+ char *buffer;
+ struct sctp_rcvinfo rcv;
+ union sctp_sockstore addr;
+ int flags;
+
+ if ((buffer = malloc(control->length)) == NULL) {
+ return;
+ }
+ so = stcb->sctp_socket;
+ for (m = control->data; m; m = SCTP_BUF_NEXT(m)) {
+ sctp_sbfree(control, control->stcb, &so->so_rcv, m);
+ }
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ m_copydata(control->data, 0, control->length, buffer);
+ memset(&rcv, 0, sizeof(struct sctp_rcvinfo));
+ rcv.rcv_sid = control->sinfo_stream;
+ rcv.rcv_ssn = control->sinfo_ssn;
+ 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 = MSG_EOR;
+ if (control->spec_flags & M_NOTIFICATION) {
+ flags |= MSG_NOTIFICATION;
+ }
+ inp->recv_callback(so, addr, buffer, control->length, rcv, flags, inp->ulp_info);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ sctp_free_remote_addr(control->whoFrom);
+ control->whoFrom = NULL;
+ sctp_m_freem(control->data);
+ control->data = NULL;
+ control->length = 0;
+ sctp_free_a_readq(stcb, control);
+ }
+ return;
+ }
+#endif
+ TAILQ_INSERT_TAIL(&inp->read_queue, control, next);
+ if (inp_read_lock_held == 0)
+ SCTP_INP_READ_UNLOCK(inp);
+ if (inp && inp->sctp_socket) {
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE)) {
+ SCTP_ZERO_COPY_EVENT(inp, inp->sctp_socket);
+ } else {
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ if (!so_locked) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ }
+#endif
+ }
+ }
+}
+
+
+int
+sctp_append_to_readq(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_queued_to_read *control,
+ struct mbuf *m,
+ int end,
+ int ctls_cumack,
+ struct sockbuf *sb)
+{
+ /*
+ * A partial delivery API event is underway. OR we are appending on
+ * the reassembly queue.
+ *
+ * If PDAPI this means we need to add m to the end of the data.
+ * Increase the length in the control AND increment the sb_cc.
+ * Otherwise sb is NULL and all we need to do is put it at the end
+ * of the mbuf chain.
+ */
+ int len = 0;
+ struct mbuf *mm, *tail = NULL, *prev = NULL;
+
+ if (inp) {
+ SCTP_INP_READ_LOCK(inp);
+ }
+ if (control == NULL) {
+ get_out:
+ if (inp) {
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+ return (-1);
+ }
+ if (inp && (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_CANT_READ)) {
+ SCTP_INP_READ_UNLOCK(inp);
+ return (0);
+ }
+ if (control->end_added) {
+ /* huh this one is complete? */
+ goto get_out;
+ }
+ mm = m;
+ if (mm == NULL) {
+ goto get_out;
+ }
+
+ while (mm) {
+ if (SCTP_BUF_LEN(mm) == 0) {
+ /* Skip mbufs with NO lenght */
+ if (prev == NULL) {
+ /* First one */
+ m = sctp_m_free(mm);
+ mm = m;
+ } else {
+ SCTP_BUF_NEXT(prev) = sctp_m_free(mm);
+ mm = SCTP_BUF_NEXT(prev);
+ }
+ continue;
+ }
+ prev = mm;
+ len += SCTP_BUF_LEN(mm);
+ if (sb) {
+ 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(mm));
+ }
+ sctp_sballoc(stcb, sb, mm);
+ 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);
+ }
+ }
+ mm = SCTP_BUF_NEXT(mm);
+ }
+ if (prev) {
+ tail = prev;
+ } else {
+ /* Really there should always be a prev */
+ if (m == NULL) {
+ /* Huh nothing left? */
+#ifdef INVARIANTS
+ panic("Nothing left to add?");
+#else
+ goto get_out;
+#endif
+ }
+ tail = m;
+ }
+ if (control->tail_mbuf) {
+ /* append */
+ SCTP_BUF_NEXT(control->tail_mbuf) = m;
+ control->tail_mbuf = tail;
+ } else {
+ /* nothing there */
+#ifdef INVARIANTS
+ if (control->data != NULL) {
+ panic("This should NOT happen");
+ }
+#endif
+ control->data = m;
+ control->tail_mbuf = tail;
+ }
+ atomic_add_int(&control->length, len);
+ if (end) {
+ /* message is complete */
+ if (stcb && (control == stcb->asoc.control_pdapi)) {
+ stcb->asoc.control_pdapi = NULL;
+ }
+ control->held_length = 0;
+ control->end_added = 1;
+ }
+ if (stcb == NULL) {
+ control->do_not_ref_stcb = 1;
+ }
+ /*
+ * When we are appending in partial delivery, the cum-ack is used
+ * for the actual pd-api highest tsn on this mbuf. The true cum-ack
+ * is populated in the outbound sinfo structure from the true cumack
+ * if the association exists...
+ */
+ control->sinfo_tsn = control->sinfo_cumtsn = ctls_cumack;
+#if defined(__Userspace__)
+ if (inp->recv_callback) {
+ uint32_t pd_point, length;
+
+ 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;
+ char *buffer;
+ struct sctp_rcvinfo rcv;
+ union sctp_sockstore addr;
+ int flags;
+
+ if ((buffer = malloc(control->length)) == NULL) {
+ return (-1);
+ }
+ 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, control->length, buffer);
+ memset(&rcv, 0, sizeof(struct sctp_rcvinfo));
+ rcv.rcv_sid = control->sinfo_stream;
+ rcv.rcv_ssn = control->sinfo_ssn;
+ 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) {
+ sctp_free_remote_addr(control->whoFrom);
+ control->whoFrom = NULL;
+ sctp_free_a_readq(stcb, control);
+ } else {
+ control->some_taken = 1;
+ }
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ SCTP_TCB_UNLOCK(stcb);
+ inp->recv_callback(so, addr, buffer, length, rcv, flags, inp->ulp_info);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ if (inp)
+ SCTP_INP_READ_UNLOCK(inp);
+ return (0);
+ }
+#endif
+ if (inp) {
+ SCTP_INP_READ_UNLOCK(inp);
+ }
+ if (inp && inp->sctp_socket) {
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE)) {
+ SCTP_ZERO_COPY_EVENT(inp, inp->sctp_socket);
+ } else {
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+
+ so = SCTP_INP_SO(inp);
+ 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 (0);
+ }
+#endif
+ sctp_sorwakeup(inp, inp->sctp_socket);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ }
+ return (0);
+}
+
+
+
+/*************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, len;
+
+ if ((code == 0) || (info == NULL)) {
+ return (NULL);
+ }
+ info_len = strlen(info);
+ len = 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((uint16_t)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;
+ size_t len;
+
+ len = 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((uint16_t)len);
+ no_user_data_cause->tsn = tsn; /* tsn is passed in as NBO */
+ }
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ )
+{
+ struct sctp_stream_out *strq;
+ struct sctp_tmit_chunk *chk = NULL, *tp2;
+ struct sctp_stream_queue_pending *sp;
+ uint16_t stream = 0, seq = 0;
+ uint8_t foundeom = 0;
+ int ret_sz = 0;
+ int notdone;
+ int do_wakeup_routine = 0;
+
+#if defined(__APPLE__)
+ if (so_locked) {
+ sctp_lock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ } else {
+ sctp_unlock_assert(SCTP_INP_SO(stcb->sctp_ep));
+ }
+#endif
+ stream = tp1->rec.data.stream_number;
+ seq = tp1->rec.data.stream_seq;
+ 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[stream].abandoned_sent[0]++;
+#if defined(SCTP_DETAILED_STR_STATS)
+ stcb->asoc.strmout[stream].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[stream].abandoned_unsent[0]++;
+#if defined(SCTP_DETAILED_STR_STATS)
+ stcb->asoc.strmout[stream].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.stream_number != stream) ||
+ (tp1->rec.data.stream_seq != seq)) {
+ 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[stream];
+ 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 = SCTP_DATA_LAST_FRAG;
+ chk->sent = SCTP_FORWARD_TSN_SKIP;
+ chk->asoc = &stcb->asoc;
+ chk->rec.data.stream_seq = strq->next_sequence_send;
+ chk->rec.data.stream_number = sp->stream;
+ chk->rec.data.payloadtype = sp->ppid;
+ chk->rec.data.context = sp->context;
+ chk->flags = sp->act_flags;
+ chk->whoTo = NULL;
+#if defined(__FreeBSD__) || defined(__Panda__)
+ chk->rec.data.TSN_seq = atomic_fetchadd_int(&stcb->asoc.sending_seq, 1);
+#else
+ chk->rec.data.TSN_seq = 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++;
+ } else {
+ chk->rec.data.rcv_flags |= SCTP_DATA_LAST_FRAG;
+ }
+ strq->next_sequence_send++;
+ 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(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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. */
+ if (holds_lock == 0) {
+ SCTP_INP_RUNLOCK(inp);
+ }
+ return (laddr->ifa);
+ 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. */
+ if (holds_lock == 0) {
+ SCTP_INP_RUNLOCK(inp);
+ }
+ return (laddr->ifa);
+ 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. */
+ if (holds_lock == 0) {
+ SCTP_INP_RUNLOCK(inp);
+ }
+ return (laddr->ifa);
+ break;
+ }
+ }
+#endif
+ }
+ if (holds_lock == 0) {
+ SCTP_INP_RUNLOCK(inp);
+ }
+ return (NULL);
+}
+
+uint32_t
+sctp_get_ifa_hash_val(struct sockaddr *addr)
+{
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addr;
+ return (sin->sin_addr.s_addr ^ (sin->sin_addr.s_addr >> 16));
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+ uint32_t hash_of_addr;
+
+ sin6 = (struct sockaddr_in6 *)addr;
+#if !defined(__Windows__) && !defined(__Userspace_os_FreeBSD) && !defined(__Userspace_os_Darwin) && !defined(__Userspace_os_Windows)
+ 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. */
+ if (holds_lock == 0)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (sctp_ifap);
+ 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. */
+ if (holds_lock == 0)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (sctp_ifap);
+ 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. */
+ if (holds_lock == 0)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (sctp_ifap);
+ break;
+ }
+ }
+#endif
+ }
+ if (holds_lock == 0)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (NULL);
+}
+
+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? */
+ int r_unlocked = 0;
+ uint32_t dif, rwnd;
+ struct socket *so = NULL;
+
+ if (stcb == NULL)
+ return;
+
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+
+ if (stcb->asoc.state & (SCTP_STATE_ABOUT_TO_BE_FREED |
+ SCTP_STATE_SHUTDOWN_RECEIVED |
+ SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ /* 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);
+ 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 */
+ 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;
+ int my_len = 0;
+ int cp_len = 0, 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;
+ uint32_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;
+ int slen = 0;
+ uint32_t held_length = 0;
+#if defined(__FreeBSD__) && __FreeBSD_version >= 700000
+ 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__)
+#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__) && __FreeBSD_version > 500000
+ | 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__)
+#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, uio->uio_resid);
+#endif
+ }
+#if (defined(__FreeBSD__) && __FreeBSD_version < 700000) || 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__)
+#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, uio->uio_resid);
+#endif
+ }
+
+#if defined(__APPLE__)
+ error = sblock(&so->so_rcv, SBLOCKWAIT(in_flags));
+#endif
+
+#if defined(__FreeBSD__)
+ error = sblock(&so->so_rcv, (block_allowed ? SBL_WAIT : 0));
+#endif
+ if (error) {
+ goto release_unlocked;
+ }
+#if defined(__FreeBSD__) && __FreeBSD_version >= 700000
+ sockbuf_lock = 1;
+#endif
+ restart:
+#if (defined(__FreeBSD__) && __FreeBSD_version < 700000) || defined(__Userspace__)
+ if (hold_sblock == 0) {
+ SOCKBUF_LOCK(&so->so_rcv);
+ hold_sblock = 1;
+ }
+#endif
+#if defined(__APPLE__)
+ sbunlock(&so->so_rcv, 1);
+#endif
+
+#if defined(__FreeBSD__) && __FreeBSD_version < 700000
+ sbunlock(&so->so_rcv);
+#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__) && __FreeBSD_version > 500000) || defined(__Windows__)
+ 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) && block_allowed) {
+ /* we need to wait for data */
+ 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;
+ }
+ }
+ error = sbwait(&so->so_rcv);
+ if (error) {
+ goto out;
+ }
+ held_length = 0;
+ goto restart_nosblocks;
+ } else if (so->so_rcv.sb_cc == 0) {
+ if (so->so_error) {
+ error = so->so_error;
+ if ((in_flags & MSG_PEEK) == 0)
+ so->so_error = 0;
+ } else {
+ if ((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;
+ }
+ }
+ 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__)
+ error = sblock(&so->so_rcv, SBLOCKWAIT(in_flags));
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version < 700000
+ error = sblock(&so->so_rcv, (block_allowed ? M_WAITOK : 0));
+#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;
+ }
+ 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;
+ if (hold_rlock) {
+ SCTP_INP_READ_UNLOCK(inp);
+ hold_rlock = 0;
+ }
+ found_one:
+ /*
+ * If we reach here, control has a some data for us to read off.
+ * Note that stcb COULD be NULL.
+ */
+ control->some_taken++;
+ if (hold_sblock) {
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ hold_sblock = 0;
+ }
+ 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 = 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) && filling_sinfo) {
+ memcpy(sinfo, control, sizeof(struct sctp_nonpad_sndrcvinfo));
+ 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->sreinfo_next_flags = SCTP_NEXT_MSG_AVAIL;
+ if (nxt->sinfo_flags & SCTP_UNORDERED) {
+ s_extra->sreinfo_next_flags |= SCTP_NEXT_MSG_IS_UNORDERED;
+ }
+ if (nxt->spec_flags & M_NOTIFICATION) {
+ s_extra->sreinfo_next_flags |= SCTP_NEXT_MSG_IS_NOTIFICATION;
+ }
+ s_extra->sreinfo_next_aid = nxt->sinfo_assoc_id;
+ s_extra->sreinfo_next_length = nxt->length;
+ s_extra->sreinfo_next_ppid = nxt->sinfo_ppid;
+ s_extra->sreinfo_next_stream = nxt->sinfo_stream;
+ if (nxt->tail_mbuf != NULL) {
+ if (nxt->end_added) {
+ s_extra->sreinfo_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->sreinfo_next_flags = SCTP_NO_NEXT_MSG;
+ s_extra->sreinfo_next_aid = 0;
+ s_extra->sreinfo_next_length = 0;
+ s_extra->sreinfo_next_ppid = 0;
+ s_extra->sreinfo_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 = control->sinfo_ssn;
+ 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
+ }
+ /* 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__)
+#if defined(APPLE_LEOPARD)
+ cp_len = (int)uio->uio_resid;
+#else
+ cp_len = (int)uio_resid(uio);
+#endif
+#else
+ cp_len = (int)uio->uio_resid;
+#endif
+ my_len = (int)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__)
+ SCTP_SOCKET_UNLOCK(so, 0);
+#endif
+ if (cp_len > 0)
+ error = uiomove(mtod(m, char *), cp_len, uio);
+#if defined(__APPLE__)
+ 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;
+ }
+ if ((SCTP_BUF_NEXT(m) == NULL) &&
+ (cp_len >= SCTP_BUF_LEN(m)) &&
+ ((control->end_added == 0) ||
+ (control->end_added &&
+ (TAILQ_NEXT(control, next) == NULL)))
+ ) {
+ 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 += 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(__APPLE__)
+ 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) -= 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, 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 += 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__)
+#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 (TAILQ_NEXT(control, next) == NULL) {
+ /* If we don't have a next we need a
+ * lock, if there is a next interrupt
+ * is filling ahead of us and we don't
+ * need a lock to remove this guy
+ * (which is the head of the queue).
+ */
+ 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;
+ 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__)
+#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 >= (uint32_t)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__) && __FreeBSD_version > 500000) || defined(__Windows__)
+ 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__)
+ 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__)
+ goto release;
+#else
+ goto release_unlocked;
+#endif
+ }
+ control->held_length = 0;
+ }
+#if defined(__APPLE__)
+ 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__)
+#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 += 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(__FreeBSD__) && __FreeBSD_version < 700000) || 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__)
+ sbunlock(&so->so_rcv, 1);
+#endif
+
+#if defined(__FreeBSD__)
+ sbunlock(&so->so_rcv);
+#if defined(__FreeBSD__) && __FreeBSD_version >= 700000
+ sockbuf_lock = 0;
+#endif
+#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->sreinfo_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__) && __FreeBSD_version >= 700000
+ 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
+ }
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ /* Save the value back for next time */
+ stcb->freed_by_sorcv_sincelast = freed_so_far;
+ }
+ 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__)
+#if defined(APPLE_LEOPARD)
+ ((uio) ? (slen - uio->uio_resid) : slen),
+#else
+ ((uio) ? (slen - uio_resid(uio)) : slen),
+#endif
+#else
+ ((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__)
+#if defined(APPLE_LEOPARD)
+ ((uio) ? (slen - uio->uio_resid) : slen),
+#else
+ ((uio) ? (slen - uio_resid(uio)) : slen),
+#endif
+#else
+ ((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();
+ bzero(wi, 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_WQ_ADDR_UNLOCK();
+ sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
+ (struct sctp_inpcb *)NULL,
+ (struct sctp_tcb *)NULL,
+ (struct sctp_nets *)NULL);
+ 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;
+ 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__)
+ SCTP_SOCKET_LOCK(so, 1);
+#endif
+ if (filling_sinfo) {
+ memset(&sinfo, 0, sizeof(struct sctp_extrcvinfo));
+ }
+ error = sctp_sorecvmsg(so, uio, mp0, from, fromlen, flagsp,
+ (struct sctp_sndrcvinfo *)&sinfo, filling_sinfo);
+ if (controlp != NULL) {
+ /* copy back the sinfo in a CMSG format */
+ if (filling_sinfo)
+ *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__) && __FreeBSD_version > 500000) || defined(__Windows__)
+ *psa = sodupsockaddr(from, M_NOWAIT);
+#else
+ *psa = dup_sockaddr(from, mp0 == 0);
+#endif
+ } else {
+ *psa = NULL;
+ }
+ }
+#if defined(__APPLE__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ return (error);
+}
+
+
+#if (defined(__FreeBSD__) && __FreeBSD_version < 603000) || defined(__Windows__)
+/*
+ * 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_SCTP_USRREQ+SCTP_LOC_7);
+ *error = EINVAL;
+ goto out_now;
+ }
+ if (sctp_add_remote_addr(stcb, sa, NULL, 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_SCTP_USRREQ+SCTP_LOC_7);
+ *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_SCTP_USRREQ+SCTP_LOC_8);
+ *error = EINVAL;
+ goto out_now;
+ }
+ if (sctp_add_remote_addr(stcb, sa, NULL, 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_SCTP_USRREQ+SCTP_LOC_8);
+ *error = ENOBUFS;
+ goto out_now;
+ }
+ added++;
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ incr = sizeof(struct sockaddr_in6);
+ if (sctp_add_remote_addr(stcb, sa, NULL, 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_SCTP_USRREQ+SCTP_LOC_8);
+ *error = ENOBUFS;
+ goto out_now;
+ }
+ added++;
+ break;
+#endif
+ default:
+ break;
+ }
+ sa = (struct sockaddr *)((caddr_t)sa + incr);
+ }
+ out_now:
+ return (added);
+}
+
+struct sctp_tcb *
+sctp_connectx_helper_find(struct sctp_inpcb *inp, struct sockaddr *addr,
+ int *totaddr, int *num_v4, int *num_v6, int *error,
+ int limit, int *bad_addr)
+{
+ struct sockaddr *sa;
+ struct sctp_tcb *stcb = NULL;
+ size_t incr, at, i;
+ at = incr = 0;
+ sa = addr;
+
+ *error = *num_v6 = *num_v4 = 0;
+ /* account and validate addresses */
+ for (i = 0; i < (size_t)*totaddr; i++) {
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ (*num_v4) += 1;
+ incr = sizeof(struct sockaddr_in);
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != incr) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ *bad_addr = 1;
+ return (NULL);
+ }
+#endif
+ 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 */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ *bad_addr = 1;
+ return (NULL);
+ }
+ (*num_v6) += 1;
+ incr = sizeof(struct sockaddr_in6);
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != incr) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ *bad_addr = 1;
+ return (NULL);
+ }
+#endif
+ break;
+ }
+#endif
+ default:
+ *totaddr = i;
+ /* we are done */
+ break;
+ }
+ if (i == (size_t)*totaddr) {
+ break;
+ }
+ SCTP_INP_INCR_REF(inp);
+ stcb = sctp_findassociation_ep_addr(&inp, sa, NULL, NULL, NULL);
+ if (stcb != NULL) {
+ /* Already have or am bring up an association */
+ return (stcb);
+ } else {
+ SCTP_INP_DECR_REF(inp);
+ }
+ if ((at + incr) > (size_t)limit) {
+ *totaddr = i;
+ break;
+ }
+ sa = (struct sockaddr *)((caddr_t)sa + incr);
+ }
+ return ((struct sctp_tcb *)NULL);
+}
+
+/*
+ * 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, sctp_assoc_t assoc_id,
+ uint32_t vrf_id, int *error, void *p)
+{
+ struct sockaddr *addr_touse;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin;
+#endif
+#ifdef SCTP_MVRF
+ int i, fnd = 0;
+#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]) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ addr_touse = sa;
+#ifdef INET6
+ if (sa->sa_family == AF_INET6) {
+#ifdef INET
+ struct sockaddr_in6 *sin6;
+
+#endif
+#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 *)addr_touse;
+ 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_touse = (struct sockaddr *)&sin;
+ }
+#endif
+ }
+#endif
+#ifdef INET
+ if (sa->sa_family == 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;
+ }
+ }
+#endif
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) {
+#if !(defined(__Panda__) || defined(__Windows__))
+ 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_touse, NULL, p);
+ return;
+ }
+ /*
+ * No locks required here since bind and mgmt_ep_sa
+ * all do their own locking. If we do something for
+ * the FIX: below we may need to lock in that case.
+ */
+ if (assoc_id == 0) {
+ /* add the address */
+ struct sctp_inpcb *lep;
+ struct sockaddr_in *lsin = (struct sockaddr_in *)addr_touse;
+
+ /* validate the incoming port */
+ if ((lsin->sin_port != 0) &&
+ (lsin->sin_port != inp->sctp_lport)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ } else {
+ /* user specified 0 port, set it to existing port */
+ lsin->sin_port = inp->sctp_lport;
+ }
+
+ lep = sctp_pcb_findep(addr_touse, 1, 0, vrf_id);
+ if (lep != NULL) {
+ /*
+ * We must decrement the refcount
+ * since we have the ep already and
+ * are binding. No remove going on
+ * here.
+ */
+ SCTP_INP_DECR_REF(lep);
+ }
+ if (lep == inp) {
+ /* already bound to it.. ok */
+ return;
+ } else if (lep == NULL) {
+ ((struct sockaddr_in *)addr_touse)->sin_port = 0;
+ *error = sctp_addr_mgmt_ep_sa(inp, addr_touse,
+ SCTP_ADD_IP_ADDRESS,
+ vrf_id, NULL);
+ } else {
+ *error = EADDRINUSE;
+ }
+ if (*error)
+ return;
+ } else {
+ /*
+ * FIX: decide whether we allow assoc based
+ * bindx
+ */
+ }
+}
+
+/*
+ * 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, sctp_assoc_t assoc_id,
+ uint32_t vrf_id, int *error)
+{
+ struct sockaddr *addr_touse;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin;
+#endif
+#ifdef SCTP_MVRF
+ int i, fnd = 0;
+#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]) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+ *error = EINVAL;
+ return;
+ }
+#endif
+ addr_touse = sa;
+#ifdef INET6
+ if (sa->sa_family == AF_INET6) {
+#ifdef INET
+ struct sockaddr_in6 *sin6;
+#endif
+
+#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 *)addr_touse;
+ 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_touse = (struct sockaddr *)&sin;
+ }
+#endif
+ }
+#endif
+#ifdef INET
+ if (sa->sa_family == 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;
+ }
+ }
+#endif
+ /*
+ * No lock required mgmt_ep_sa does its own locking.
+ * If the FIX: below is ever changed we may need to
+ * lock before calling association level binding.
+ */
+ if (assoc_id == 0) {
+ /* delete the address */
+ *error = sctp_addr_mgmt_ep_sa(inp, addr_touse,
+ SCTP_DEL_IP_ADDRESS,
+ vrf_id, NULL);
+ } else {
+ /*
+ * FIX: decide whether we allow assoc based
+ * bindx
+ */
+ }
+}
+
+/*
+ * 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__)
+ 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__)
+ 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(__Windows__)
+ 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__)
+#if __FreeBSD_version >= 800044
+static void
+sctp_recv_udp_tunneled_packet(struct mbuf *m, int off, struct inpcb *ignored,
+ 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;
+ iph = mtod(m, struct ip *);
+ switch (iph->ip_v) {
+#ifdef INET
+ case IPVERSION:
+#if __FreeBSD_version >= 1000000
+ iph->ip_len = htons(ntohs(iph->ip_len) - sizeof(struct udphdr));
+#else
+ iph->ip_len -= sizeof(struct udphdr);
+#endif
+ 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);
+}
+#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)
+{
+#if __FreeBSD_version >= 800044
+ 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, 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, 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);
+#else
+ return (ENOTSUP);
+#endif
+}
+#endif
diff --git a/netwerk/sctp/src/netinet/sctputil.h b/netwerk/sctp/src/netinet/sctputil.h
new file mode 100755
index 0000000000..cca915bb45
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctputil.h
@@ -0,0 +1,414 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctputil.h 276914 2015-01-10 20:49:57Z 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) || defined(__APPLE__)
+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
+ */
+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);
+
+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_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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ );
+
+int
+sctp_append_to_readq(struct sctp_inpcb *inp,
+ struct sctp_tcb *stcb,
+ struct sctp_queued_to_read *control,
+ struct mbuf *m,
+ int end,
+ int new_cumack,
+ struct sockbuf *sb);
+
+
+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 *);
+
+uint32_t
+sctp_calculate_rto(struct sctp_tcb *, struct sctp_association *,
+ struct sctp_nets *, struct timeval *, int, 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ );
+
+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 *);
+
+void sctp_report_all_outbound(struct sctp_tcb *, uint16_t, int, int
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ );
+
+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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+ );
+
+/* 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__)
+ 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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+);
+
+void sctp_handle_ootb(struct mbuf *, int, int,
+ struct sockaddr *, struct sockaddr *,
+ struct sctphdr *, struct sctp_inpcb *,
+ struct mbuf *,
+#if defined(__FreeBSD__)
+ uint8_t, uint32_t,
+#endif
+ uint32_t, uint16_t);
+
+int sctp_connectx_helper_add(struct sctp_tcb *stcb, struct sockaddr *addr,
+ int totaddr, int *error);
+
+struct sctp_tcb *
+sctp_connectx_helper_find(struct sctp_inpcb *inp, struct sockaddr *addr,
+ int *totaddr, int *num_v4, int *num_v6, int *error, int limit, int *bad_addr);
+
+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
+#if !defined(__APPLE__) && !defined(SCTP_SO_LOCK_TESTING)
+ SCTP_UNUSED
+#endif
+);
+
+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, sctp_assoc_t assoc_id,
+ uint32_t vrf_id, int *error, void *p);
+void sctp_bindx_delete_address(struct sctp_inpcb *inp,
+ struct sockaddr *sa, sctp_assoc_t assoc_id,
+ 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__)
+void sctp_over_udp_stop(void);
+int sctp_over_udp_start(void);
+#endif
+#if defined(__Windows__)
+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 *, int);
+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
+#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..4adb671966
--- /dev/null
+++ b/netwerk/sctp/src/netinet6/sctp6_usrreq.c
@@ -0,0 +1,1883 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet6/sctp6_usrreq.c 276914 2015-01-10 20:49:57Z tuexen $");
+#endif
+
+#include <netinet/sctp_os.h>
+#ifdef INET6
+#ifdef __FreeBSD__
+#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_input.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_bsd_addr.h>
+#include <netinet/sctp_crc32.h>
+#if !defined(__Userspace_os_Windows)
+#include <netinet/udp.h>
+#endif
+
+#if defined(__APPLE__)
+#define APPLE_FILE_NO 9
+#endif
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#ifdef INET6
+#include <netipsec/ipsec6.h>
+#endif /* INET6 */
+#endif /* IPSEC */
+
+#if !defined(__Userspace__)
+extern struct protosw inetsw[];
+#endif
+#if defined(__Panda__) || 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(__Userspace_os_Windows)
+ uint32_t temp;
+#endif
+ bzero(sin, 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(__Userspace_os_Windows)
+ 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(struct sockaddr_in *sin, struct sockaddr_in6 *sin6)
+{
+ bzero(sin6, 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(__Userspace_os_Windows)
+ ((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)
+#elif defined( __Panda__)
+sctp6_input(pakhandle_type *i_pak)
+#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;
+#if !defined(SCTP_WITH_NO_CSUM)
+ uint8_t compute_crc;
+#endif
+#if defined(__FreeBSD__)
+ uint32_t mflowid;
+ uint8_t mflowtype;
+#endif
+#if !(defined(__APPLE__) || defined (__FreeBSD__))
+ uint16_t port = 0;
+#endif
+
+#if defined(__Panda__)
+ /* This is Evil, but its the only way to make panda work right. */
+ iphlen = sizeof(struct ip6_hdr);
+#else
+ iphlen = *offp;
+#endif
+ 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 __Panda__
+ SCTP_DETACH_HEADER_FROM_CHAIN(*i_pak);
+ (void)SCTP_RELEASE_HEADER(*i_pak);
+#endif
+#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__)
+#if __FreeBSD_version > 1000049
+ 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);
+#elif __FreeBSD_version >= 800000
+ SCTPDBG(SCTP_DEBUG_CRCOFFLOAD,
+ "sctp6_input(): Packet of length %d received on %s with csum_flags 0x%x.\n",
+ m->m_pkthdr.len,
+ if_name(m->m_pkthdr.rcvif),
+ m->m_pkthdr.csum_flags);
+#else
+ 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
+#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(__Windows__)
+ 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);
+ #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);
+ ip6 = mtod(m, struct ip6_hdr *);
+ IP6_EXTHDR_GET(sh, struct sctphdr *, m, iphlen,
+ (int)(sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr)));
+ if (sh == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ return (IPPROTO_DONE);
+ }
+ 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(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_recvnocrc);
+#else
+#if defined(__FreeBSD__) && __FreeBSD_version >= 800000
+ 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_recvnocrc);
+ compute_crc = 0;
+ } else {
+#endif
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ compute_crc = 1;
+ }
+#endif
+ sctp_common_input_processing(&m, iphlen, offset, length,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+#if !defined(SCTP_WITH_NO_CSUM)
+ compute_crc,
+#endif
+ ecn_bits,
+#if defined(__FreeBSD__)
+ mflowtype, mflowid,
+#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
+
+#if defined(__Panda__)
+void
+#else
+static void
+#endif
+sctp6_notify_mbuf(struct sctp_inpcb *inp, struct icmp6_hdr *icmp6,
+ struct sctphdr *sh, struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ uint32_t nxtsz;
+
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL) ||
+ (icmp6 == NULL) || (sh == NULL)) {
+ goto out;
+ }
+ /* First do we even look at it? */
+ if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag))
+ goto out;
+
+ if (icmp6->icmp6_type != ICMP6_PACKET_TOO_BIG) {
+ /* not PACKET TO BIG */
+ goto out;
+ }
+ /*
+ * ok we need to look closely. We could even get smarter and look at
+ * anyone that we sent to in case we get a different ICMP that tells
+ * us there is no way to reach a host, but for this impl, all we
+ * care about is MTU discovery.
+ */
+ nxtsz = ntohl(icmp6->icmp6_mtu);
+ /* Stop any PMTU timer */
+ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL, SCTP_FROM_SCTP6_USRREQ+SCTP_LOC_1);
+
+ /* Adjust destination size limit */
+ if (net->mtu > nxtsz) {
+ net->mtu = nxtsz;
+ if (net->port) {
+ net->mtu -= sizeof(struct udphdr);
+ }
+ }
+ /* now what about the ep? */
+ if (stcb->asoc.smallest_mtu > nxtsz) {
+ struct sctp_tmit_chunk *chk;
+
+ /* Adjust that too */
+ stcb->asoc.smallest_mtu = nxtsz;
+ /* now off to subtract IP_DF flag if needed */
+
+ TAILQ_FOREACH(chk, &stcb->asoc.send_queue, sctp_next) {
+ if ((uint32_t) (chk->send_size + IP_HDR_SIZE) > nxtsz) {
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ }
+ }
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if ((uint32_t) (chk->send_size + IP_HDR_SIZE) > 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)
+ stcb->asoc.sent_queue_retran_cnt++;
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ chk->rec.data.doing_fast_retransmit = 0;
+
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ /* Clear any time so NO RTT is being done */
+ chk->sent_rcv_time.tv_sec = 0;
+ chk->sent_rcv_time.tv_usec = 0;
+ stcb->asoc.total_flight -= chk->send_size;
+ net->flight_size -= chk->send_size;
+ }
+ }
+ }
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL);
+out:
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+}
+#endif
+
+
+void
+sctp6_notify(struct sctp_inpcb *inp,
+ struct icmp6_hdr *icmph,
+ struct sctphdr *sh,
+ struct sockaddr *to,
+ struct sctp_tcb *stcb,
+ struct sctp_nets *net)
+{
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ struct socket *so;
+
+#endif
+
+ /* protection */
+ if ((inp == NULL) || (stcb == NULL) || (net == NULL) ||
+ (sh == NULL) || (to == NULL)) {
+ if (stcb)
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ /* First job is to verify the vtag matches what I would send */
+ if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) {
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ if (icmph->icmp6_type != ICMP_UNREACH) {
+ /* We only care about unreachable */
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+ }
+ if ((icmph->icmp6_code == ICMP_UNREACH_NET) ||
+ (icmph->icmp6_code == ICMP_UNREACH_HOST) ||
+ (icmph->icmp6_code == ICMP_UNREACH_NET_UNKNOWN) ||
+ (icmph->icmp6_code == ICMP_UNREACH_HOST_UNKNOWN) ||
+ (icmph->icmp6_code == ICMP_UNREACH_ISOLATED) ||
+ (icmph->icmp6_code == ICMP_UNREACH_NET_PROHIB) ||
+ (icmph->icmp6_code == ICMP_UNREACH_HOST_PROHIB) ||
+#if defined(__Panda__)
+ (icmph->icmp6_code == ICMP_UNREACH_ADMIN)) {
+#elif defined(__Userspace_os_NetBSD)
+ (icmph->icmp6_code == ICMP_UNREACH_ADMIN_PROHIBIT)) {
+#else
+ (icmph->icmp6_code == ICMP_UNREACH_FILTER_PROHIB)) {
+#endif
+
+ /*
+ * Hmm reachablity problems we must examine closely. If its
+ * not reachable, we may have lost a network. Or if there is
+ * NO protocol at the other end named SCTP. well we consider
+ * it a OOTB abort.
+ */
+ 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 ((icmph->icmp6_code == ICMP_UNREACH_PROTOCOL) ||
+ (icmph->icmp6_code == ICMP_UNREACH_PORT)) {
+ /*
+ * Here the peer is either playing tricks on us,
+ * including an address that belongs to someone who
+ * does not support SCTP OR was a userland
+ * implementation that shutdown and now is dead. In
+ * either case treat it like a OOTB abort with no
+ * TCB
+ */
+ sctp_abort_notification(stcb, 1, 0, NULL, SCTP_SO_NOT_LOCKED);
+#if defined(__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+ 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(SCTP_SO_LOCK_TESTING)
+ 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 {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+}
+
+
+
+#if !defined(__Panda__) && !defined(__Userspace__)
+void
+sctp6_ctlinput(int cmd, struct sockaddr *pktdst, void *d)
+{
+ struct sctphdr sh;
+ struct ip6ctlparam *ip6cp = NULL;
+ uint32_t vrf_id;
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ vrf_id = SCTP_DEFAULT_VRFID;
+#endif
+
+#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) {
+ /*
+ * XXX: We assume that when IPV6 is non NULL, M and OFF are
+ * valid.
+ */
+ /* check if we can safely examine src and dst ports */
+ struct sctp_inpcb *inp = NULL;
+ struct sctp_tcb *stcb = NULL;
+ struct sctp_nets *net = NULL;
+ struct sockaddr_in6 final;
+
+ if (ip6cp->ip6c_m == NULL)
+ return;
+
+ bzero(&sh, sizeof(sh));
+ bzero(&final, sizeof(final));
+ inp = NULL;
+ net = NULL;
+ m_copydata(ip6cp->ip6c_m, ip6cp->ip6c_off, sizeof(sh),
+ (caddr_t)&sh);
+ ip6cp->ip6c_src->sin6_port = sh.src_port;
+#ifdef HAVE_SIN6_LEN
+ final.sin6_len = sizeof(final);
+#endif
+ final.sin6_family = AF_INET6;
+#if defined(__FreeBSD__) && __FreeBSD_cc_version < 440000
+ final.sin6_addr = *ip6cp->ip6c_finaldst;
+#else
+ final.sin6_addr = ((struct sockaddr_in6 *)pktdst)->sin6_addr;
+#endif /* __FreeBSD_cc_version */
+ final.sin6_port = sh.dest_port;
+ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&final,
+ (struct sockaddr *)ip6cp->ip6c_src,
+ &inp, &net, 1, vrf_id);
+ /* inp's ref-count increased && stcb locked */
+ if (stcb != NULL && inp && (inp->sctp_socket != NULL)) {
+ if (cmd == PRC_MSGSIZE) {
+ sctp6_notify_mbuf(inp,
+ ip6cp->ip6c_icmp6,
+ &sh,
+ stcb,
+ net);
+ /* inp's ref-count reduced && stcb unlocked */
+ } else {
+ sctp6_notify(inp, ip6cp->ip6c_icmp6, &sh,
+ (struct sockaddr *)&final,
+ stcb, net);
+ /* inp's ref-count reduced && stcb unlocked */
+ }
+ } else {
+#if !defined(__Windows__)
+ if (PRC_IS_REDIRECT(cmd) && inp) {
+ in6_rtchange((struct in6pcb *)inp,
+ inet6ctlerrmap[cmd]);
+ }
+#endif
+ if (inp) {
+ /* 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
+ */
+#ifdef __FreeBSD__
+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;
+
+#if defined(__FreeBSD__) || defined(__APPLE__)
+ vrf_id = SCTP_DEFAULT_VRFID;
+#else
+ vrf_id = panda_get_vrf_from_call(); /* from connectx call? */
+#endif
+
+#if defined(__FreeBSD__) && __FreeBSD_version > 602000
+ error = priv_check(req->td, PRIV_NETINET_GETCRED);
+#elif defined(__FreeBSD__) && __FreeBSD_version >= 500000
+ error = suser(req->td);
+#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(__FreeBSD__) && __FreeBSD_version > 690000) || defined(__Windows__)
+static void
+#elif defined(__Panda__) || defined(__Userspace__)
+int
+#else
+static int
+#endif
+sctp6_abort(struct socket *so)
+{
+ struct sctp_inpcb *inp;
+ uint32_t flags;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+#if (defined(__FreeBSD__) && __FreeBSD_version > 690000) || defined(__Windows__)
+ return;
+#else
+ 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)))) {
+#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__)
+ 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__) && __FreeBSD_version > 690000) || defined(__Windows__)
+ return;
+#else
+ return (0);
+#endif
+}
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+static int
+sctp6_attach(struct socket *so, int proto SCTP_UNUSED, struct thread *p SCTP_UNUSED)
+#elif defined(__Panda__) || defined(__Userspace__)
+int
+sctp6_attach(struct socket *so, int proto SCTP_UNUSED, uint32_t vrf_id)
+#elif defined(__Windows__)
+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
+{
+ struct in6pcb *inp6;
+ int error;
+ struct sctp_inpcb *inp;
+#if !defined(__Panda__) && !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! */
+ inp6 = (struct in6pcb *)inp;
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ inp6->inp_vflag |= INP_IPV6;
+#else
+ inp->inp_vflag |= INP_IPV6;
+#endif
+#if !defined(__Panda__)
+ inp6->in6p_hops = -1; /* use kernel default */
+ inp6->in6p_cksum = -1; /* just to be sure */
+#endif
+#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.
+ */
+ inp6->inp_ip_ttl = MODULE_GLOBAL(ip_defttl);
+#endif
+ /*
+ * Hmm what about the IPSEC stuff that is missing here but in
+ * sctp_attach()?
+ */
+ SCTP_INP_WUNLOCK(inp);
+ return (0);
+}
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+static int
+sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+#elif defined(__FreeBSD__) || defined(__APPLE__)
+static int
+sctp6_bind(struct socket *so, struct sockaddr *addr, struct proc *p)
+{
+#elif defined(__Panda__) || defined(__Userspace__)
+int
+sctp6_bind(struct socket *so, struct sockaddr *addr, void * p)
+{
+#elif defined(__Windows__)
+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;
+ struct in6pcb *inp6;
+ int error;
+
+ 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(__Windows__)
+ 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
+ inp6 = (struct in6pcb *)inp;
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ inp6->inp_vflag &= ~INP_IPV4;
+ inp6->inp_vflag |= INP_IPV6;
+#else
+ inp->inp_vflag &= ~INP_IPV4;
+ inp->inp_vflag |= INP_IPV6;
+#endif
+ if ((addr != NULL) && (SCTP_IPV6_V6ONLY(inp6) == 0)) {
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ /* binding v4 addr to v6 socket, so reset flags */
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ inp6->inp_vflag |= INP_IPV4;
+ inp6->inp_vflag &= ~INP_IPV6;
+#else
+ inp->inp_vflag |= INP_IPV4;
+ inp->inp_vflag &= ~INP_IPV6;
+#endif
+ 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)) {
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ inp6->inp_vflag |= INP_IPV4;
+#else
+ inp->inp_vflag |= INP_IPV4;
+#endif
+ }
+#ifdef INET
+ if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) {
+ struct sockaddr_in sin;
+
+ in6_sin6_2_sin(&sin, sin6_p);
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__) || defined(__Userspace__)
+ inp6->inp_vflag |= INP_IPV4;
+ inp6->inp_vflag &= ~INP_IPV6;
+#else
+ inp->inp_vflag |= INP_IPV4;
+ inp->inp_vflag &= ~INP_IPV6;
+#endif
+ error = sctp_inpcb_bind(so, (struct sockaddr *)&sin, NULL, p);
+ return (error);
+ }
+#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);
+ return (EINVAL);
+ }
+#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);
+ return (EINVAL);
+ }
+ }
+ error = sctp_inpcb_bind(so, addr, NULL, p);
+ return (error);
+}
+
+
+#if (defined(__FreeBSD__) && __FreeBSD_version > 690000) || defined(__Windows__) || 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
+
+#if !defined(__Panda__)
+static
+#endif
+int
+sctp6_detach(struct socket *so)
+{
+#if defined(__Userspace__)
+ sctp_close(so);
+ return (0);
+#else
+ return (sctp_detach(so));
+#endif
+}
+
+#endif
+
+#if !defined(__Panda__) && !defined(__Userspace__)
+static
+#endif
+int
+sctp6_disconnect(struct socket *so)
+{
+ return (sctp_disconnect(so));
+}
+
+
+int
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+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(__Panda__) && !defined(__Windows__) && !defined(__Userspace__)
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+static int
+sctp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct thread *p)
+{
+#elif defined(__FreeBSD__) || 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;
+ struct in6pcb *inp6;
+
+#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);
+ }
+ inp6 = (struct in6pcb *)inp;
+ /*
+ * 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(inp6)) {
+ /*
+ * 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__)
+ /* 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.
+ */
+ int ret;
+
+ ret = sctp_output(inp, inp->pkt, addr, inp->control, p, flags);
+ inp->pkt = NULL;
+ inp->control = NULL;
+ return (ret);
+ } else {
+ return (0);
+ }
+}
+#endif
+
+#if defined(__FreeBSD__) && __FreeBSD_version >= 500000
+static int
+sctp6_connect(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+#elif defined(__FreeBSD__) || defined(__APPLE__)
+static int
+sctp6_connect(struct socket *so, struct sockaddr *addr, struct proc *p)
+{
+#elif defined(__Panda__)
+int
+sctp6_connect(struct socket *so, struct sockaddr *addr, void *p)
+{
+#elif defined(__Windows__)
+static int
+sctp6_connect(struct socket *so, struct sockaddr *addr, PKTHREAD p)
+{
+#elif defined(__Userspace__)
+int
+sctp6_connect(struct socket *so, struct sockaddr *addr)
+{
+ void *p = NULL;
+#else
+static int
+sctp6_connect(struct socket *so, struct mbuf *nam, struct proc *p)
+{
+ struct sockaddr *addr = mtod(nam, struct sockaddr *);
+#endif
+ uint32_t vrf_id;
+ int error = 0;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+#ifdef INET
+ struct in6pcb *inp6;
+ struct sockaddr_in6 *sin6;
+ union sctp_sockstore store;
+#endif
+
+#ifdef INET
+ inp6 = (struct in6pcb *)so->so_pcb;
+#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(__Windows__)
+ 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(inp6)) {
+ /*
+ * 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_UNLOCK(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, p);
+ 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);
+ }
+ stcb->asoc.state = SCTP_STATE_COOKIE_WAIT;
+ (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+
+ /* initialize authentication parameters for the assoc */
+ sctp_initialize_auth_params(inp, stcb);
+
+ sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+ SCTP_TCB_UNLOCK(stcb);
+ return (error);
+}
+
+static int
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+sctp6_getaddr(struct socket *so, struct sockaddr **addr)
+{
+ struct sockaddr_in6 *sin6;
+#elif defined(__Panda__)
+sctp6_getaddr(struct socket *so, struct sockaddr *addr)
+{
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
+#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;
+
+#ifdef SCTP_KAME
+ int error;
+#endif /* SCTP_KAME */
+
+ /*
+ * Do the malloc first in case it blocks.
+ */
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof(*sin6));
+ if (sin6 == NULL)
+ return (ENOMEM);
+#elif defined(__Panda__)
+ bzero(sin6, sizeof(*sin6));
+#else
+ SCTP_BUF_LEN(nam) = sizeof(*sin6);
+ bzero(sin6, 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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) {
+ goto notConn6;
+ }
+ 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 */
+ goto notConn6;
+ }
+ 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 */
+ notConn6:
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ (*addr) = (struct sockaddr *)sin6;
+#endif
+ return (0);
+}
+
+static int
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+sctp6_peeraddr(struct socket *so, struct sockaddr **addr)
+{
+ struct sockaddr_in6 *sin6;
+#elif defined(__Panda__)
+sctp6_peeraddr(struct socket *so, struct sockaddr *addr)
+{
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
+#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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6);
+ if (sin6 == NULL)
+ return (ENOMEM);
+#elif defined(__Panda__)
+ memset(sin6, 0, sizeof(*sin6));
+#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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ 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(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ *addr = (struct sockaddr *)sin6;
+#endif
+ return (0);
+}
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+static int
+sctp6_in6getaddr(struct socket *so, struct sockaddr **nam)
+{
+#ifdef INET
+ struct sockaddr *addr;
+#endif
+#elif defined(__Panda__)
+int
+sctp6_in6getaddr(struct socket *so, struct sockaddr *nam, uint32_t *namelen)
+{
+ struct sockaddr *addr = 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 in6pcb *inp6 = sotoin6pcb(so);
+ int error;
+
+ if (inp6 == 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) {
+ /* try v4 next if v6 failed */
+ error = sctp_ingetaddr(so, nam);
+ if (error) {
+ return (error);
+ }
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ addr = *nam;
+#endif
+ /* if I'm V6ONLY, convert it to v4-mapped */
+ if (SCTP_IPV6_V6ONLY(inp6)) {
+ struct sockaddr_in6 sin6;
+
+ in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6);
+ memcpy(addr, &sin6, sizeof(struct sockaddr_in6));
+ }
+ }
+#endif
+#if defined(__Panda__)
+ *namelen = nam->sa_len;
+#endif
+ return (error);
+}
+
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+static int
+sctp6_getpeeraddr(struct socket *so, struct sockaddr **nam)
+{
+#ifdef INET
+ struct sockaddr *addr;
+#endif
+#elif defined(__Panda__)
+int
+sctp6_getpeeraddr(struct socket *so, struct sockaddr *nam, uint32_t *namelen)
+{
+ struct sockaddr *addr = (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 in6pcb *inp6 = sotoin6pcb(so);
+ int error;
+
+ if (inp6 == 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) {
+ /* try v4 next if v6 failed */
+ error = sctp_peeraddr(so, nam);
+ if (error) {
+ return (error);
+ }
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+ addr = *nam;
+#endif
+ /* if I'm V6ONLY, convert it to v4-mapped */
+ if (SCTP_IPV6_V6ONLY(inp6)) {
+ struct sockaddr_in6 sin6;
+
+ in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6);
+ memcpy(addr, &sin6, sizeof(struct sockaddr_in6));
+ }
+ }
+#endif
+#if defined(__Panda__)
+ *namelen = nam->sa_len;
+#endif
+ return (error);
+}
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Windows__)
+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,
+#if __FreeBSD_version >= 690000
+ .pru_close = sctp6_close,
+ .pru_detach = sctp6_close,
+ .pru_sopoll = sopoll_generic,
+ .pru_flush = sctp_flush,
+#else
+ .pru_detach = sctp6_detach,
+ .pru_sopoll = sopoll,
+#endif
+ .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__)
+ .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(__Windows__)
+ 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(__Panda__) && !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 s;
+ int error = 0;
+ int family;
+ uint32_t vrf_id;
+ 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
+ );
+#ifdef INET6
+ case PF_INET6:
+ error = in6_control(so, (long)m, (caddr_t)nam,
+ (struct ifnet *)control, p);
+#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:
+ 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..3402bc08cd
--- /dev/null
+++ b/netwerk/sctp/src/netinet6/sctp6_var.h
@@ -0,0 +1,88 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet6/sctp6_var.h 243186 2012-11-17 20:04:04Z 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(struct sockaddr_in *, struct sockaddr_in6 *);
+#endif
+#endif
+#if defined(_KERNEL)
+
+#if defined(__FreeBSD__) || (__APPLE__) || defined(__Windows__)
+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__)
+int sctp6_input(struct mbuf **, int *);
+int sctp6_input_with_port(struct mbuf **, int *, uint16_t);
+#elif defined(__Panda__)
+int sctp6_input (pakhandle_type *);
+#elif defined(__FreeBSD__) && __FreeBSD_version < 902000
+int sctp6_input __P((struct mbuf **, int *, int));
+int sctp6_input_with_port __P((struct mbuf **, int *, uint16_t));
+#else
+int sctp6_input(struct mbuf **, int *, int);
+int sctp6_input_with_port(struct mbuf **, int *, uint16_t);
+#endif
+#if defined(__FreeBSD__) && __FreeBSD_version < 902000
+int sctp6_output
+__P((struct sctp_inpcb *, struct mbuf *, struct sockaddr *,
+ struct mbuf *, struct proc *));
+void sctp6_ctlinput __P((int, struct sockaddr *, void *));
+#else
+int sctp6_output(struct sctp_inpcb *, struct mbuf *, struct sockaddr *,
+ struct mbuf *, struct proc *);
+void sctp6_ctlinput(int, struct sockaddr *, void *);
+#endif
+#if !(defined(__FreeBSD__) || defined(__APPLE__))
+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
+extern void sctp6_notify(struct sctp_inpcb *, struct icmp6_hdr *,
+ struct sctphdr *, struct sockaddr *,
+ struct sctp_tcb *, struct sctp_nets *);
+#endif
+#endif
diff --git a/netwerk/sctp/src/user_atomic.h b/netwerk/sctp/src/user_atomic.h
new file mode 100755
index 0000000000..33659cd226
--- /dev/null
+++ b/netwerk/sctp/src/user_atomic.h
@@ -0,0 +1,300 @@
+/*-
+ * 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(__Userspace_os_Darwin) || defined (__Userspace_os_Windows)
+#if defined (__Userspace_os_Windows)
+#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; \
+ } \
+}
+#if defined(__Userspace_os_Windows)
+static void atomic_init() {} /* empty when we are not using atomic_mtx */
+#else
+static inline void atomic_init() {} /* empty when we are not using atomic_mtx */
+#endif
+#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() {} /* 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 (__Userspace_os_Windows)
+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() {
+ (void)pthread_mutex_init(&atomic_mtx, NULL);
+}
+static inline void atomic_destroy() {
+ (void)pthread_mutex_destroy(&atomic_mtx);
+}
+static inline void atomic_lock() {
+ (void)pthread_mutex_lock(&atomic_mtx);
+}
+static inline void atomic_unlock() {
+ (void)pthread_mutex_unlock(&atomic_mtx);
+}
+#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..6830a2368b
--- /dev/null
+++ b/netwerk/sctp/src/user_environment.c
@@ -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.
+ */
+
+/* __Userspace__ */
+
+#include <stdlib.h>
+#if !defined (__Userspace_os_Windows)
+#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;
+
+/* Source: /usr/src/sys/dev/random/harvest.c */
+static int read_random_phony(void *, int);
+
+static int (*read_func)(void *, int) = read_random_phony;
+
+/* Userland-visible version of read_random */
+int
+read_random(void *buf, int count)
+{
+ return ((*read_func)(buf, count));
+}
+
+/* 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).
+ */
+static int
+read_random_phony(void *buf, int count)
+{
+ uint32_t randval;
+ int size, i;
+
+ /* srandom() is called in kern/init_main.c:proc0_post() */
+
+ /* 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);
+}
+
diff --git a/netwerk/sctp/src/user_environment.h b/netwerk/sctp/src/user_environment.h
new file mode 100755
index 0000000000..6de9d9a5a3
--- /dev/null
+++ b/netwerk/sctp/src/user_environment.h
@@ -0,0 +1,110 @@
+/*-
+ * 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 __Userspace_os_FreeBSD
+#ifndef _SYS_MUTEX_H_
+#include <sys/mutex.h>
+#endif
+#endif
+#if defined (__Userspace_os_Windows)
+#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 ephermal 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 (__Userspace_os_Windows)
+#define min(a,b) ((a)>(b)?(b):(a))
+#define max(a,b) ((a)>(b)?(a):(b))
+#endif
+
+extern int read_random(void *buf, int count);
+
+/* 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(__Userspace_os_Linux)
+#define IPV6_VERSION 0x60
+#endif
+#if defined(INVARIANTS)
+#define panic(args...) \
+ do { \
+ SCTP_PRINTF(args);\
+ exit(1); \
+} while (0)
+#endif
+
+#if defined(INVARIANTS)
+#define KASSERT(cond, args) \
+ do { \
+ if (!(cond)) { \
+ printf args ;\
+ exit(1); \
+ } \
+ } 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..c557cd0a13
--- /dev/null
+++ b/netwerk/sctp/src/user_inpcb.h
@@ -0,0 +1,378 @@
+/*-
+ * 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> */
+
+#define in6pcb inpcb /* for KAME src sync over BSD*'s */
+#define in6p_sp inp_sp /* for KAME src sync over BSD*'s */
+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 {
+ u_int32_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 {
+ u_int16_t ie_fport; /* foreign port */
+ u_int16_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 {
+ u_int8_t inc_flags;
+ u_int8_t inc_len;
+ u_int16_t inc_pad; /* XXX alignment for in_endpoints */
+ /* protocol dependent part */
+ struct in_endpoints inc_ie;
+};
+#define inc_isipv6 inc_flags /* temp compatability */
+#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 */
+
+ u_int32_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 sotoin6pcb(so) sotoinpcb(so) /* for KAME src sync over BSD*'s */
+
+#define INP_SOCKAF(so) so->so_proto->pr_domain->dom_family
+
+#define INP_CHECK_SOCKAF(so, af) (INP_SOCKAF(so) == af)
+
+/* #ifdef _KERNEL */
+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 _KERNEL */
+
+#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..f5e4a60e4f
--- /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(__Userspace_os_Windows)
+struct ip6_hdr {
+ union {
+ struct ip6_hdrctl {
+ u_int32_t ip6_un1_flow; /* 20 bits of flow-ID */
+ u_int16_t ip6_un1_plen; /* payload length */
+ u_int8_t ip6_un1_nxt; /* next header */
+ u_int8_t ip6_un1_hlim; /* hop limit */
+ } ip6_un1;
+ u_int8_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(__Userspace_os_Windows)
+#define s6_addr16 u.Word
+#endif
+#if !defined(__Userspace_os_Windows)
+#if !defined(__Userspace_os_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(__Userspace_os_FreeBSD) && !defined(__Userspace_os_OpenBSD) && !defined(__Userspace_os_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..e713417da8
--- /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 {
+ u_int32_t ira_addr;
+ u_int32_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(__Userspace_os_Windows)
+#pragma pack (push, 1)
+struct icmp6_hdr {
+ u_int8_t icmp6_type;
+ u_int8_t icmp6_code;
+ u_int16_t icmp6_cksum;
+ union {
+ u_int32_t icmp6_un_data32[1];
+ u_int16_t icmp6_un_data16[2];
+ u_int8_t icmp6_un_data8[4];
+ } icmp6_dataun;
+};
+#pragma pack()
+
+#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;
+ u_int16_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;
+ u_int32_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..4ea954898e
--- /dev/null
+++ b/netwerk/sctp/src/user_malloc.h
@@ -0,0 +1,266 @@
+/*-
+ * 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 (__Userspace_os_Windows)
+#include <strings.h>
+#include <stdint.h>
+#else
+#if defined(_MSC_VER) && _MSC_VER >= 1600
+#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);
+
+
+/* End copy: Copied code for __Userspace__ */
+
+#if 0
+#ifdef _KERNEL
+#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 } \
+ }; \
+ SYSINIT(type##_init, SI_SUB_KMEM, SI_ORDER_SECOND, malloc_init, \
+ type); \
+ SYSUNINIT(type##_uninit, SI_SUB_KMEM, SI_ORDER_ANY, \
+ malloc_uninit, type)
+
+
+#define MALLOC_DECLARE(type) \
+ extern struct malloc_type type[1]
+
+MALLOC_DECLARE(M_CACHE);
+MALLOC_DECLARE(M_DEVBUF);
+MALLOC_DECLARE(M_TEMP);
+
+MALLOC_DECLARE(M_IP6OPT); /* for INET6 */
+MALLOC_DECLARE(M_IP6NDP); /* for INET6 */
+
+/*
+ * Deprecated macro versions of not-quite-malloc() and free().
+ */
+#define MALLOC(space, cast, size, type, flags) \
+ ((space) = (cast)malloc((u_long)(size), (type), (flags)))
+#define FREE(addr, type) free((addr), (type))
+
+/*
+ * XXX this should be declared in <sys/uio.h>, but that tends to fail
+ * because <sys/uio.h> is included in a header before the source file
+ * has a chance to include <sys/malloc.h> to get MALLOC_DECLARE() defined.
+ */
+MALLOC_DECLARE(M_IOV);
+
+extern struct mtx malloc_mtx;
+
+/* XXX struct malloc_type is unused for contig*(). */
+void contigfree(void *addr, unsigned long size, struct malloc_type *type);
+void *contigmalloc(unsigned long size, struct malloc_type *type, int flags,
+ vm_paddr_t low, vm_paddr_t high, unsigned long alignment,
+ unsigned long boundary);
+void free(void *addr, struct malloc_type *type);
+void *malloc(unsigned long size, struct malloc_type *type, int flags);
+void malloc_init(void *);
+int malloc_last_fail(void);
+void malloc_type_allocated(struct malloc_type *type, unsigned long size);
+void malloc_type_freed(struct malloc_type *type, unsigned long size);
+void malloc_uninit(void *);
+void *realloc(void *addr, unsigned long size, struct malloc_type *type,
+ int flags);
+void *reallocf(void *addr, unsigned long size, struct malloc_type *type,
+ int flags);
+
+
+#endif /* _KERNEL */
+#endif
+
+#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..de8f0fbc8c
--- /dev/null
+++ b/netwerk/sctp/src/user_mbuf.c
@@ -0,0 +1,1431 @@
+/*-
+ * 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"
+
+struct mbstat mbstat;
+#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_init() */
+ 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 int clust_constructor_dup(caddr_t m_clust, struct mbuf* m)
+{
+ u_int *refcnt;
+ int type, size;
+
+ /* 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 (refcnt == NULL) {
+#if !defined(SCTP_SIMPLE_ALLOCATOR)
+ umem_reap();
+#endif
+ 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)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 (0);
+}
+
+
+
+/* __Userspace__ */
+void
+m_clget(struct mbuf *m, int how)
+{
+ caddr_t mclust_ret;
+#if defined(SCTP_SIMPLE_ALLOCATOR)
+ struct clust_args clust_mb_args;
+#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.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, 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
+}
+
+/*
+ * 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, u_int32_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 **************/
+
+/* __Userspace__
+ * TODO: mbuf_init must be called in the initialization routines
+ * of userspace stack.
+ */
+void
+mbuf_init(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?
+ *
+ */
+
+
+ /*
+ * [Re]set counters and local statistics knobs.
+ *
+ */
+
+ mbstat.m_mbufs = 0;
+ mbstat.m_mclusts = 0;
+ mbstat.m_drain = 0;
+ mbstat.m_msize = MSIZE;
+ mbstat.m_mclbytes = MCLBYTES;
+ mbstat.m_minclsize = MINCLSIZE;
+ mbstat.m_mlen = MLEN;
+ mbstat.m_mhlen = MHLEN;
+ mbstat.m_numtypes = MT_NTYPES;
+
+ mbstat.m_mcfail = mbstat.m_mpfail = 0;
+ mbstat.sf_iocnt = 0;
+ mbstat.sf_allocwait = mbstat.sf_allocfail = 0;
+
+}
+
+
+
+/*
+ * __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 = &m->m_dat[MLEN] - (m->m_data + m->m_len);
+ do {
+ count = min(min(max(len, max_protohdr), space), n->m_len);
+ bcopy(mtod(n, caddr_t), mtod(m, caddr_t) + m->m_len,
+ (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);
+ mbstat.m_mpfail++; /* XXX: No consistency. */
+ 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;
+ bcopy(mtod(n, caddr_t) + off, mtod(n->m_next, caddr_t), 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;
+ bcopy(mtod(n, caddr_t) + off, mtod(o, caddr_t), 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));
+
+ 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
+ bcopy(mtod(m, caddr_t)+off, mtod(n, caddr_t),
+ (u_int)n->m_len);
+ if (len != M_COPYALL)
+ len -= n->m_len;
+ off = 0;
+ m = m->m_next;
+ np = &n->m_next;
+ }
+ if (top == NULL)
+ mbstat.m_mcfail++; /* XXX: No consistency. */
+
+ return (top);
+nospace:
+ m_freem(top);
+ mbstat.m_mcfail++; /* XXX: No consistency. */
+ 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)
+{
+
+ 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);
+ bcopy(t + 1, p + 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(u_int32_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;
+ bzero(mtod(n, caddr_t), 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);
+ bcopy(cp, off + mtod(m, caddr_t), (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);
+ bcopy(mtod(m, caddr_t) + off, cp, 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 */
+ bcopy(mtod(n, caddr_t), mtod(m, caddr_t) + m->m_len, (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 {
+ bcopy(mtod(m, caddr_t) + len, mtod(n, caddr_t), 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;
+ bcopy(mtod(mb, caddr_t), buffer+offset, 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..f3717407e1
--- /dev/null
+++ b/netwerk/sctp/src/user_mbuf.h
@@ -0,0 +1,438 @@
+/*-
+ * 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);
+
+
+/* mbuf initialization function */
+void mbuf_init(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(u_int32_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 */
+
+/*
+ * General mbuf allocator statistics structure.
+ * __Userspace__ mbstat may be useful for gathering statistics.
+ * In the kernel many of these statistics are no longer used as
+ * they track allocator statistics through kernel UMA's built in statistics mechanism.
+ */
+struct mbstat {
+ u_long m_mbufs; /* XXX */
+ u_long m_mclusts; /* XXX */
+
+ u_long m_drain; /* times drained protocols for space */
+ u_long m_mcfail; /* XXX: times m_copym failed */
+ u_long m_mpfail; /* XXX: times m_pullup failed */
+ u_long m_msize; /* length of an mbuf */
+ u_long m_mclbytes; /* length of an mbuf cluster */
+ u_long m_minclsize; /* min length of data to allocate a cluster */
+ u_long m_mlen; /* length of data in an mbuf */
+ u_long m_mhlen; /* length of data in a header mbuf */
+
+ /* Number of mbtypes (gives # elems in mbtypes[] array: */
+ short m_numtypes;
+
+ /* XXX: Sendfile stats should eventually move to their own struct */
+ u_long sf_iocnt; /* times sendfile had to do disk I/O */
+ u_long sf_allocfail; /* times sfbuf allocation failed */
+ u_long sf_allocwait; /* times sfbuf allocation had to wait */
+};
+
+
+/*
+ * 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 */
+ u_int16_t m_tag_id; /* Tag ID */
+ u_int16_t m_tag_len; /* Length of data */
+ u_int32_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 */
+ u_int16_t tso_segsz; /* TSO segment size */
+ u_int16_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_SKIP_FIREWALL 0x4000 /* skip firewall processing */
+#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_SKIP_FIREWALL|\
+ 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 */
+
+extern struct mbstat mbstat; /* General mbuf stats/infos */
+
+
+/*
+ * 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)
+
+#endif
diff --git a/netwerk/sctp/src/user_queue.h b/netwerk/sctp/src/user_queue.h
new file mode 100755
index 0000000000..44f8994902
--- /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 (__Userspace_os_Windows)
+#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..7b2ff58464
--- /dev/null
+++ b/netwerk/sctp/src/user_recv_thread.c
@@ -0,0 +1,1524 @@
+/*-
+ * 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(__Userspace_os_Windows)
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <pthread.h>
+#if !defined(__Userspace_os_DragonFly) && !defined(__Userspace_os_FreeBSD) && !defined(__Userspace_os_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(__Userspace_os_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(__Userspace_os_Darwin) || defined(__Userspace_os_DragonFly) || defined(__Userspace_os_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 32 /* What should this value be? */
+#define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
+#if defined(__Userspace_os_Darwin) || defined(__Userspace_os_DragonFly) || defined(__Userspace_os_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(__Userspace_os_Darwin) || defined(__Userspace_os_DragonFly) || defined(__Userspace_os_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, *found_ifa = NULL;
+
+ /* handle only the types we want */
+ if ((type != RTM_NEWADDR) && (type != RTM_DELADDR)) {
+ return;
+ }
+
+ rc = getifaddrs(&g_interfaces);
+ if (rc != 0) {
+ return;
+ }
+ for (ifa = g_interfaces; ifa; ifa = ifa->ifa_next) {
+ if (index == if_nametoindex(ifa->ifa_name)) {
+ found_ifa = ifa;
+ break;
+ }
+ }
+ if (found_ifa == NULL) {
+ return;
+ }
+
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ ifa->ifa_addr = (struct sockaddr *)malloc(sizeof(struct sockaddr_in));
+ memcpy(ifa->ifa_addr, sa, sizeof(struct sockaddr_in));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ ifa->ifa_addr = (struct sockaddr *)malloc(sizeof(struct sockaddr_in6));
+ memcpy(ifa->ifa_addr, sa, sizeof(struct sockaddr_in6));
+ break;
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_USR, "Address family %d not supported.\n", sa->sa_family);
+ }
+
+ /* relay the appropriate address change to the base code */
+ if (type == RTM_NEWADDR) {
+ (void)sctp_add_addr_to_vrf(SCTP_DEFAULT_VRFID, ifa, if_nametoindex(ifa->ifa_name),
+ 0,
+ ifa->ifa_name,
+ (void *)ifa,
+ ifa->ifa_addr,
+ 0,
+ 1);
+ } else {
+ sctp_del_addr_from_vrf(SCTP_DEFAULT_VRFID, ifa->ifa_addr,
+ if_nametoindex(ifa->ifa_name),
+ ifa->ifa_name);
+ }
+}
+
+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];
+
+ while (1) {
+ bzero(rt_buffer, 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) {
+ 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) {
+ 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;
+#if !defined(SCTP_WITH_NO_CSUM)
+ int compute_crc = 1;
+#endif
+ struct sctp_chunkhdr *ch;
+ struct sockaddr_in src, dst;
+#if !defined(__Userspace_os_Windows)
+ struct msghdr msg;
+ struct iovec recv_iovec[MAXLEN_MBUF_CHAIN];
+#else
+ WSABUF recv_iovec[MAXLEN_MBUF_CHAIN];
+ int nResult, m_ErrorCode;
+ DWORD flags;
+ 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, ncounter = 0;
+ int iovlen = MCLBYTES;
+ int want_ext = (iovlen > MLEN)? 1 : 0;
+ int want_header = 0;
+
+ bzero((void *)&src, sizeof(struct sockaddr_in));
+ bzero((void *)&dst, 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ flags = 0;
+ ncounter = 0;
+ fromlen = sizeof(struct sockaddr_in);
+ bzero((void *)&from, sizeof(struct sockaddr_in));
+
+ nResult = WSARecvFrom(SCTP_BASE_VAR(userspace_rawsctp), recv_iovec, MAXLEN_MBUF_CHAIN, (LPDWORD)&ncounter, (LPDWORD)&flags, (struct sockaddr*)&from, &fromlen, NULL, NULL);
+ if (nResult != 0) {
+ m_ErrorCode = WSAGetLastError();
+ if (m_ErrorCode == WSAETIMEDOUT) {
+ continue;
+ }
+ if ((m_ErrorCode == WSAENOTSOCK) || (m_ErrorCode == WSAEINTR)) {
+ break;
+ }
+ }
+ n = ncounter;
+#else
+ bzero((void *)&msg, 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) {
+ 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 (n <= iovlen) {
+ SCTP_BUF_LEN(recvmbuf[0]) = n;
+ (to_fill)++;
+ } else {
+ i = 0;
+ SCTP_BUF_LEN(recvmbuf[0]) = iovlen;
+
+ ncounter -= iovlen;
+ (to_fill)++;
+ do {
+ recvmbuf[i]->m_next = recvmbuf[i+1];
+ SCTP_BUF_LEN(recvmbuf[i]->m_next) = min(ncounter, iovlen);
+ i++;
+ 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))) {
+ return (NULL);
+ }
+ if (SCTP_IS_IT_BROADCAST(dst.sin_addr, recvmbuf[0])) {
+ return (NULL);
+ }
+
+ port = 0;
+
+#if defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_recvnocrc);
+#else
+ if (src.sin_addr.s_addr == dst.sin_addr.s_addr) {
+ compute_crc = 0;
+ SCTP_STAT_INCR(sctps_recvnocrc);
+ } else {
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ }
+#endif
+ 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,
+#if !defined(SCTP_WITH_NO_CSUM)
+ compute_crc,
+#endif
+ 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);
+ return (NULL);
+}
+#endif
+
+#if defined(INET6)
+static void *
+recv_function_raw6(void *arg)
+{
+ struct mbuf **recvmbuf6;
+#if !defined(__Userspace_os_Windows)
+ 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 flags;
+ struct sockaddr_in6 from;
+ int fromlen;
+ 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, ncounter = 0;
+#if !defined(SCTP_WITH_NO_CSUM)
+ int compute_crc = 1;
+#endif
+ int iovlen = MCLBYTES;
+ int want_ext = (iovlen > MLEN)? 1 : 0;
+ int want_header = 0;
+
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ flags = 0;
+ ncounter = 0;
+ fromlen = sizeof(struct sockaddr_in6);
+ bzero((void *)&from, 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 == WSAETIMEDOUT)
+ continue;
+ if (m_ErrorCode == WSAENOTSOCK || m_ErrorCode == WSAEINTR)
+ break;
+ }
+ n = ncounter;
+#else
+ bzero((void *)&msg, sizeof(struct msghdr));
+ bzero((void *)&src, sizeof(struct sockaddr_in6));
+ bzero((void *)&dst, sizeof(struct sockaddr_in6));
+ bzero((void *)cmsgbuf, 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_LEN(sizeof (struct in6_pktinfo));
+ msg.msg_flags = 0;
+
+ ncounter = n = recvmsg(SCTP_BASE_VAR(userspace_rawsctp6), &msg, 0);
+ if (n < 0) {
+ if (errno == EAGAIN) {
+ 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 (n <= iovlen) {
+ SCTP_BUF_LEN(recvmbuf6[0]) = n;
+ (to_fill)++;
+ } else {
+ i = 0;
+ SCTP_BUF_LEN(recvmbuf6[0]) = iovlen;
+
+ ncounter -= iovlen;
+ (to_fill)++;
+ do {
+ recvmbuf6[i]->m_next = recvmbuf6[i+1];
+ SCTP_BUF_LEN(recvmbuf6[i]->m_next) = min(ncounter, iovlen);
+ i++;
+ 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;
+ }
+ }
+
+ 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 defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_recvnocrc);
+#else
+ if (memcmp(&src.sin6_addr, &dst.sin6_addr, sizeof(struct in6_addr)) == 0) {
+ compute_crc = 0;
+ SCTP_STAT_INCR(sctps_recvnocrc);
+ } else {
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ }
+#endif
+ 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,
+#if !defined(SCTP_WITH_NO_CSUM)
+ compute_crc,
+#endif
+ 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);
+ 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, ncounter, offset;
+ 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
+#if !defined(SCTP_WITH_NO_CSUM)
+ int compute_crc = 1;
+#endif
+#if !defined(__Userspace_os_Windows)
+ 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;
+#endif
+
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ bzero((void *)&msg, sizeof(struct msghdr));
+#else
+ bzero((void *)&msg, sizeof(WSAMSG));
+#endif
+ bzero((void *)&src, sizeof(struct sockaddr_in));
+ bzero((void *)&dst, sizeof(struct sockaddr_in));
+ bzero((void *)cmsgbuf, sizeof(cmsgbuf));
+
+#if !defined(__Userspace_os_Windows)
+ 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) {
+ 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 == WSAETIMEDOUT) {
+ continue;
+ }
+ if ((m_ErrorCode == WSAENOTSOCK) || (m_ErrorCode == WSAEINTR)) {
+ break;
+ }
+ }
+ 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 (n <= iovlen) {
+ SCTP_BUF_LEN(udprecvmbuf[0]) = n;
+ (to_fill)++;
+ } else {
+ i = 0;
+ SCTP_BUF_LEN(udprecvmbuf[0]) = iovlen;
+
+ ncounter -= iovlen;
+ (to_fill)++;
+ do {
+ udprecvmbuf[i]->m_next = udprecvmbuf[i+1];
+ SCTP_BUF_LEN(udprecvmbuf[i]->m_next) = min(ncounter, iovlen);
+ i++;
+ 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))) {
+ return (NULL);
+ }
+ if (SCTP_IS_IT_BROADCAST(dst.sin_addr, udprecvmbuf[0])) {
+ return (NULL);
+ }
+
+ /*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 defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_recvnocrc);
+#else
+ if (src.sin_addr.s_addr == dst.sin_addr.s_addr) {
+ compute_crc = 0;
+ SCTP_STAT_INCR(sctps_recvnocrc);
+ } else {
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ }
+#endif
+ 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,
+#if !defined(SCTP_WITH_NO_CSUM)
+ compute_crc,
+#endif
+ 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);
+ 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, ncounter, offset;
+ 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))];
+#if !defined(SCTP_WITH_NO_CSUM)
+ int compute_crc = 1;
+#endif
+#if !defined(__Userspace_os_Windows)
+ 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;
+#endif
+
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ bzero((void *)&msg, sizeof(struct msghdr));
+#else
+ bzero((void *)&msg, sizeof(WSAMSG));
+#endif
+ bzero((void *)&src, sizeof(struct sockaddr_in6));
+ bzero((void *)&dst, sizeof(struct sockaddr_in6));
+ bzero((void *)cmsgbuf, CMSG_SPACE(sizeof (struct in6_pktinfo)));
+
+#if !defined(__Userspace_os_Windows)
+ 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_LEN(sizeof (struct in6_pktinfo));
+ msg.msg_flags = 0;
+
+ ncounter = n = recvmsg(SCTP_BASE_VAR(userspace_udpsctp6), &msg, 0);
+ if (n < 0) {
+ if (errno == EAGAIN) {
+ 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 == WSAETIMEDOUT) {
+ continue;
+ }
+ if ((m_ErrorCode == WSAENOTSOCK) || (m_ErrorCode == WSAEINTR)) {
+ break;
+ }
+ }
+ 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 (n <= iovlen) {
+ SCTP_BUF_LEN(udprecvmbuf6[0]) = n;
+ (to_fill)++;
+ } else {
+ i = 0;
+ SCTP_BUF_LEN(udprecvmbuf6[0]) = iovlen;
+
+ ncounter -= iovlen;
+ (to_fill)++;
+ do {
+ udprecvmbuf6[i]->m_next = udprecvmbuf6[i+1];
+ SCTP_BUF_LEN(udprecvmbuf6[i]->m_next) = min(ncounter, iovlen);
+ i++;
+ 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)) {
+ return (NULL);
+ }
+
+ 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 defined(SCTP_WITH_NO_CSUM)
+ SCTP_STAT_INCR(sctps_recvnocrc);
+#else
+ if ((memcmp(&src.sin6_addr, &dst.sin6_addr, sizeof(struct in6_addr)) == 0)) {
+ compute_crc = 0;
+ SCTP_STAT_INCR(sctps_recvnocrc);
+ } else {
+ SCTP_STAT_INCR(sctps_recvswcrc);
+ }
+#endif
+ 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,
+#if !defined(SCTP_WITH_NO_CSUM)
+ compute_crc,
+#endif
+ 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);
+ return (NULL);
+}
+#endif
+
+static void
+setReceiveBufferSize(int sfd, int new_size)
+{
+ int ch = new_size;
+
+ if (setsockopt (sfd, SOL_SOCKET, SO_RCVBUF, (void*)&ch, sizeof(ch)) < 0) {
+#if defined (__Userspace_os_Windows)
+ 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;
+}
+
+static void
+setSendBufferSize(int sfd, int new_size)
+{
+ int ch = new_size;
+
+ if (setsockopt (sfd, SOL_SOCKET, SO_SNDBUF, (void*)&ch, sizeof(ch)) < 0) {
+#if defined (__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ struct timeval timeout;
+
+ 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(__Userspace_os_Darwin) || defined(__Userspace_os_DragonFly) || defined(__Userspace_os_FreeBSD)
+ if (SCTP_BASE_VAR(userspace_route) == -1) {
+ if ((SCTP_BASE_VAR(userspace_route) = socket(AF_ROUTE, SOCK_RAW, 0)) < 0) {
+ 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(__Userspace_os_Windows)
+ 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)) < 0) {
+#if defined(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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)) < 0) {
+#if defined(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+#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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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)) < 0) {
+#if defined(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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)) < 0) {
+#if defined(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+ 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(__Userspace_os_Windows)
+#if defined(__Userspace_os_Darwin) || defined(__Userspace_os_DragonFly) || defined(__Userspace_os_FreeBSD)
+#if defined(INET) || defined(INET6)
+ if (SCTP_BASE_VAR(userspace_route) != -1) {
+ int rc;
+
+ if ((rc = pthread_create(&SCTP_BASE_VAR(recvthreadroute), NULL, &recv_function_route, NULL))) {
+ 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 = pthread_create(&SCTP_BASE_VAR(recvthreadraw), NULL, &recv_function_raw, NULL))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/IPv4 recv thread (%d).\n", rc);
+ close(SCTP_BASE_VAR(userspace_rawsctp));
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ }
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp) != -1) {
+ int rc;
+
+ if ((rc = pthread_create(&SCTP_BASE_VAR(recvthreadudp), NULL, &recv_function_udp, NULL))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/UDP/IPv4 recv thread (%d).\n", rc);
+ close(SCTP_BASE_VAR(userspace_udpsctp));
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+ }
+ }
+#endif
+#if defined(INET6)
+ if (SCTP_BASE_VAR(userspace_rawsctp6) != -1) {
+ int rc;
+
+ if ((rc = pthread_create(&SCTP_BASE_VAR(recvthreadraw6), NULL, &recv_function_raw6, NULL))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/IPv6 recv thread (%d).\n", rc);
+ close(SCTP_BASE_VAR(userspace_rawsctp6));
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ }
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp6) != -1) {
+ int rc;
+
+ if ((rc = pthread_create(&SCTP_BASE_VAR(recvthreadudp6), NULL, &recv_function_udp6, NULL))) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/UDP/IPv6 recv thread (%d).\n", rc);
+ close(SCTP_BASE_VAR(userspace_udpsctp6));
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ }
+ }
+#endif
+#else
+#if defined(INET)
+ if (SCTP_BASE_VAR(userspace_rawsctp) != -1) {
+ if ((SCTP_BASE_VAR(recvthreadraw) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&recv_function_raw, NULL, 0, NULL)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/IPv4 recv thread.\n");
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp));
+ SCTP_BASE_VAR(userspace_rawsctp) = -1;
+ }
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp) != -1) {
+ if ((SCTP_BASE_VAR(recvthreadudp) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&recv_function_udp, NULL, 0, NULL)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/UDP/IPv4 recv thread.\n");
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp));
+ SCTP_BASE_VAR(userspace_udpsctp) = -1;
+ }
+ }
+#endif
+#if defined(INET6)
+ if (SCTP_BASE_VAR(userspace_rawsctp6) != -1) {
+ if ((SCTP_BASE_VAR(recvthreadraw6) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&recv_function_raw6, NULL, 0, NULL)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/IPv6 recv thread.\n");
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp6));
+ SCTP_BASE_VAR(userspace_rawsctp6) = -1;
+ }
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp6) != -1) {
+ if ((SCTP_BASE_VAR(recvthreadudp6) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&recv_function_udp6, NULL, 0, NULL)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Can't start SCTP/UDP/IPv6 recv thread.\n");
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp6));
+ SCTP_BASE_VAR(userspace_udpsctp6) = -1;
+ }
+ }
+#endif
+#endif
+}
+
+void
+recv_thread_destroy(void)
+{
+#if defined(__Userspace_os_Darwin) || defined(__Userspace_os_DragonFly) || defined(__Userspace_os_FreeBSD)
+#if defined(INET) || defined(INET6)
+ if (SCTP_BASE_VAR(userspace_route) != -1) {
+ close(SCTP_BASE_VAR(userspace_route));
+ }
+#endif
+#endif
+#if defined(INET)
+ if (SCTP_BASE_VAR(userspace_rawsctp) != -1) {
+#if defined(__Userspace_os_Windows)
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp));
+#else
+ close(SCTP_BASE_VAR(userspace_rawsctp));
+#endif
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp) != -1) {
+#if defined(__Userspace_os_Windows)
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp));
+#else
+ close(SCTP_BASE_VAR(userspace_udpsctp));
+#endif
+ }
+#endif
+#if defined(INET6)
+ if (SCTP_BASE_VAR(userspace_rawsctp6) != -1) {
+#if defined(__Userspace_os_Windows)
+ closesocket(SCTP_BASE_VAR(userspace_rawsctp6));
+#else
+ close(SCTP_BASE_VAR(userspace_rawsctp6));
+#endif
+ }
+ if (SCTP_BASE_VAR(userspace_udpsctp6) != -1) {
+#if defined(__Userspace_os_Windows)
+ closesocket(SCTP_BASE_VAR(userspace_udpsctp6));
+#else
+ close(SCTP_BASE_VAR(userspace_udpsctp6));
+#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..7134001fe7
--- /dev/null
+++ b/netwerk/sctp/src/user_socket.c
@@ -0,0 +1,3420 @@
+/*-
+ * 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>
+#ifdef INET6
+#include <netinet6/sctp6_var.h>
+#endif
+#if defined(__Userspace_os_Linux)
+#define __FAVOR_BSD /* (on Ubuntu at least) enables UDP header field names like BSD in RFC 768 */
+#endif
+#if !defined (__Userspace_os_Windows)
+#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);
+
+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, ...))
+{
+ sctp_init(port, conn_output, debug_printf);
+}
+
+
+/* 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)
+{
+#if defined(__Userspace__) /* __Userspace__ */
+
+ SOCKBUF_LOCK_ASSERT(sb);
+
+ sb->sb_flags |= SB_WAIT;
+#if defined (__Userspace_os_Windows)
+ 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
+
+#else
+ SOCKBUF_LOCK_ASSERT(sb);
+
+ sb->sb_flags |= SB_WAIT;
+ return (msleep(&sb->sb_cc, &sb->sb_mtx,
+ (sb->sb_flags & SB_NOINTR) ? PSOCK : PSOCK | PCATCH, "sbwait",
+ sb->sb_timeo));
+#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 */
+int
+soabort(so)
+ struct socket *so;
+{
+ int error;
+#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) {
+ error = sctp6_abort(so);
+ } else {
+#if defined(INET)
+ error = sctp_abort(so);
+#else
+ error = EAFNOSUPPORT;
+#endif
+ }
+#elif defined(INET)
+ error = sctp_abort(so);
+#else
+ error = EAFNOSUPPORT;
+#endif
+ if (error) {
+ sofree(so);
+ return error;
+ }
+ return (0);
+}
+
+
+/* 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(ident, so)
+ void *ident;
+ struct socket *so;
+{
+ SOCK_LOCK(so);
+#if defined (__Userspace_os_Windows)
+ 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(ident)
+ 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 (__Userspace_os_Windows)
+ 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);
+
+}
+
+/* From /src/sys/sys/sysproto.h */
+struct sctp_generic_sendmsg_args {
+ int sd;
+ caddr_t msg;
+ int mlen;
+ caddr_t to;
+ socklen_t tolen; /* was __socklen_t */
+ struct sctp_sndrcvinfo * sinfo;
+ int flags;
+};
+
+struct sctp_generic_recvmsg_args {
+ int sd;
+ struct iovec *iov;
+ int iovlen;
+ struct sockaddr *from;
+ socklen_t *fromlenaddr; /* was __socklen_t */
+ struct sctp_sndrcvinfo *sinfo;
+ int *msg_flags;
+};
+
+
+ /*
+ Source: /src/sys/gnu/fs/xfs/FreeBSD/xfs_ioctl.c
+ */
+static __inline__ int
+copy_to_user(void *dst, void *src, int len) {
+ memcpy(dst, src, len);
+ return 0;
+}
+
+static __inline__ int
+copy_from_user(void *dst, void *src, int 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;
+ int 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 > 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)
+ bcopy(cp, iov->iov_base, cnt);
+ else
+ bcopy(iov->iov_base, cp, cnt);
+ break;
+ }
+ iov->iov_base = (char *)iov->iov_base + cnt;
+ iov->iov_len -= cnt;
+ uio->uio_resid -= cnt;
+ uio->uio_offset += cnt;
+ cp = (char *)cp + cnt;
+ n -= cnt;
+ }
+out:
+ return (error);
+}
+
+
+/* Source: src/sys/kern/uipc_syscalls.c */
+int
+getsockaddr(namp, uaddr, len)
+ 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);
+}
+
+
+/* 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,
+ u_int32_t ppid,
+ u_int32_t flags,
+ u_int16_t stream_no,
+ u_int32_t timetolive,
+ u_int32_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);
+ }
+ /* 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;
+
+ if (so == NULL) {
+ errno = EBADF;
+ return (-1);
+ }
+ memset(&sinfo, 0, sizeof(struct sctp_sndrcvinfo));
+ 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;
+ 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;
+ } 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) {
+ 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,
+ u_int32_t ppid,
+ u_int32_t flags,
+ u_int16_t stream_no,
+ u_int32_t timetolive,
+ u_int32_t context)
+{
+
+ struct sctp_sndrcvinfo sndrcvinfo, *sinfo = &sndrcvinfo;
+ /* struct uio auio;
+ struct iovec iov[1]; */
+ int error = 0;
+ int uflags = 0;
+ int retvalsendmsg;
+
+ 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)
+ retvalsendmsg = len;
+ else if(error == EWOULDBLOCK) {
+ errno = EWOULDBLOCK;
+ retvalsendmsg = (-1);
+ } else {
+ SCTP_PRINTF("%s: error = %d\n", __func__, error);
+ errno = error;
+ retvalsendmsg = (-1);
+ }
+ return retvalsendmsg;
+
+}
+
+
+/* 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;
+ int ulen, i, retval;
+ 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 != (int)ulen &&
+ (error == EINTR ||
+#if !defined(__Userspace_os_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 */
+ retval = (int)ulen - auio.uio_resid;
+ return (retval);
+ } 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;
+ int ulen, 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 != (int)ulen &&
+ (errno == EINTR ||
+#if !defined(__Userspace_os_NetBSD)
+ errno == ERESTART ||
+#endif
+ errno == EWOULDBLOCK)) {
+ errno = 0;
+ }
+ }
+ 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)) {
+ 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 (errno == 0) {
+ /* ready return value */
+ return ((int)ulen - auio.uio_resid);
+ } else {
+ return (-1);
+ }
+}
+
+
+
+
+#if defined(__Userspace__)
+/* 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);
+}
+#else
+/* The kernel version for reference is below. The #else
+ should be removed once the __Userspace__
+ version is tested.
+ * socreate returns a socket with a ref count of 1. The socket should be
+ * closed with soclose().
+ */
+int
+socreate(int dom, struct socket **aso, int type, int proto,
+ struct ucred *cred, struct thread *td)
+{
+ struct protosw *prp;
+ struct socket *so;
+ int error;
+
+ if (proto)
+ prp = pffindproto(dom, proto, type);
+ else
+ prp = pffindtype(dom, type);
+
+ if (prp == NULL || prp->pr_usrreqs->pru_attach == NULL ||
+ prp->pr_usrreqs->pru_attach == pru_attach_notsupp)
+ return (EPROTONOSUPPORT);
+
+ if (jailed(cred) && jail_socket_unixiproute_only &&
+ prp->pr_domain->dom_family != PF_LOCAL &&
+ prp->pr_domain->dom_family != PF_INET &&
+ prp->pr_domain->dom_family != PF_ROUTE) {
+ return (EPROTONOSUPPORT);
+ }
+
+ if (prp->pr_type != type)
+ return (EPROTOTYPE);
+ so = soalloc();
+ if (so == NULL)
+ return (ENOBUFS);
+
+ TAILQ_INIT(&so->so_incomp);
+ TAILQ_INIT(&so->so_comp);
+ so->so_type = type;
+ so->so_cred = crhold(cred);
+ so->so_proto = prp;
+#ifdef MAC
+ mac_create_socket(cred, so);
+#endif
+ knlist_init(&so->so_rcv.sb_sel.si_note, SOCKBUF_MTX(&so->so_rcv),
+ NULL, NULL, NULL);
+ knlist_init(&so->so_snd.sb_sel.si_note, SOCKBUF_MTX(&so->so_snd),
+ NULL, NULL, NULL);
+ so->so_count = 1;
+ /*
+ * Auto-sizing of socket buffers is managed by the protocols and
+ * the appropriate flags must be set in the pru_attach function.
+ */
+ error = (*prp->pr_usrreqs->pru_attach)(so, proto, td);
+ 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);
+}
+#endif
+
+
+
+
+/* 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);
+}
+
+#if defined(__Userspace__)
+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);
+}
+#else /* kernel version for reference */
+int
+soreserve(struct socket *so, u_long sndcc, u_long rcvcc)
+{
+ struct thread *td = curthread;
+
+ SOCKBUF_LOCK(&so->so_snd);
+ SOCKBUF_LOCK(&so->so_rcv);
+ if (sbreserve_locked(&so->so_snd, sndcc, so, td) == 0)
+ goto bad;
+ if (sbreserve_locked(&so->so_rcv, rcvcc, so, td) == 0)
+ goto bad2;
+ 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 > so->so_snd.sb_hiwat)
+ so->so_snd.sb_lowat = so->so_snd.sb_hiwat;
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ SOCKBUF_UNLOCK(&so->so_snd);
+ return (0);
+bad2:
+ sbrelease_locked(&so->so_snd, so);
+bad:
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ SOCKBUF_UNLOCK(&so->so_snd);
+ return (ENOBUFS);
+}
+#endif
+
+
+
+
+
+/* Taken from /src/sys/kern/uipc_sockbuf.c
+ * and modified for __Userspace__
+ */
+
+#if defined(__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 (__Userspace_os_Windows)
+ WakeAllConditionVariable(&(sb)->sb_cond);
+#else
+ pthread_cond_broadcast(&(sb)->sb_cond);
+#endif
+ }
+ SOCKBUF_UNLOCK(sb);
+ /*__Userspace__ what todo about so_upcall?*/
+
+}
+#else /* kernel version for reference */
+/*
+ * Wakeup processes waiting on a socket buffer. Do asynchronous notification
+ * via SIGIO if the socket has the SS_ASYNC flag set.
+ *
+ * Called with the socket buffer lock held; will release the lock by the end
+ * of the function. This allows the caller to acquire the socket buffer lock
+ * while testing for the need for various sorts of wakeup and hold it through
+ * to the point where it's no longer required. We currently hold the lock
+ * through calls out to other subsystems (with the exception of kqueue), and
+ * then release it to avoid lock order issues. It's not clear that's
+ * correct.
+ */
+void
+sowakeup(struct socket *so, struct sockbuf *sb)
+{
+
+ SOCKBUF_LOCK_ASSERT(sb);
+
+ selwakeuppri(&sb->sb_sel, PSOCK);
+ sb->sb_flags &= ~SB_SEL;
+ if (sb->sb_flags & SB_WAIT) {
+ sb->sb_flags &= ~SB_WAIT;
+ wakeup(&sb->sb_cc);
+ }
+ KNOTE_LOCKED(&sb->sb_sel.si_note, 0);
+ SOCKBUF_UNLOCK(sb);
+ if ((so->so_state & SS_ASYNC) && so->so_sigio != NULL)
+ pgsigio(&so->so_sigio, SIGIO, 0);
+ if (sb->sb_flags & SB_UPCALL)
+ (*so->so_upcall)(so, so->so_upcallarg, M_NOWAIT);
+ if (sb->sb_flags & SB_AIO)
+ aio_swake(so, sb);
+ mtx_assert(SOCKBUF_MTX(sb), MA_NOTOWNED);
+}
+#endif
+
+
+
+/* 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 (__Userspace_os_Windows)
+ 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;
+
+ 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 (__Userspace_os_Windows)
+ 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(__Userspace_os_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(__Userspace_os_NetBSD)
+ if (error == ERESTART) {
+ error = EINTR;
+ }
+#endif
+done1:
+ return (error);
+}
+
+int usrsctp_connect(struct socket *so, struct sockaddr *name, int namelen)
+{
+ struct sockaddr *sa;
+
+ 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();
+ 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);
+ }
+ 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_set_ulpinfo(struct socket *so, void *ulp_info)
+{
+ return (register_ulp_info(so, ulp_info));
+}
+
+int
+usrsctp_bindx(struct socket *so, struct sockaddr *addrs, int addrcnt, int flags)
+{
+ struct sctp_getaddresses *gaddrs;
+ struct sockaddr *sa;
+#ifdef INET
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+ int i;
+ size_t argsz;
+#if defined(INET) || defined(INET6)
+ uint16_t sport = 0;
+#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);
+ }
+ /* 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;
+ }
+ }
+#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;
+ }
+ }
+#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
+ }
+ argsz = sizeof(struct sctp_getaddresses) +
+ sizeof(struct sockaddr_storage);
+ if ((gaddrs = (struct sctp_getaddresses *)malloc(argsz)) == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ sa = addrs;
+ for (i = 0; i < addrcnt; i++) {
+#ifndef HAVE_SA_LEN
+ size_t sa_len;
+#endif
+ memset(gaddrs, 0, argsz);
+ gaddrs->sget_assoc_id = 0;
+#ifdef HAVE_SA_LEN
+ memcpy(gaddrs->addr, sa, sa->sa_len);
+ if (usrsctp_setsockopt(so, IPPROTO_SCTP, flags, gaddrs, (socklen_t)argsz) != 0) {
+ free(gaddrs);
+ 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;
+ }
+ memcpy(gaddrs->addr, sa, sa_len);
+ /*
+ * 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 ((i == 0) && (sport != 0)) {
+ switch (gaddrs->addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ sin = (struct sockaddr_in *)gaddrs->addr;
+ sin->sin_port = sport;
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)gaddrs->addr;
+ sin6->sin6_port = sport;
+ break;
+#endif
+ }
+ }
+#endif
+ if (usrsctp_setsockopt(so, IPPROTO_SCTP, flags, gaddrs, (socklen_t)argsz) != 0) {
+ free(gaddrs);
+ return (-1);
+ }
+ sa = (struct sockaddr *)((caddr_t)sa + sa_len);
+#endif
+ }
+ free(gaddrs);
+ 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
+ memcpy(cpto, at, sizeof(struct sockaddr_in));
+ cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in));
+ len += 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)) {
+ in6_sin6_2_sin((struct sockaddr_in *)cpto, (struct sockaddr_in6 *)at);
+ cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in));
+ len += sizeof(struct sockaddr_in);
+ } else {
+ memcpy(cpto, at, sizeof(struct sockaddr_in6));
+ cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in6));
+ len += sizeof(struct sockaddr_in6);
+ }
+#else
+ memcpy(cpto, at, sizeof(struct sockaddr_in6));
+ cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in6));
+ len += sizeof(struct sockaddr_in6);
+#endif
+ at = (struct sockaddr *)((caddr_t)at + sizeof(struct sockaddr_in6));
+ break;
+#endif
+ default:
+ errno = EINVAL;
+ return (-1);
+ }
+ if (len > (sizeof(buf) - sizeof(int))) {
+ /* Never enough memory */
+ errno = E2BIG;
+ return (-1);
+ }
+ cnt++;
+ }
+ /* do we have any? */
+ if (cnt == 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+ 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 = (struct sockaddr *)&addrs->addr[0];
+ cnt = 0;
+ sa = (struct sockaddr *)&addrs->addr[0];
+ 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 - sizeof(sctp_assoc_t));
+ /* 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 sockaddr_storage) +
+ 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 = (struct sockaddr *)&addrs->addr[0];
+ cnt = 0;
+ sa = (struct sockaddr *)&addrs->addr[0];
+ 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 - sizeof(sctp_assoc_t));
+ /* 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 send_len;
+ int len;
+ int send_count;
+ struct ip *ip;
+ struct udphdr *udp;
+#if !defined (__Userspace_os_Windows)
+ int res;
+#endif
+ struct sockaddr_in dst;
+#if defined (__Userspace_os_Windows)
+ WSAMSG win_msg_hdr;
+ int 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(__Userspace_os_Linux) || defined (__Userspace_os_Windows)
+ /* need to put certain fields into network order for Linux */
+ ip->ip_len = htons(ip->ip_len);
+ ip->ip_off = 0;
+#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_len = SCTP_HEADER_LEN(m); /* length of entire packet */
+ send_count = 0;
+ for (iovcnt = 0; m != NULL && iovcnt < MAXLEN_MBUF_CHAIN; m = m->m_next, iovcnt++) {
+#if !defined (__Userspace_os_Windows)
+ 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 (__Userspace_os_Windows)
+ 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 ((res = sendmsg(SCTP_BASE_VAR(userspace_rawsctp), &msg_hdr, MSG_DONTWAIT)) != send_len) {
+ *result = errno;
+ }
+ }
+ if ((use_udp_tunneling) && (SCTP_BASE_VAR(userspace_udpsctp) > -1)) {
+ if ((res = sendmsg(SCTP_BASE_VAR(userspace_udpsctp), &msg_hdr, MSG_DONTWAIT)) != send_len) {
+ *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();
+ } else if (win_sent_len != send_len) {
+ *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();
+ } else if (win_sent_len != send_len) {
+ *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 send_len;
+ int len;
+ int send_count;
+ struct ip6_hdr *ip6;
+ struct udphdr *udp;
+#if !defined (__Userspace_os_Windows)
+ int res;
+#endif
+ struct sockaddr_in6 dst;
+#if defined (__Userspace_os_Windows)
+ WSAMSG win_msg_hdr;
+ int 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? */
+#if defined(__Userspace_os_Linux) || defined (__Userspace_os_Windows)
+ /* need to put certain fields into network order for Linux */
+ ip6->ip6_plen = htons(ip6->ip6_plen);
+#endif
+ }
+
+ 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_len = SCTP_HEADER_LEN(m); /* length of entire packet */
+ send_count = 0;
+ for (iovcnt = 0; m != NULL && iovcnt < MAXLEN_MBUF_CHAIN; m = m->m_next, iovcnt++) {
+#if !defined (__Userspace_os_Windows)
+ 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 (__Userspace_os_Windows)
+ 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 ((res = sendmsg(SCTP_BASE_VAR(userspace_rawsctp6), &msg_hdr, MSG_DONTWAIT)) != send_len) {
+ *result = errno;
+ }
+ }
+ if ((use_udp_tunneling) && (SCTP_BASE_VAR(userspace_udpsctp6) > -1)) {
+ if ((res = sendmsg(SCTP_BASE_VAR(userspace_udpsctp6), &msg_hdr, MSG_DONTWAIT)) != send_len) {
+ *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();
+ } else if (win_sent_len != send_len) {
+ *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();
+ } else if (win_sent_len != send_len) {
+ *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(void *buf, size_t len, int outbound)
+{
+ size_t i, pos;
+ char *dump_buf, *packet;
+#ifdef _WIN32
+ struct timeb tb;
+ struct tm t;
+#else
+ struct timeval tv;
+ struct tm *t;
+ 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);
+ _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));
+#else
+ gettimeofday(&tv, NULL);
+ sec = (time_t)tv.tv_sec;
+ t = localtime((const time_t *)&sec);
+ snprintf(dump_buf, PREAMBLE_LENGTH + 1, PREAMBLE_FORMAT,
+ outbound ? 'O' : 'I',
+ t->tm_hour, t->tm_min, t->tm_sec, (long)tv.tv_usec);
+#endif
+ pos += PREAMBLE_LENGTH;
+#ifdef _WIN32
+ 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++] = ' ';
+ }
+#ifdef _WIN32
+ 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_conninput(void *addr, const void *buffer, size_t length, uint8_t ecn_bits)
+{
+ struct sockaddr_conn src, dst;
+ struct mbuf *m;
+ struct sctphdr *sh;
+ struct sctp_chunkhdr *ch;
+
+ 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(length, 1, M_NOWAIT, 0, MT_DATA)) == NULL) {
+ return;
+ }
+ m_copyback(m, 0, 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), length,
+ (struct sockaddr *)&src,
+ (struct sockaddr *)&dst,
+ sh, ch,
+#if !defined(SCTP_WITH_NO_CSUM)
+ 1,
+#endif
+ ecn_bits,
+ SCTP_DEFAULT_VRFID, 0);
+ if (m) {
+ sctp_m_freem(m);
+ }
+ return;
+}
+
+
+#define USRSCTP_SYSCTL_SET_DEF(__field) \
+void usrsctp_sysctl_set_ ## __field(uint32_t value) { \
+ SCTP_BASE_SYSCTL(__field) = value; \
+}
+
+USRSCTP_SYSCTL_SET_DEF(sctp_sendspace)
+USRSCTP_SYSCTL_SET_DEF(sctp_recvspace)
+USRSCTP_SYSCTL_SET_DEF(sctp_auto_asconf)
+USRSCTP_SYSCTL_SET_DEF(sctp_multiple_asconfs)
+USRSCTP_SYSCTL_SET_DEF(sctp_ecn_enable)
+USRSCTP_SYSCTL_SET_DEF(sctp_pr_enable)
+USRSCTP_SYSCTL_SET_DEF(sctp_auth_enable)
+USRSCTP_SYSCTL_SET_DEF(sctp_asconf_enable)
+USRSCTP_SYSCTL_SET_DEF(sctp_reconfig_enable)
+USRSCTP_SYSCTL_SET_DEF(sctp_nrsack_enable)
+USRSCTP_SYSCTL_SET_DEF(sctp_pktdrop_enable)
+USRSCTP_SYSCTL_SET_DEF(sctp_strict_sacks)
+#if !defined(SCTP_WITH_NO_CSUM)
+USRSCTP_SYSCTL_SET_DEF(sctp_no_csum_on_loopback)
+#endif
+USRSCTP_SYSCTL_SET_DEF(sctp_peer_chunk_oh)
+USRSCTP_SYSCTL_SET_DEF(sctp_max_burst_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_max_chunks_on_queue)
+USRSCTP_SYSCTL_SET_DEF(sctp_hashtblsize)
+USRSCTP_SYSCTL_SET_DEF(sctp_pcbtblsize)
+USRSCTP_SYSCTL_SET_DEF(sctp_min_split_point)
+USRSCTP_SYSCTL_SET_DEF(sctp_chunkscale)
+USRSCTP_SYSCTL_SET_DEF(sctp_delayed_sack_time_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_sack_freq_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_system_free_resc_limit)
+USRSCTP_SYSCTL_SET_DEF(sctp_asoc_free_resc_limit)
+USRSCTP_SYSCTL_SET_DEF(sctp_heartbeat_interval_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_pmtu_raise_time_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_shutdown_guard_time_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_secret_lifetime_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_rto_max_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_rto_min_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_rto_initial_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_init_rto_max_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_valid_cookie_life_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_init_rtx_max_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_assoc_rtx_max_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_path_rtx_max_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_add_more_threshold)
+USRSCTP_SYSCTL_SET_DEF(sctp_nr_outgoing_streams_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_cmt_on_off)
+USRSCTP_SYSCTL_SET_DEF(sctp_cmt_use_dac)
+USRSCTP_SYSCTL_SET_DEF(sctp_use_cwnd_based_maxburst)
+USRSCTP_SYSCTL_SET_DEF(sctp_nat_friendly)
+USRSCTP_SYSCTL_SET_DEF(sctp_L2_abc_variable)
+USRSCTP_SYSCTL_SET_DEF(sctp_mbuf_threshold_count)
+USRSCTP_SYSCTL_SET_DEF(sctp_do_drain)
+USRSCTP_SYSCTL_SET_DEF(sctp_hb_maxburst)
+USRSCTP_SYSCTL_SET_DEF(sctp_abort_if_one_2_one_hits_limit)
+USRSCTP_SYSCTL_SET_DEF(sctp_strict_data_order)
+USRSCTP_SYSCTL_SET_DEF(sctp_min_residual)
+USRSCTP_SYSCTL_SET_DEF(sctp_max_retran_chunk)
+USRSCTP_SYSCTL_SET_DEF(sctp_logging_level)
+USRSCTP_SYSCTL_SET_DEF(sctp_default_cc_module)
+USRSCTP_SYSCTL_SET_DEF(sctp_default_frag_interleave)
+USRSCTP_SYSCTL_SET_DEF(sctp_mobility_base)
+USRSCTP_SYSCTL_SET_DEF(sctp_mobility_fasthandoff)
+USRSCTP_SYSCTL_SET_DEF(sctp_inits_include_nat_friendly)
+USRSCTP_SYSCTL_SET_DEF(sctp_udp_tunneling_port)
+USRSCTP_SYSCTL_SET_DEF(sctp_enable_sack_immediately)
+USRSCTP_SYSCTL_SET_DEF(sctp_vtag_time_wait)
+USRSCTP_SYSCTL_SET_DEF(sctp_blackhole)
+USRSCTP_SYSCTL_SET_DEF(sctp_diag_info_code)
+USRSCTP_SYSCTL_SET_DEF(sctp_fr_max_burst_default)
+USRSCTP_SYSCTL_SET_DEF(sctp_path_pf_threshold)
+USRSCTP_SYSCTL_SET_DEF(sctp_default_ss_module)
+USRSCTP_SYSCTL_SET_DEF(sctp_rttvar_bw)
+USRSCTP_SYSCTL_SET_DEF(sctp_rttvar_rtt)
+USRSCTP_SYSCTL_SET_DEF(sctp_rttvar_eqret)
+USRSCTP_SYSCTL_SET_DEF(sctp_steady_step)
+USRSCTP_SYSCTL_SET_DEF(sctp_use_dccc_ecn)
+USRSCTP_SYSCTL_SET_DEF(sctp_buffer_splitting)
+USRSCTP_SYSCTL_SET_DEF(sctp_initial_cwnd)
+#ifdef SCTP_DEBUG
+USRSCTP_SYSCTL_SET_DEF(sctp_debug_on)
+#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_strict_sacks)
+#if !defined(SCTP_WITH_NO_CSUM)
+USRSCTP_SYSCTL_GET_DEF(sctp_no_csum_on_loopback)
+#endif
+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_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_strict_data_order)
+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..5c3d9eee7c
--- /dev/null
+++ b/netwerk/sctp/src/user_socketvar.h
@@ -0,0 +1,842 @@
+/*-
+ * 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(__Userspace_os_Darwin)
+#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(__Userspace_os_DragonFly) && !defined(__Userspace_os_FreeBSD) && !defined(__Userspace_os_NetBSD) && !defined(__Userspace_os_Windows) && !defined(__Userspace_os_NaCl)
+#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(__Userspace_os_Darwin) || defined(__Userspace_os_DragonFly) || defined(__Userspace_os_FreeBSD) || defined(__Userspace_os_OpenBSD) || defined (__Userspace_os_Windows) || defined(__Userspace_os_NaCl)
+#define UIO_MAXIOV 1024
+#define ERESTART (-1)
+#endif
+
+#if !defined(__Userspace_os_Darwin) && !defined(__Userspace_os_NetBSD) && !defined(__Userspace_os_OpenBSD)
+enum uio_rw { UIO_READ, UIO_WRITE };
+#endif
+
+#if !defined(__Userspace_os_NetBSD) && !defined(__Userspace_os_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 */
+ int 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 (__Userspace_os_Windows)
+#define AF_ROUTE 17
+typedef __int32 pid_t;
+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(__Userspace_os_Windows)
+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;
+#define ACCEPT_LOCK_ASSERT() KASSERT(pthread_mutex_trylock(&accept_mtx) == EBUSY, ("%s: accept_mtx not locked", __func__))
+#define ACCEPT_LOCK() (void)pthread_mutex_lock(&accept_mtx)
+#define ACCEPT_UNLOCK() (void)pthread_mutex_unlock(&accept_mtx)
+#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 (__Userspace_os_Windows)
+#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
+#define SOCKBUF_LOCK_INIT(_sb, _name) \
+ pthread_mutex_init(SOCKBUF_MTX(_sb), NULL)
+#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 */
+
+/*
+ * Externalized form of struct socket used by the sysctl(3) interface.
+ */
+struct xsocket {
+ size_t xso_len; /* length of this structure */
+ struct socket *xso_so; /* makes a convenient handle sometimes */
+ short so_type;
+ short so_options;
+ short so_linger;
+ short so_state;
+ caddr_t so_pcb; /* another convenient handle */
+ int xso_protocol;
+ int xso_family;
+ u_short so_qlen;
+ u_short so_incqlen;
+ u_short so_qlimit;
+ short so_timeo;
+ u_short so_error;
+ pid_t so_pgid;
+ u_long so_oobmark;
+ struct xsockbuf {
+ u_int sb_cc;
+ u_int sb_hiwat;
+ u_int sb_mbcnt;
+ u_int sb_mbmax;
+ int sb_lowat;
+ int sb_timeo;
+ short sb_flags;
+ } so_rcv, so_snd;
+ uid_t so_uid; /* XXX */
+};
+
+#if defined(_KERNEL)
+
+
+/*
+ * Macros for sockets and socket buffering.
+ */
+
+/*
+ * 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)
+
+/*
+ * How much space is there in a socket buffer (so->so_snd or so->so_rcv)?
+ * This is problematical if the fields are unsigned, as the space might
+ * still be negative (cc > hiwat or mbcnt > mbmax). Should detect
+ * overflow and return 0. Should use "lmin" but it doesn't exist now.
+ */
+#define sbspace(sb) \
+ ((long) imin((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) \
+ ((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)
+
+/* 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_proto->pr_flags&PR_CONNREQUIRED)==0)) || \
+ ((so)->so_snd.sb_state & SBS_CANTSENDMORE) || \
+ (so)->so_error)
+
+/* adjust counters in sb reflecting allocation of m */
+#define sballoc(sb, m) { \
+ (sb)->sb_cc += (m)->m_len; \
+ if ((m)->m_type != MT_DATA && (m)->m_type != MT_OOBDATA) \
+ (sb)->sb_ctl += (m)->m_len; \
+ (sb)->sb_mbcnt += MSIZE; \
+ if ((m)->m_flags & M_EXT) \
+ (sb)->sb_mbcnt += (m)->m_ext.ext_size; \
+}
+
+/* adjust counters in sb reflecting freeing of m */
+#define sbfree(sb, m) { \
+ (sb)->sb_cc -= (m)->m_len; \
+ if ((m)->m_type != MT_DATA && (m)->m_type != MT_OOBDATA) \
+ (sb)->sb_ctl -= (m)->m_len; \
+ (sb)->sb_mbcnt -= MSIZE; \
+ if ((m)->m_flags & M_EXT) \
+ (sb)->sb_mbcnt -= (m)->m_ext.ext_size; \
+ if ((sb)->sb_sndptr == (m)) { \
+ (sb)->sb_sndptr = NULL; \
+ (sb)->sb_sndptroff = 0; \
+ } \
+ if ((sb)->sb_sndptroff != 0) \
+ (sb)->sb_sndptroff -= (m)->m_len; \
+}
+
+/*
+ * soref()/sorele() ref-count the socket structure. Note that you must
+ * still explicitly close the socket, but the last ref count will free
+ * the structure.
+ */
+#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)
+
+#define sotryfree(so) do { \
+ ACCEPT_LOCK_ASSERT(); \
+ SOCK_LOCK_ASSERT(so); \
+ if ((so)->so_count == 0) \
+ sofree(so); \
+ else { \
+ SOCK_UNLOCK(so); \
+ ACCEPT_UNLOCK(); \
+ } \
+} while(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)
+
+/*
+ * Argument structure for sosetopt et seq. This is in the KERNEL
+ * section because it will never be visible to user code.
+ */
+enum sopt_dir { SOPT_GET, SOPT_SET };
+struct sockopt {
+ enum sopt_dir sopt_dir; /* is this a get or a set? */
+ int sopt_level; /* second arg of [gs]etsockopt */
+ int sopt_name; /* third arg of [gs]etsockopt */
+ void *sopt_val; /* fourth arg of [gs]etsockopt */
+ size_t sopt_valsize; /* (almost) fifth arg of [gs]etsockopt */
+ struct thread *sopt_td; /* calling thread or null if kernel */
+};
+
+struct accept_filter {
+ char accf_name[16];
+ void (*accf_callback)
+ (struct socket *so, void *arg, int waitflag);
+ void * (*accf_create)
+ (struct socket *so, char *arg);
+ void (*accf_destroy)
+ (struct socket *so);
+ SLIST_ENTRY(accept_filter) accf_next;
+};
+
+extern int maxsockets;
+extern u_long sb_max;
+extern struct uma_zone *socket_zone;
+extern so_gen_t so_gencnt;
+
+struct mbuf;
+struct sockaddr;
+struct ucred;
+struct uio;
+
+/*
+ * From uipc_socket and friends
+ */
+int do_getopt_accept_filter(struct socket *so, struct sockopt *sopt);
+int do_setopt_accept_filter(struct socket *so, struct sockopt *sopt);
+int so_setsockopt(struct socket *so, int level, int optname,
+ void *optval, size_t optlen);
+int sockargs(struct mbuf **mp, caddr_t buf, int buflen, int type);
+int getsockaddr(struct sockaddr **namp, caddr_t uaddr, size_t len);
+void sbappend(struct sockbuf *sb, struct mbuf *m);
+void sbappend_locked(struct sockbuf *sb, struct mbuf *m);
+void sbappendstream(struct sockbuf *sb, struct mbuf *m);
+void sbappendstream_locked(struct sockbuf *sb, struct mbuf *m);
+int sbappendaddr(struct sockbuf *sb, const struct sockaddr *asa,
+ struct mbuf *m0, struct mbuf *control);
+int sbappendaddr_locked(struct sockbuf *sb, const struct sockaddr *asa,
+ struct mbuf *m0, struct mbuf *control);
+int sbappendcontrol(struct sockbuf *sb, struct mbuf *m0,
+ struct mbuf *control);
+int sbappendcontrol_locked(struct sockbuf *sb, struct mbuf *m0,
+ struct mbuf *control);
+void sbappendrecord(struct sockbuf *sb, struct mbuf *m0);
+void sbappendrecord_locked(struct sockbuf *sb, struct mbuf *m0);
+void sbcheck(struct sockbuf *sb);
+void sbcompress(struct sockbuf *sb, struct mbuf *m, struct mbuf *n);
+struct mbuf *
+ sbcreatecontrol(caddr_t p, int size, int type, int level);
+void sbdestroy(struct sockbuf *sb, struct socket *so);
+void sbdrop(struct sockbuf *sb, int len);
+void sbdrop_locked(struct sockbuf *sb, int len);
+void sbdroprecord(struct sockbuf *sb);
+void sbdroprecord_locked(struct sockbuf *sb);
+void sbflush(struct sockbuf *sb);
+void sbflush_locked(struct sockbuf *sb);
+void sbrelease(struct sockbuf *sb, struct socket *so);
+void sbrelease_locked(struct sockbuf *sb, struct socket *so);
+int sbreserve(struct sockbuf *sb, u_long cc, struct socket *so,
+ struct thread *td);
+int sbreserve_locked(struct sockbuf *sb, u_long cc, struct socket *so,
+ struct thread *td);
+struct mbuf *
+ sbsndptr(struct sockbuf *sb, u_int off, u_int len, u_int *moff);
+void sbtoxsockbuf(struct sockbuf *sb, struct xsockbuf *xsb);
+int sbwait(struct sockbuf *sb);
+int sblock(struct sockbuf *sb, int flags);
+void sbunlock(struct sockbuf *sb);
+void soabort(struct socket *so);
+int soaccept(struct socket *so, struct sockaddr **nam);
+int socheckuid(struct socket *so, uid_t uid);
+int sobind(struct socket *so, struct sockaddr *nam, struct thread *td);
+void socantrcvmore(struct socket *so);
+void socantrcvmore_locked(struct socket *so);
+void socantsendmore(struct socket *so);
+void socantsendmore_locked(struct socket *so);
+int soclose(struct socket *so);
+int soconnect(struct socket *so, struct sockaddr *nam, struct thread *td);
+int soconnect2(struct socket *so1, struct socket *so2);
+int socow_setup(struct mbuf *m0, struct uio *uio);
+int socreate(int dom, struct socket **aso, int type, int proto,
+ struct ucred *cred, struct thread *td);
+int sodisconnect(struct socket *so);
+struct sockaddr *sodupsockaddr(const struct sockaddr *sa, int mflags);
+void sofree(struct socket *so);
+int sogetopt(struct socket *so, struct sockopt *sopt);
+void sohasoutofband(struct socket *so);
+void soisconnected(struct socket *so);
+void soisconnecting(struct socket *so);
+void soisdisconnected(struct socket *so);
+void soisdisconnecting(struct socket *so);
+int solisten(struct socket *so, int backlog, struct thread *td);
+void solisten_proto(struct socket *so, int backlog);
+int solisten_proto_check(struct socket *so);
+struct socket *
+ sonewconn(struct socket *head, int connstatus);
+int sooptcopyin(struct sockopt *sopt, void *buf, size_t len, size_t minlen);
+int sooptcopyout(struct sockopt *sopt, const void *buf, size_t len);
+
+/* XXX; prepare mbuf for (__FreeBSD__ < 3) routines. */
+int soopt_getm(struct sockopt *sopt, struct mbuf **mp);
+int soopt_mcopyin(struct sockopt *sopt, struct mbuf *m);
+int soopt_mcopyout(struct sockopt *sopt, struct mbuf *m);
+
+int sopoll(struct socket *so, int events, struct ucred *active_cred,
+ struct thread *td);
+int sopoll_generic(struct socket *so, int events,
+ struct ucred *active_cred, struct thread *td);
+int soreceive(struct socket *so, struct sockaddr **paddr, struct uio *uio,
+ struct mbuf **mp0, struct mbuf **controlp, int *flagsp);
+int soreceive_generic(struct socket *so, struct sockaddr **paddr,
+ struct uio *uio, struct mbuf **mp0, struct mbuf **controlp,
+ int *flagsp);
+int soreserve(struct socket *so, u_long sndcc, u_long rcvcc);
+void sorflush(struct socket *so);
+int sosend(struct socket *so, struct sockaddr *addr, struct uio *uio,
+ struct mbuf *top, struct mbuf *control, int flags,
+ struct thread *td);
+int sosend_dgram(struct socket *so, struct sockaddr *addr,
+ struct uio *uio, struct mbuf *top, struct mbuf *control,
+ int flags, struct thread *td);
+int sosend_generic(struct socket *so, struct sockaddr *addr,
+ struct uio *uio, struct mbuf *top, struct mbuf *control,
+ int flags, struct thread *td);
+int sosetopt(struct socket *so, struct sockopt *sopt);
+int soshutdown(struct socket *so, int how);
+void sotoxsocket(struct socket *so, struct xsocket *xso);
+void sowakeup(struct socket *so, struct sockbuf *sb);
+
+#ifdef SOCKBUF_DEBUG
+void sblastrecordchk(struct sockbuf *, const char *, int);
+#define SBLASTRECORDCHK(sb) sblastrecordchk((sb), __FILE__, __LINE__)
+
+void sblastmbufchk(struct sockbuf *, const char *, int);
+#define SBLASTMBUFCHK(sb) sblastmbufchk((sb), __FILE__, __LINE__)
+#else
+#define SBLASTRECORDCHK(sb) /* nothing */
+#define SBLASTMBUFCHK(sb) /* nothing */
+#endif /* SOCKBUF_DEBUG */
+
+/*
+ * Accept filter functions (duh).
+ */
+int accept_filt_add(struct accept_filter *filt);
+int accept_filt_del(char *name);
+struct accept_filter *accept_filt_get(char *name);
+#ifdef ACCEPT_FILTER_MOD
+#ifdef SYSCTL_DECL
+SYSCTL_DECL(_net_inet_accf);
+#endif
+int accept_filt_generic_mod_event(module_t mod, int event, void *data);
+#endif
+
+#endif /* _KERNEL */
+
+
+/*-------------------------------------------------------------*/
+/*-------------------------------------------------------------*/
+/* __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 .
+ */
+#if defined(__Userspace__)
+
+/* ---------------------------------------------------------- */
+/* --- 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);
+
+
+
+/* -------------- */
+/* --- 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);
+#if defined(__Userspace__)
+extern int sctpconn_bind(struct socket *so, struct sockaddr *addr);
+#endif
+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);
+#if defined(__Userspace__)
+extern int sctpconn_connect(struct socket *so, struct sockaddr *addr);
+#endif
+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 /* __Userspace__ */
+
+#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..1bdefdb809
--- /dev/null
+++ b/netwerk/sctp/src/user_uma.h
@@ -0,0 +1,98 @@
+/*-
+ * 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, u_int32_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, u_int32_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..afd46562d4
--- /dev/null
+++ b/netwerk/sctp/src/usrsctp.h
@@ -0,0 +1,1220 @@
+/*-
+ * 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 int16_t __int16
+#define int32_t __int32
+#endif
+
+#define ssize_t __int64
+#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;
+
+#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 {
+#if defined(INET)
+ struct sockaddr_in sin;
+#endif
+#if defined(INET6)
+ struct sockaddr_in6 sin6;
+#endif
+ struct sockaddr_conn sconn;
+ struct sockaddr sa;
+};
+
+#define SCTP_FUTURE_ASSOC 0
+#define SCTP_CURRENT_ASSOC 1
+#define SCTP_ALL_ASSOC 2
+
+/*** 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_MAX 0x05
+
+/* 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[4];
+};
+
+/* 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_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_ENABLE_STREAM_RESET 0x00000900 /* struct sctp_assoc_value */
+
+/*
+ * 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
+
+/*
+ * 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_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;
+};
+
+
+/* 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, ...));
+
+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_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);
+
+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 *);
+
+#define SCTP_DUMP_OUTBOUND 1
+#define SCTP_DUMP_INBOUND 0
+
+char *
+usrsctp_dumppacket(void *, size_t, int);
+
+void
+usrsctp_freedumpbuffer(char *);
+
+#define USRSCTP_SYSCTL_DECL(__field) \
+void 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_strict_sacks)
+#if !defined(SCTP_WITH_NO_CSUM)
+USRSCTP_SYSCTL_DECL(sctp_no_csum_on_loopback)
+#endif
+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_hashtblsize)
+USRSCTP_SYSCTL_DECL(sctp_pcbtblsize)
+USRSCTP_SYSCTL_DECL(sctp_min_split_point)
+USRSCTP_SYSCTL_DECL(sctp_chunkscale)
+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_strict_data_order)
+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_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_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_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..4a49cfaacb
--- /dev/null
+++ b/netwerk/sctp/update_sctp.sh
@@ -0,0 +1,33 @@
+#!/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 SVN checkout of the libsctp source
+#
+# sctp usrlib source is available (via svn) at:
+# svn co https://sctp-refimpl.googlecode.com/svn/trunk sctpSVN
+#
+# 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; svnversion -n)`
+
+ cp $1/KERN/usrsctp/usrsctplib/*.c $1/KERN/usrsctp/usrsctplib/*.h netwerk/sctp/src
+ cp $1/KERN/usrsctp/usrsctplib/netinet/*.c $1/KERN/usrsctp/usrsctplib/netinet/*.h netwerk/sctp/src/netinet
+ cp $1/KERN/usrsctp/usrsctplib/netinet6/*.c $1/KERN/usrsctp/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 SVN on $date" >> netwerk/sctp/sctp_update.log
+ echo "sctp updated to version $revision from SVN on $date"
+ echo "WARNING: reapply any local patches!"
+else
+ echo "usage: $0 path_to_sctpSVN_directory"
+ echo "run from the root of your m-c clone"
+fi
diff --git a/netwerk/socket/moz.build b/netwerk/socket/moz.build
new file mode 100644
index 0000000000..34361a10c7
--- /dev/null
+++ b/netwerk/socket/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/.
+
+XPIDL_SOURCES += [
+ 'nsISocketProvider.idl',
+ 'nsISocketProviderService.idl',
+ 'nsISOCKSSocketInfo.idl',
+ 'nsISSLSocketControl.idl',
+ 'nsITransportSecurityInfo.idl',
+]
+
+XPIDL_MODULE = 'necko_socket'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
+
+UNIFIED_SOURCES += [
+ 'nsSocketProviderService.cpp',
+ 'nsSOCKSIOLayer.cpp',
+ 'nsSOCKSSocketProvider.cpp',
+ 'nsUDPSocketProvider.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ XPIDL_SOURCES += [
+ 'nsINamedPipeService.idl',
+ ]
+ UNIFIED_SOURCES += [
+ 'nsNamedPipeIOLayer.cpp',
+ 'nsNamedPipeService.cpp'
+ ]
+
+FINAL_LIBRARY = 'xul'
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..b8c2b41848
--- /dev/null
+++ b/netwerk/socket/nsISSLSocketControl.idl
@@ -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 "nsISupports.idl"
+
+interface nsIInterfaceRequestor;
+interface nsIX509Cert;
+
+%{C++
+#include "nsTArrayForwardDeclare.h"
+class nsCString;
+%}
+[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);
+
+ /* 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;
+
+ /* 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.
+ */
+ boolean joinConnection(
+ in ACString npnProtocol, /* e.g. "spdy/2" */
+ 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;
+
+ /* 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 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;
+
+ /**
+ * bypassAuthentication is true if the server certificate checks are
+ * not be enforced. This is to enable non-secure transport over TLS.
+ */
+ [infallible] readonly attribute boolean bypassAuthentication;
+
+ /*
+ * failedVerification is true if any enforced certificate checks have failed.
+ * Connections that have not yet tried to verify, have verifications bypassed,
+ * or are using acceptable exceptions will all return false.
+ */
+ [infallible] readonly attribute boolean failedVerification;
+};
+
diff --git a/netwerk/socket/nsISocketProvider.idl b/netwerk/socket/nsISocketProvider.idl
new file mode 100644
index 0000000000..19b010eda8
--- /dev/null
+++ b/netwerk/socket/nsISocketProvider.idl
@@ -0,0 +1,124 @@
+/* -*- 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 NeckoOriginAttributes(mozilla::NeckoOriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes);
+
+%{ 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 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,
+ 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 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;
+
+ /**
+ * This flag is an explicit opt-in that allows a normally secure socket
+ * provider to use, at its discretion, an insecure algorithm. e.g.
+ * a TLS socket without authentication.
+ */
+ const unsigned long MITM_OK = 1 << 3;
+
+ /**
+ * 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 << 4;
+};
+
+%{C++
+/**
+ * nsISocketProvider implementations should be registered with XPCOM under a
+ * contract ID of the form: "@mozilla.org/network/socket;2?type=foo"
+ */
+#define NS_NETWORK_SOCKET_CONTRACTID_PREFIX \
+ "@mozilla.org/network/socket;2?type="
+%}
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..bec59c8e53
--- /dev/null
+++ b/netwerk/socket/nsITransportSecurityInfo.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIX509CertList;
+
+[scriptable, uuid(216112d3-28bc-4671-b057-f98cc09ba1ea)]
+interface nsITransportSecurityInfo : nsISupports {
+ readonly attribute unsigned long securityState;
+ readonly attribute wstring errorMessage;
+ readonly attribute long errorCode; // PRErrorCode
+
+ /**
+ * 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 null.
+ */
+ readonly attribute nsIX509CertList failedCertChain;
+};
+
diff --git a/netwerk/socket/nsNamedPipeIOLayer.cpp b/netwerk/socket/nsNamedPipeIOLayer.cpp
new file mode 100644
index 0000000000..6de51ea1c3
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeIOLayer.cpp
@@ -0,0 +1,952 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <utility>
+#include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Move.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "nsINamedPipeService.h"
+#include "nsISupportsImpl.h"
+#include "nsIThread.h"
+#include "nsNamedPipeIOLayer.h"
+#include "nsNetCID.h"
+#include "nspr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+#include "nsThreadUtils.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 nsACString& 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(do_GetService(NS_NAMEDPIPESERVICE_CONTRACTID))
+ , 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([]{}),
+ 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([]{}),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+// Named pipe operations
+
+nsresult
+NamedPipeInfo::Connect(const nsACString& aPath)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ HANDLE pipe;
+ nsAutoCString path(aPath);
+
+ pipe = CreateFileA(path.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(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv = mNamedPipeService->RemoveDataObserver(mPipe, this);
+ 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 NS_OK;
+}
+
+int32_t
+NamedPipeInfo::Read(void* aBuffer, int32_t aSize)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ if (!mHasPendingWrite) {
+ return true;
+ }
+ return WaitForSingleObject(mWriteOverlapped.hEvent, aTimeout) == WAIT_OBJECT_0;
+}
+
+void
+NamedPipeInfo::SetNonblocking(bool nonblocking)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mNonblocking = nonblocking;
+}
+
+bool
+NamedPipeInfo::IsConnected() const
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mPipe && mPipe != INVALID_HANDLE_VALUE;
+}
+
+bool
+NamedPipeInfo::IsNonblocking() const
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mNonblocking;
+}
+
+HANDLE
+NamedPipeInfo::GetHandle() const
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mPipe;
+}
+
+
+int16_t
+NamedPipeInfo::GetPollFlags(int16_t aInFlags, int16_t* aOutFlags)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ *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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ NamedPipeInfo* info = GetNamedPipeInfo(aFd);
+ if (!info) {
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return PR_FAILURE;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(info->Connect(
+ nsDependentCString(aAddr->local.path))))) {
+ return PR_FAILURE;
+ }
+
+ return PR_SUCCESS;
+}
+
+static PRStatus
+nsNamedPipeConnectContinue(PRFileDesc* aFd, PRInt16 aOutFlags)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ return PR_SUCCESS;
+}
+
+static PRStatus
+nsNamedPipeClose(PRFileDesc* aFd)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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_DELETE(aFd);
+
+ return PR_SUCCESS;
+}
+
+static PRInt32
+nsNamedPipeSend(PRFileDesc* aFd,
+ const void* aBuffer,
+ PRInt32 aAmount,
+ PRIntn aFlags,
+ PRIntervalTime aTimeout)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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(PR_GetCurrentThread() == gSocketThread);
+
+ 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, NS_LITERAL_CSTRING("\\\\.\\pipe\\"));
+}
+
+PRFileDesc*
+CreateNamedPipeLayer()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ 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..3c5a893d9d
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeIOLayer.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 mozilla_netwerk_socket_nsNamedPipeIOLayer_h
+#define mozilla_netwerk_socket_nsNamedPipeIOLayer_h
+
+#include "nscore.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..8e72cc8ef0
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeService.cpp
@@ -0,0 +1,324 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIThread.h"
+#include "nsNamedPipeService.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.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__))
+
+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([self = 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;
+}
+
+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;
+}
+
+static NS_DEFINE_CID(kNamedPipeServiceCID, NS_NAMEDPIPESERVICE_CID);
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/socket/nsNamedPipeService.h b/netwerk/socket/nsNamedPipeService.h
new file mode 100644
index 0000000000..1b4f97cacb
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeService.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 "nsTArray.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
+
+ explicit NamedPipeService();
+
+ nsresult Init();
+
+private:
+ virtual ~NamedPipeService() = default;
+ 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
+};
+
+} // 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..22f5751fbe
--- /dev/null
+++ b/netwerk/socket/nsSOCKSIOLayer.cpp
@@ -0,0 +1,1612 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab 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 "nspr.h"
+#include "private/pprio.h"
+#include "nsString.h"
+#include "nsCRT.h"
+
+#include "nsIServiceManager.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);
+
+ 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;
+ 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)
+ , mVersion(-1)
+ , mDestinationFamily(AF_INET)
+ , mFlags(0)
+ , mTimeout(PR_INTERVAL_NO_TIMEOUT)
+{
+ 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);
+ }
+ NS_NOTREACHED("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)
+{
+ mVersion = version;
+ mDestinationFamily = family;
+ mProxy = proxy;
+ mDestinationHost = host;
+ mFlags = flags;
+ 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);
+
+ mFD = fd;
+ nsresult rv = dns->AsyncResolve(proxyHost, 0, this,
+ NS_GetCurrentThread(),
+ 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 {
+ if (addresses++) {
+ mDnsRec->ReportUnusable(proxyPort);
+ }
+
+ rv = mDnsRec->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];
+ NetAddrToString(&mInternalProxyAddr, 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."));
+ 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,
+ 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_DELETE(layer);
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ADDREF(infoObject);
+ infoObject->Init(socksVersion, family, proxy, host, flags);
+ 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_DELETE(layer);
+ 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, NS_LITERAL_CSTRING("file:"));
+#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..afbbc36067
--- /dev/null
+++ b/netwerk/socket/nsSOCKSIOLayer.h
@@ -0,0 +1,25 @@
+/* -*- 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 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,
+ 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..c62534f7bf
--- /dev/null
+++ b/netwerk/socket/nsSOCKSSocketProvider.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIServiceManager.h"
+#include "nsNamedPipeIOLayer.h"
+#include "nsSOCKSSocketProvider.h"
+#include "nsSOCKSIOLayer.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+
+using mozilla::NeckoOriginAttributes;
+
+//////////////////////////////////////////////////////////////////////////
+
+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;
+}
+
+NS_IMETHODIMP
+nsSOCKSSocketProvider::NewSocket(int32_t family,
+ const char *host,
+ int32_t port,
+ nsIProxyInfo *proxy,
+ const NeckoOriginAttributes &originAttributes,
+ uint32_t flags,
+ PRFileDesc **result,
+ nsISupports **socksInfo)
+{
+ PRFileDesc *sock;
+
+#if defined(XP_WIN)
+ nsAutoCString proxyHost;
+ proxy->GetHost(proxyHost);
+ if (IsNamedPipePath(proxyHost)) {
+ sock = CreateNamedPipeLayer();
+ } else
+#endif
+ {
+ sock = PR_OpenTCPSocket(family);
+ if (!sock) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ nsresult rv = nsSOCKSIOLayerAddToSocket(family,
+ host,
+ port,
+ proxy,
+ mVersion,
+ flags,
+ 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 NeckoOriginAttributes &originAttributes,
+ uint32_t flags,
+ PRFileDesc *sock,
+ nsISupports **socksInfo)
+{
+ nsresult rv = nsSOCKSIOLayerAddToSocket(family,
+ host,
+ port,
+ proxy,
+ mVersion,
+ flags,
+ 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..d468ec36ed
--- /dev/null
+++ b/netwerk/socket/nsSOCKSSocketProvider.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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() {}
+
+ 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..b3fc5d9e72
--- /dev/null
+++ b/netwerk/socket/nsSocketProviderService.cpp
@@ -0,0 +1,45 @@
+/* -*- 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 "nsString.h"
+#include "nsIServiceManager.h"
+#include "nsISocketProvider.h"
+#include "nsSocketProviderService.h"
+#include "nsError.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsSocketProviderService::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsISocketProviderService> inst = new nsSocketProviderService();
+ if (!inst)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ else
+ rv = inst->QueryInterface(aIID, aResult);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsSocketProviderService, nsISocketProviderService)
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsSocketProviderService::GetSocketProvider(const char *type,
+ nsISocketProvider **result)
+{
+ nsresult rv;
+ nsAutoCString contractID(
+ NS_LITERAL_CSTRING(NS_NETWORK_SOCKET_CONTRACTID_PREFIX) +
+ nsDependentCString(type));
+
+ rv = CallGetService(contractID.get(), result);
+ if (NS_FAILED(rv))
+ rv = NS_ERROR_UNKNOWN_SOCKET_TYPE;
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/socket/nsSocketProviderService.h b/netwerk/socket/nsSocketProviderService.h
new file mode 100644
index 0000000000..4082abcfa5
--- /dev/null
+++ b/netwerk/socket/nsSocketProviderService.h
@@ -0,0 +1,24 @@
+/* -*- 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 nsSocketProviderService_h__
+#define nsSocketProviderService_h__
+
+#include "nsISocketProviderService.h"
+
+class nsSocketProviderService : public nsISocketProviderService
+{
+ virtual ~nsSocketProviderService() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISOCKETPROVIDERSERVICE
+
+ nsSocketProviderService() {}
+
+ static nsresult Create(nsISupports *, REFNSIID aIID, void **aResult);
+};
+
+#endif /* nsSocketProviderService_h__ */
diff --git a/netwerk/socket/nsUDPSocketProvider.cpp b/netwerk/socket/nsUDPSocketProvider.cpp
new file mode 100644
index 0000000000..552c26ba2e
--- /dev/null
+++ b/netwerk/socket/nsUDPSocketProvider.cpp
@@ -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 "nsUDPSocketProvider.h"
+
+#include "nspr.h"
+
+using mozilla::NeckoOriginAttributes;
+
+NS_IMPL_ISUPPORTS(nsUDPSocketProvider, nsISocketProvider)
+
+nsUDPSocketProvider::~nsUDPSocketProvider()
+{
+}
+
+NS_IMETHODIMP
+nsUDPSocketProvider::NewSocket(int32_t aFamily,
+ const char *aHost,
+ int32_t aPort,
+ nsIProxyInfo *aProxy,
+ const NeckoOriginAttributes &originAttributes,
+ uint32_t aFlags,
+ 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 NeckoOriginAttributes &originAttributes,
+ uint32_t aFlags,
+ struct PRFileDesc * aFileDesc,
+ nsISupports **aSecurityInfo)
+{
+ // does not make sense to strap a UDP socket onto an existing socket
+ NS_NOTREACHED("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..32c0e3b255
--- /dev/null
+++ b/netwerk/socket/nsUDPSocketProvider.h
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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();
+
+};
+
+#endif /* nsUDPSocketProvider_h__ */
+
diff --git a/netwerk/srtp/src/LICENSE b/netwerk/srtp/src/LICENSE
new file mode 100644
index 0000000000..dd43240cae
--- /dev/null
+++ b/netwerk/srtp/src/LICENSE
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright (c) 2001-2006 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 b/netwerk/srtp/src/README
new file mode 100644
index 0000000000..08fafaedbc
--- /dev/null
+++ b/netwerk/srtp/src/README
@@ -0,0 +1,174 @@
+Secure RTP (SRTP) Reference Implementation
+David A. McGrew
+Cisco Systems, Inc.
+mcgrew@cisco.com
+
+
+This package provides an implementation of the Secure Real-time
+Transport Protocol (SRTP), the Universal Security Transform (UST), and
+a supporting cryptographic kernel. These mechanisms are documented in
+the Internet Drafts in the doc/ subdirectory. The SRTP API is
+documented in include/srtp.h, and the library is in libsrtp.a (after
+compilation). An overview and reference manual is available in
+doc/libsrtp.pdf. The PDF documentation is more up to date than this
+file.
+
+
+Installation:
+
+./configure [ options ] # GNU autoconf script
+make # or gmake if needed; use GNU make
+
+The configure script accepts the following options:
+
+ --help provides a usage summary
+ --disable-debug compile without the runtime debugging system
+ --enable-syslog use syslog for error reporting
+ --disable-stdout use stdout for error reporting
+ --enable-console use /dev/console for error reporting
+ --gdoi use GDOI key management (disabled at present)
+
+By default, debbuging is enabled and stdout is used for debugging.
+You can use the above configure options to have the debugging output
+sent to syslog or the system console. Alternatively, you can define
+ERR_REPORTING_FILE in include/conf.h to be any other file that can be
+opened by libSRTP, and debug messages will be sent to it.
+
+This package has been tested on Mac OS X (powerpc-apple-darwin1.4),
+Cygwin (i686-pc-cygwin), and Sparc (sparc-sun-solaris2.6). Previous
+versions have been tested on Linux and OpenBSD on both x86 and sparc
+platforms.
+
+A quick tour of this package:
+
+Makefile targets: all, clean, ...
+README this file
+CHANGES change log
+VERSION version number of this package
+LICENSE legal details (it's a BSD-like license)
+crypto/ciphers/ ciphers (null, aes_icm, ...)
+crypto/math/ crypto math routines
+crypto/hash/ crypto hashing (hmac, tmmhv2, ...)
+crypto/replay/ replay protection
+doc/ documentation: rfcs, apis, and suchlike
+include/ include files for all code in distribution
+srtp/ secure real-time transport protocol implementation
+tables/ apps for generating tables (useful in porting)
+test/ test drivers
+
+
+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 (n.b. not used in libsrtp)
+ 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: rtpw [-d <debug>]* [-k <key> [-a][-e]] [-s | -r] dest_ip dest_port
+or rtpw -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.
+
+ options:
+
+ -s (s)rtp sender - causes app to send words
+
+ -r (s)rtp receive - causes app to receve words
+
+ -k <key> use srtp master key <key>, where the
+ key is a hexadecimal value (without the
+ leading "0x")
+
+ -e encrypt/decrypt (for data confidentiality)
+ (requires use of -k option as well)
+
+ -a message authentication
+ (requires use of -k option as well)
+
+ -l list 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).
+
+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:
+
+set k=c1eec3717da76195bb878578790af71c4ee9f859e197a414a78d5abc7451
+
+[sh1]$ test/rtpw -s -k $k -ea 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 -ea 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
+...
+
+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 is 128 bits in length, and is hard-coded to this
+ value for now.
+
+
diff --git a/netwerk/srtp/src/VERSION b/netwerk/srtp/src/VERSION
new file mode 100644
index 0000000000..1c99cf0e80
--- /dev/null
+++ b/netwerk/srtp/src/VERSION
@@ -0,0 +1 @@
+1.4.4
diff --git a/netwerk/srtp/src/configure.in b/netwerk/srtp/src/configure.in
new file mode 100644
index 0000000000..c55d73833e
--- /dev/null
+++ b/netwerk/srtp/src/configure.in
@@ -0,0 +1,209 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(srtp)
+
+dnl Must come before AC_PROG_CC
+if test -z "$CFLAGS"; then
+ dnl Default value for CFLAGS if not specified.
+ CFLAGS="-Wall -O4 -fexpensive-optimizations -funroll-loops"
+fi
+
+dnl Checks for programs.
+AC_PROG_RANLIB
+AC_PROG_CC
+AC_PROG_INSTALL
+
+dnl Check the byte order
+AC_C_BIGENDIAN
+
+AC_CANONICAL_HOST
+
+dnl check host_cpu type, set defines appropriately
+case $host_cpu in
+ i*86 | x86_64 )
+ AC_DEFINE(CPU_CISC, 1,
+ [Define if building for a CISC machine (e.g. Intel).])
+ AC_DEFINE(HAVE_X86, 1,
+ [Define to use X86 inlined assembly code]);;
+ * )
+ # CPU_RISC is only supported for big endian machines.
+ if test "$ac_cv_c_bigendian" = "yes"; then
+ AC_DEFINE(CPU_RISC, 1,
+ [Define if building for a RISC machine (assume slow byte access).])
+ else
+ AC_DEFINE(CPU_CISC, 1)
+ fi
+ ;;
+esac
+
+dnl Check if we are on a Windows platform.
+case $host_os in
+ *cygwin*|*mingw* )
+ EXE=.exe
+ HOST_IS_WINDOWS=yes
+ ;;
+ * )
+ EXE=""
+ ;;
+esac
+AC_SUBST(EXE) # define executable suffix; this is needed for `make clean'
+
+
+AC_ARG_ENABLE(kernel-linux,
+ [AS_HELP_STRING([--enable-kernel-linux],
+ [build library to run in Linux kernel context])],
+ [], enable_kernel_linux=no)
+AC_MSG_CHECKING(whether to build for Linux kernel context)
+if test "$enable_kernel_linux" = "yes"; then
+ AC_DEFINE(SRTP_KERNEL, 1,
+ [Define to compile for kernel contexts.])
+ AC_DEFINE(SRTP_KERNEL_LINUX, 1,
+ [Define to compile for Linux kernel context.])
+fi
+AC_MSG_RESULT($enable_kernel_linux)
+
+if test "$cross_compiling" != yes -a "$HOST_IS_WINDOWS" != yes; then
+ dnl Check for /dev/urandom
+ AC_CHECK_FILE(/dev/urandom, DEV_URANDOM=/dev/urandom,
+ [AC_CHECK_FILE(/dev/random, DEV_URANDOM=/dev/random)])
+fi
+
+AC_MSG_CHECKING(which random device to use)
+if test "$enable_kernel_linux" = "yes"; then
+ RNG_OBJS=rand_linux_kernel.o
+ AC_MSG_RESULT([Linux kernel builtin])
+else
+ RNG_OBJS=rand_source.o
+ if test -n "$DEV_URANDOM"; then
+ AC_DEFINE_UNQUOTED(DEV_URANDOM, "$DEV_URANDOM",[Path to random device])
+ AC_MSG_RESULT([$DEV_URANDOM])
+ else
+ AC_MSG_RESULT([standard rand() function...])
+ fi
+fi
+AC_SUBST(RNG_OBJS)
+
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(stdlib.h)
+AC_CHECK_HEADERS(unistd.h)
+AC_CHECK_HEADERS(byteswap.h)
+AC_CHECK_HEADERS(stdint.h)
+AC_CHECK_HEADERS(sys/uio.h)
+AC_CHECK_HEADERS(inttypes.h)
+AC_CHECK_HEADERS(sys/types.h)
+AC_CHECK_HEADERS(machine/types.h)
+AC_CHECK_HEADERS(sys/int_types.h)
+
+dnl socket() and friends
+AC_CHECK_HEADERS(sys/socket.h netinet/in.h arpa/inet.h)
+AC_CHECK_HEADERS(windows.h, [AC_CHECK_HEADERS(winsock2.h)])
+
+AC_CHECK_HEADERS(syslog.h)
+
+AC_CHECK_TYPES([int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,uint64_t])
+AC_CHECK_SIZEOF(unsigned long)
+AC_CHECK_SIZEOF(unsigned long long)
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_C_INLINE
+AC_TYPE_SIZE_T
+
+dnl Checks for library functions.
+AC_CHECK_FUNCS(socket inet_aton usleep sigaction)
+
+dnl Find socket function if not found yet.
+if test "x$ac_cv_func_socket" = "xno"; then
+ AC_CHECK_LIB(socket, socket)
+ AC_MSG_CHECKING([for socket in -lwsock32])
+ SAVELIBS="$LIBS"
+ LIBS="$LIBS -lwsock32"
+ AC_TRY_LINK([
+#include <winsock2.h>
+],[
+socket(0, 0, 0);
+],
+ ac_cv_func_socket=yes
+ AC_MSG_RESULT(yes),
+ LIBS="$SAVELIBS"
+ AC_MSG_RESULT(no))
+fi
+
+AC_MSG_CHECKING(whether to compile in debugging)
+AC_ARG_ENABLE(debug,
+ [AS_HELP_STRING([--disable-debug],
+ [do not compile in dynamic debugging system])],
+ [], enable_debug=yes)
+if test "$enable_debug" = "yes"; then
+ AC_DEFINE(ENABLE_DEBUGGING, 1,
+ [Define to compile in dynamic debugging system.])
+fi
+AC_MSG_RESULT($enable_debug)
+
+AC_MSG_CHECKING(whether to use ISMAcryp code)
+AC_ARG_ENABLE(generic-aesicm,
+ [AS_HELP_STRING([--enable-generic-aesicm],
+ [compile in changes for ISMAcryp])],
+ [], enable_generic_aesicm=no)
+if test "$enable_generic_aesicm" = "yes"; then
+ AC_DEFINE(GENERIC_AESICM, 1, [Define this to use ISMAcryp code.])
+fi
+AC_MSG_RESULT($enable_generic_aesicm)
+
+AC_MSG_CHECKING(whether to use syslog for error reporting)
+AC_ARG_ENABLE(syslog,
+ [AS_HELP_STRING([--enable-syslog], [use syslog for error reporting])],
+ [], enable_syslog=no)
+if test "$enable_syslog" = "yes"; then
+ AC_DEFINE(USE_SYSLOG, 1, [Define to use syslog logging.])
+fi
+AC_MSG_RESULT($enable_syslog)
+
+AC_MSG_CHECKING(whether to use stdout for error reporting)
+AC_ARG_ENABLE(stdout,
+ [AS_HELP_STRING([--disable-stdout], [don't use stdout for error reporting])],
+ [], enable_stdout=yes)
+if test "$enable_stdout" = "yes"; then
+ AC_DEFINE(ERR_REPORTING_STDOUT, 1, [Define to use logging to stdout.])
+fi
+AC_MSG_RESULT($enable_stdout)
+
+AC_MSG_CHECKING(whether to use /dev/console for error reporting)
+AC_ARG_ENABLE(console,
+ [AS_HELP_STRING([--enable-console], [use /dev/console for error reporting])],
+ [], enable_console=no)
+if test "$enable_console" = "yes"; then
+ AC_DEFINE(USE_ERR_REPORTING_FILE, 1, [Write errors to this file])
+ AC_DEFINE(ERR_REPORTING_FILE, "/dev/console", [Report errors to this file.])
+fi
+AC_MSG_RESULT($enable_console)
+
+AC_MSG_CHECKING(whether to use GDOI key management)
+AC_ARG_ENABLE(gdoi,
+ [AS_HELP_STRING([--enable-gdoi], [enable GDOI key management])],
+ [], enable_gdoi=no)
+if test "$enable_gdoi" = "yes"; then
+ AC_DEFINE(SRTP_GDOI, 1, [Define to use GDOI.])
+ GDOI_OBJS=gdoi/srtp+gdoi.o
+ AC_SUBST(GDOI_OBJS)
+fi
+AC_MSG_RESULT($enable_gdoi)
+
+AC_CONFIG_HEADER(crypto/include/config.h:config_in.h)
+
+AC_OUTPUT(Makefile crypto/Makefile doc/Makefile)
+
+# This is needed when building outside the source dir.
+AS_MKDIR_P(crypto/ae_xfm)
+AS_MKDIR_P(crypto/cipher)
+AS_MKDIR_P(crypto/hash)
+AS_MKDIR_P(crypto/kernel)
+AS_MKDIR_P(crypto/math)
+AS_MKDIR_P(crypto/replay)
+AS_MKDIR_P(crypto/rng)
+AS_MKDIR_P(crypto/test)
+AS_MKDIR_P(doc)
+AS_MKDIR_P(srtp)
+AS_MKDIR_P(tables)
+AS_MKDIR_P(test)
diff --git a/netwerk/srtp/src/crypto/ae_xfm/xfm.c b/netwerk/srtp/src/crypto/ae_xfm/xfm.c
new file mode 100644
index 0000000000..c3c08d9c7b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/ae_xfm/xfm.c
@@ -0,0 +1,605 @@
+/*
+ * xfm.c
+ *
+ * Crypto transform implementation
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "cryptoalg.h"
+#include "aes_cbc.h"
+#include "hmac.h"
+#include "crypto_kernel.h" /* for crypto_get_random() */
+
+#define KEY_LEN 16
+#define ENC_KEY_LEN 16
+#define MAC_KEY_LEN 16
+#define IV_LEN 16
+#define TAG_LEN 12
+#define MAX_EXPAND 27
+
+err_status_t
+aes_128_cbc_hmac_sha1_96_func(void *key,
+ void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *opaque,
+ unsigned *opaque_len,
+ void *auth_tag) {
+ aes_cbc_ctx_t aes_ctx;
+ hmac_ctx_t hmac_ctx;
+ unsigned char enc_key[ENC_KEY_LEN];
+ unsigned char mac_key[MAC_KEY_LEN];
+ err_status_t status;
+
+ /* check if we're doing authentication only */
+ if ((iv == NULL) && (opaque == NULL) && (opaque_len == NULL)) {
+
+ /* perform authentication only */
+
+ } else if ((iv == NULL) || (opaque == NULL) || (opaque_len == NULL)) {
+
+ /*
+ * bad parameter - we expect either all three pointers to be NULL,
+ * or none of those pointers to be NULL
+ */
+ return err_status_fail;
+
+ } else {
+
+ /* derive encryption and authentication keys from the input key */
+ status = hmac_init(&hmac_ctx, key, KEY_LEN);
+ if (status) return status;
+ status = hmac_compute(&hmac_ctx, "ENC", 3, ENC_KEY_LEN, enc_key);
+ if (status) return status;
+
+ status = hmac_init(&hmac_ctx, key, KEY_LEN);
+ if (status) return status;
+ status = hmac_compute(&hmac_ctx, "MAC", 3, MAC_KEY_LEN, mac_key);
+ if (status) return status;
+
+
+ /* perform encryption and authentication */
+
+ /* set aes key */
+ status = aes_cbc_context_init(&aes_ctx, key, ENC_KEY_LEN, direction_encrypt);
+ if (status) return status;
+
+ /* set iv */
+ status = crypto_get_random(iv, IV_LEN);
+ if (status) return status;
+ status = aes_cbc_set_iv(&aes_ctx, iv);
+
+ /* encrypt the opaque data */
+ status = aes_cbc_nist_encrypt(&aes_ctx, opaque, opaque_len);
+ if (status) return status;
+
+ /* authenticate clear and opaque data */
+ status = hmac_init(&hmac_ctx, mac_key, MAC_KEY_LEN);
+ if (status) return status;
+
+ status = hmac_start(&hmac_ctx);
+ if (status) return status;
+
+ status = hmac_update(&hmac_ctx, clear, clear_len);
+ if (status) return status;
+
+ status = hmac_compute(&hmac_ctx, opaque, *opaque_len, TAG_LEN, auth_tag);
+ if (status) return status;
+
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+aes_128_cbc_hmac_sha1_96_inv(void *key,
+ void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *opaque,
+ unsigned *opaque_len,
+ void *auth_tag) {
+ aes_cbc_ctx_t aes_ctx;
+ hmac_ctx_t hmac_ctx;
+ unsigned char enc_key[ENC_KEY_LEN];
+ unsigned char mac_key[MAC_KEY_LEN];
+ unsigned char tmp_tag[TAG_LEN];
+ unsigned char *tag = auth_tag;
+ err_status_t status;
+ int i;
+
+ /* check if we're doing authentication only */
+ if ((iv == NULL) && (opaque == NULL) && (opaque_len == NULL)) {
+
+ /* perform authentication only */
+
+ } else if ((iv == NULL) || (opaque == NULL) || (opaque_len == NULL)) {
+
+ /*
+ * bad parameter - we expect either all three pointers to be NULL,
+ * or none of those pointers to be NULL
+ */
+ return err_status_fail;
+
+ } else {
+
+ /* derive encryption and authentication keys from the input key */
+ status = hmac_init(&hmac_ctx, key, KEY_LEN);
+ if (status) return status;
+ status = hmac_compute(&hmac_ctx, "ENC", 3, ENC_KEY_LEN, enc_key);
+ if (status) return status;
+
+ status = hmac_init(&hmac_ctx, key, KEY_LEN);
+ if (status) return status;
+ status = hmac_compute(&hmac_ctx, "MAC", 3, MAC_KEY_LEN, mac_key);
+ if (status) return status;
+
+ /* perform encryption and authentication */
+
+ /* set aes key */
+ status = aes_cbc_context_init(&aes_ctx, key, ENC_KEY_LEN, direction_decrypt);
+ if (status) return status;
+
+ /* set iv */
+ status = rand_source_get_octet_string(iv, IV_LEN);
+ if (status) return status;
+ status = aes_cbc_set_iv(&aes_ctx, iv);
+
+ /* encrypt the opaque data */
+ status = aes_cbc_nist_decrypt(&aes_ctx, opaque, opaque_len);
+ if (status) return status;
+
+ /* authenticate clear and opaque data */
+ status = hmac_init(&hmac_ctx, mac_key, MAC_KEY_LEN);
+ if (status) return status;
+
+ status = hmac_start(&hmac_ctx);
+ if (status) return status;
+
+ status = hmac_update(&hmac_ctx, clear, clear_len);
+ if (status) return status;
+
+ status = hmac_compute(&hmac_ctx, opaque, *opaque_len, TAG_LEN, tmp_tag);
+ if (status) return status;
+
+ /* compare the computed tag with the one provided as input */
+ for (i=0; i < TAG_LEN; i++)
+ if (tmp_tag[i] != tag[i])
+ return err_status_auth_fail;
+
+ }
+
+ return err_status_ok;
+}
+
+
+#define ENC 1
+
+#define DEBUG 0
+
+err_status_t
+aes_128_cbc_hmac_sha1_96_enc(void *key,
+ const void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *opaque,
+ unsigned *opaque_len) {
+ aes_cbc_ctx_t aes_ctx;
+ hmac_ctx_t hmac_ctx;
+ unsigned char enc_key[ENC_KEY_LEN];
+ unsigned char mac_key[MAC_KEY_LEN];
+ unsigned char *auth_tag;
+ err_status_t status;
+
+ /* check if we're doing authentication only */
+ if ((iv == NULL) && (opaque == NULL) && (opaque_len == NULL)) {
+
+ /* perform authentication only */
+
+ } else if ((iv == NULL) || (opaque == NULL) || (opaque_len == NULL)) {
+
+ /*
+ * bad parameter - we expect either all three pointers to be NULL,
+ * or none of those pointers to be NULL
+ */
+ return err_status_fail;
+
+ } else {
+
+#if DEBUG
+ printf("ENC using key %s\n", octet_string_hex_string(key, KEY_LEN));
+#endif
+
+ /* derive encryption and authentication keys from the input key */
+ status = hmac_init(&hmac_ctx, key, KEY_LEN);
+ if (status) return status;
+ status = hmac_compute(&hmac_ctx, "ENC", 3, ENC_KEY_LEN, enc_key);
+ if (status) return status;
+
+ status = hmac_init(&hmac_ctx, key, KEY_LEN);
+ if (status) return status;
+ status = hmac_compute(&hmac_ctx, "MAC", 3, MAC_KEY_LEN, mac_key);
+ if (status) return status;
+
+
+ /* perform encryption and authentication */
+
+ /* set aes key */
+ status = aes_cbc_context_init(&aes_ctx, key, ENC_KEY_LEN, direction_encrypt);
+ if (status) return status;
+
+ /* set iv */
+ status = rand_source_get_octet_string(iv, IV_LEN);
+ if (status) return status;
+ status = aes_cbc_set_iv(&aes_ctx, iv);
+ if (status) return status;
+
+#if DEBUG
+ printf("plaintext len: %d\n", *opaque_len);
+ printf("iv: %s\n", octet_string_hex_string(iv, IV_LEN));
+ printf("plaintext: %s\n", octet_string_hex_string(opaque, *opaque_len));
+#endif
+
+#if ENC
+ /* encrypt the opaque data */
+ status = aes_cbc_nist_encrypt(&aes_ctx, opaque, opaque_len);
+ if (status) return status;
+#endif
+
+#if DEBUG
+ printf("ciphertext len: %d\n", *opaque_len);
+ printf("ciphertext: %s\n", octet_string_hex_string(opaque, *opaque_len));
+#endif
+
+ /*
+ * authenticate clear and opaque data, then write the
+ * authentication tag to the location immediately following the
+ * ciphertext
+ */
+ status = hmac_init(&hmac_ctx, mac_key, MAC_KEY_LEN);
+ if (status) return status;
+
+ status = hmac_start(&hmac_ctx);
+ if (status) return status;
+
+ status = hmac_update(&hmac_ctx, clear, clear_len);
+ if (status) return status;
+#if DEBUG
+ printf("hmac input: %s\n",
+ octet_string_hex_string(clear, clear_len));
+#endif
+ auth_tag = (unsigned char *)opaque;
+ auth_tag += *opaque_len;
+ status = hmac_compute(&hmac_ctx, opaque, *opaque_len, TAG_LEN, auth_tag);
+ if (status) return status;
+#if DEBUG
+ printf("hmac input: %s\n",
+ octet_string_hex_string(opaque, *opaque_len));
+#endif
+ /* bump up the opaque_len to reflect the authentication tag */
+ *opaque_len += TAG_LEN;
+
+#if DEBUG
+ printf("prot data len: %d\n", *opaque_len);
+ printf("prot data: %s\n", octet_string_hex_string(opaque, *opaque_len));
+#endif
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+aes_128_cbc_hmac_sha1_96_dec(void *key,
+ const void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *opaque,
+ unsigned *opaque_len) {
+ aes_cbc_ctx_t aes_ctx;
+ hmac_ctx_t hmac_ctx;
+ unsigned char enc_key[ENC_KEY_LEN];
+ unsigned char mac_key[MAC_KEY_LEN];
+ unsigned char tmp_tag[TAG_LEN];
+ unsigned char *auth_tag;
+ unsigned ciphertext_len;
+ err_status_t status;
+ int i;
+
+ /* check if we're doing authentication only */
+ if ((iv == NULL) && (opaque == NULL) && (opaque_len == NULL)) {
+
+ /* perform authentication only */
+
+ } else if ((iv == NULL) || (opaque == NULL) || (opaque_len == NULL)) {
+
+ /*
+ * bad parameter - we expect either all three pointers to be NULL,
+ * or none of those pointers to be NULL
+ */
+ return err_status_fail;
+
+ } else {
+#if DEBUG
+ printf("DEC using key %s\n", octet_string_hex_string(key, KEY_LEN));
+#endif
+
+ /* derive encryption and authentication keys from the input key */
+ status = hmac_init(&hmac_ctx, key, KEY_LEN);
+ if (status) return status;
+ status = hmac_compute(&hmac_ctx, "ENC", 3, ENC_KEY_LEN, enc_key);
+ if (status) return status;
+
+ status = hmac_init(&hmac_ctx, key, KEY_LEN);
+ if (status) return status;
+ status = hmac_compute(&hmac_ctx, "MAC", 3, MAC_KEY_LEN, mac_key);
+ if (status) return status;
+
+#if DEBUG
+ printf("prot data len: %d\n", *opaque_len);
+ printf("prot data: %s\n", octet_string_hex_string(opaque, *opaque_len));
+#endif
+
+ /*
+ * set the protected data length to that of the ciphertext, by
+ * subtracting out the length of the authentication tag
+ */
+ ciphertext_len = *opaque_len - TAG_LEN;
+
+#if DEBUG
+ printf("ciphertext len: %d\n", ciphertext_len);
+#endif
+ /* verify the authentication tag */
+
+ /*
+ * compute the authentication tag for the clear and opaque data,
+ * and write it to a temporary location
+ */
+ status = hmac_init(&hmac_ctx, mac_key, MAC_KEY_LEN);
+ if (status) return status;
+
+ status = hmac_start(&hmac_ctx);
+ if (status) return status;
+
+ status = hmac_update(&hmac_ctx, clear, clear_len);
+ if (status) return status;
+
+#if DEBUG
+ printf("hmac input: %s\n",
+ octet_string_hex_string(clear, clear_len));
+#endif
+
+ status = hmac_compute(&hmac_ctx, opaque, ciphertext_len, TAG_LEN, tmp_tag);
+ if (status) return status;
+
+#if DEBUG
+ printf("hmac input: %s\n",
+ octet_string_hex_string(opaque, ciphertext_len));
+#endif
+
+ /*
+ * compare the computed tag with the one provided as input (which
+ * immediately follows the ciphertext)
+ */
+ auth_tag = (unsigned char *)opaque;
+ auth_tag += ciphertext_len;
+#if DEBUG
+ printf("auth_tag: %s\n", octet_string_hex_string(auth_tag, TAG_LEN));
+ printf("tmp_tag: %s\n", octet_string_hex_string(tmp_tag, TAG_LEN));
+#endif
+ for (i=0; i < TAG_LEN; i++) {
+ if (tmp_tag[i] != auth_tag[i])
+ return err_status_auth_fail;
+ }
+
+ /* bump down the opaque_len to reflect the authentication tag */
+ *opaque_len -= TAG_LEN;
+
+ /* decrypt the confidential data */
+ status = aes_cbc_context_init(&aes_ctx, key, ENC_KEY_LEN, direction_decrypt);
+ if (status) return status;
+ status = aes_cbc_set_iv(&aes_ctx, iv);
+ if (status) return status;
+
+#if DEBUG
+ printf("ciphertext: %s\n", octet_string_hex_string(opaque, *opaque_len));
+ printf("iv: %s\n", octet_string_hex_string(iv, IV_LEN));
+#endif
+
+#if ENC
+ status = aes_cbc_nist_decrypt(&aes_ctx, opaque, &ciphertext_len);
+ if (status) return status;
+#endif
+
+#if DEBUG
+ printf("plaintext len: %d\n", ciphertext_len);
+ printf("plaintext: %s\n",
+ octet_string_hex_string(opaque, ciphertext_len));
+#endif
+
+ /* indicate the length of the plaintext */
+ *opaque_len = ciphertext_len;
+ }
+
+ return err_status_ok;
+}
+
+cryptoalg_ctx_t cryptoalg_ctx = {
+ aes_128_cbc_hmac_sha1_96_enc,
+ aes_128_cbc_hmac_sha1_96_dec,
+ KEY_LEN,
+ IV_LEN,
+ TAG_LEN,
+ MAX_EXPAND,
+};
+
+cryptoalg_t cryptoalg = &cryptoalg_ctx;
+
+#define NULL_TAG_LEN 12
+
+err_status_t
+null_enc(void *key,
+ const void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *opaque,
+ unsigned *opaque_len) {
+ int i;
+ unsigned char *auth_tag;
+ unsigned char *init_vec = iv;
+
+ /* check if we're doing authentication only */
+ if ((iv == NULL) && (opaque == NULL) && (opaque_len == NULL)) {
+
+ /* perform authentication only */
+
+ } else if ((iv == NULL) || (opaque == NULL) || (opaque_len == NULL)) {
+
+ /*
+ * bad parameter - we expect either all three pointers to be NULL,
+ * or none of those pointers to be NULL
+ */
+ return err_status_fail;
+
+ } else {
+
+#if DEBUG
+ printf("NULL ENC using key %s\n", octet_string_hex_string(key, KEY_LEN));
+ printf("NULL_TAG_LEN: %d\n", NULL_TAG_LEN);
+ printf("plaintext len: %d\n", *opaque_len);
+#endif
+ for (i=0; i < IV_LEN; i++)
+ init_vec[i] = i + (i * 16);
+#if DEBUG
+ printf("iv: %s\n",
+ octet_string_hex_string(iv, IV_LEN));
+ printf("plaintext: %s\n",
+ octet_string_hex_string(opaque, *opaque_len));
+#endif
+ auth_tag = opaque;
+ auth_tag += *opaque_len;
+ for (i=0; i < NULL_TAG_LEN; i++)
+ auth_tag[i] = i + (i * 16);
+ *opaque_len += NULL_TAG_LEN;
+#if DEBUG
+ printf("protected data len: %d\n", *opaque_len);
+ printf("protected data: %s\n",
+ octet_string_hex_string(opaque, *opaque_len));
+#endif
+
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+null_dec(void *key,
+ const void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *opaque,
+ unsigned *opaque_len) {
+ unsigned char *auth_tag;
+
+ /* check if we're doing authentication only */
+ if ((iv == NULL) && (opaque == NULL) && (opaque_len == NULL)) {
+
+ /* perform authentication only */
+
+ } else if ((iv == NULL) || (opaque == NULL) || (opaque_len == NULL)) {
+
+ /*
+ * bad parameter - we expect either all three pointers to be NULL,
+ * or none of those pointers to be NULL
+ */
+ return err_status_fail;
+
+ } else {
+
+#if DEBUG
+ printf("NULL DEC using key %s\n", octet_string_hex_string(key, KEY_LEN));
+
+ printf("protected data len: %d\n", *opaque_len);
+ printf("protected data: %s\n",
+ octet_string_hex_string(opaque, *opaque_len));
+#endif
+ auth_tag = opaque;
+ auth_tag += (*opaque_len - NULL_TAG_LEN);
+#if DEBUG
+ printf("iv: %s\n", octet_string_hex_string(iv, IV_LEN));
+#endif
+ *opaque_len -= NULL_TAG_LEN;
+#if DEBUG
+ printf("plaintext len: %d\n", *opaque_len);
+ printf("plaintext: %s\n",
+ octet_string_hex_string(opaque, *opaque_len));
+#endif
+ }
+
+ return err_status_ok;
+}
+
+cryptoalg_ctx_t null_cryptoalg_ctx = {
+ null_enc,
+ null_dec,
+ KEY_LEN,
+ IV_LEN,
+ NULL_TAG_LEN,
+ MAX_EXPAND,
+};
+
+cryptoalg_t null_cryptoalg = &null_cryptoalg_ctx;
+
+int
+cryptoalg_get_id(cryptoalg_t c) {
+ if (c == cryptoalg)
+ return 1;
+ return 0;
+}
+
+cryptoalg_t
+cryptoalg_find_by_id(int id) {
+ switch(id) {
+ case 1:
+ return cryptoalg;
+ default:
+ break;
+ }
+ return 0;
+}
diff --git a/netwerk/srtp/src/crypto/cipher/aes.c b/netwerk/srtp/src/crypto/cipher/aes.c
new file mode 100644
index 0000000000..438276c24b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes.c
@@ -0,0 +1,2079 @@
+/*
+ * aes.c
+ *
+ * An implemnetation of the AES block cipher.
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "aes.h"
+#include "err.h"
+#include "datatypes.h"
+
+typedef uint8_t gf2_8;
+
+#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))
+
+/*
+ * 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
+
+static 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,
+};
+
+static 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,
+};
+
+static 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,
+};
+
+static 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,
+};
+
+static 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,
+};
+
+static 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,
+};
+
+static 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,
+};
+
+static 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,
+};
+
+#else /* assume big endian */
+
+static 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,
+};
+
+static 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,
+};
+
+static 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,
+};
+
+static 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,
+};
+
+static 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
+};
+
+static 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
+};
+
+static 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
+};
+
+static 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
+};
+
+#endif
+
+/*
+ * the following tables (aes_sbox, aes_inv_sbox, T4, U4) are
+ * endian-neutral
+ */
+
+static 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
+};
+
+#ifndef CPU_RISC
+static 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
+};
+#endif /* ! CPU_RISC */
+
+#ifdef CPU_RISC
+static 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
+};
+
+static 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
+};
+#endif /* CPU_RISC */
+
+
+/* aes internals */
+
+extern debug_module_t mod_aes_icm;
+
+static void
+aes_128_expand_encryption_key(const uint8_t *key,
+ aes_expanded_key_t *expanded_key) {
+ int i;
+ gf2_8 rc;
+
+ /* initialize round constant */
+ rc = 1;
+
+ expanded_key->num_rounds = 10;
+
+ v128_copy_octet_string(&expanded_key->round[0], key);
+
+#if 0
+ debug_print(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(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,
+ aes_expanded_key_t *expanded_key) {
+ int i;
+ gf2_8 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(mod_aes_icm,
+ "expanded key[0]: %s", v128_hex_string(&expanded_key->round[0]));
+ debug_print(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(mod_aes_icm,
+ "expanded key[%d]: %s", i,v128_hex_string(&expanded_key->round[i]));
+#endif
+
+ }
+}
+
+err_status_t
+aes_expand_encryption_key(const uint8_t *key,
+ int key_len,
+ aes_expanded_key_t *expanded_key) {
+ if (key_len == 16) {
+ aes_128_expand_encryption_key(key, expanded_key);
+ return err_status_ok;
+ }
+ else if (key_len == 24) {
+ /* AES-192 not yet supported */
+ return err_status_bad_param;
+ }
+ else if (key_len == 32) {
+ aes_256_expand_encryption_key(key, expanded_key);
+ return err_status_ok;
+ }
+ else
+ return err_status_bad_param;
+}
+
+err_status_t
+aes_expand_decryption_key(const uint8_t *key,
+ int key_len,
+ aes_expanded_key_t *expanded_key) {
+ int i;
+ err_status_t status;
+ int num_rounds = expanded_key->num_rounds;
+
+ status = 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;
+
+ 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 /* 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 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
+ /* FIX! WRong indexes */
+ 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[1] >> 8) & 0xff]
+ ^ U2[(state->v32[2] >> 16) & 0xff] ^ U3[state->v32[3] >> 24];
+
+ column1 = U0[state->v32[1] & 0xff] ^ U1[(state->v32[2] >> 8) & 0xff]
+ ^ U2[(state->v32[3] >> 16) & 0xff] ^ U3[state->v32[0] >> 24];
+
+ column2 = U0[state->v32[2] & 0xff] ^ U1[(state->v32[3] >> 8) & 0xff]
+ ^ U2[(state->v32[0] >> 16) & 0xff] ^ U3[state->v32[1] >> 24];
+
+ column3 = U0[state->v32[3] & 0xff] ^ U1[(state->v32[0] >> 8) & 0xff]
+ ^ U2[(state->v32[1] >> 16) & 0xff] ^ U3[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_final_round(v128_t *state, const v128_t *round_key) {
+ uint32_t tmp0, tmp1, tmp2, tmp3;
+
+ 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];
+
+ 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;
+
+ 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];
+
+ 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;
+ uint16_t c
+ /* 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
+aes_encrypt(v128_t *plaintext, const 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
+aes_decrypt(v128_t *plaintext, const 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_cbc.c b/netwerk/srtp/src/crypto/cipher/aes_cbc.c
new file mode 100644
index 0000000000..ed33f5b01c
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes_cbc.c
@@ -0,0 +1,540 @@
+/*
+ * aes_cbc.c
+ *
+ * AES Cipher Block Chaining Mode
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "aes_cbc.h"
+#include "alloc.h"
+
+debug_module_t mod_aes_cbc = {
+ 0, /* debugging is off by default */
+ "aes cbc" /* printable module name */
+};
+
+
+
+err_status_t
+aes_cbc_alloc(cipher_t **c, int key_len) {
+ extern cipher_type_t aes_cbc;
+ uint8_t *pointer;
+ int tmp;
+
+ debug_print(mod_aes_cbc,
+ "allocating cipher with key length %d", key_len);
+
+ if (key_len != 16 && key_len != 24 && key_len != 32)
+ return err_status_bad_param;
+
+ /* allocate memory a cipher of type aes_cbc */
+ tmp = (sizeof(aes_cbc_ctx_t) + sizeof(cipher_t));
+ pointer = (uint8_t*)crypto_alloc(tmp);
+ if (pointer == NULL)
+ return err_status_alloc_fail;
+
+ /* set pointers */
+ *c = (cipher_t *)pointer;
+ (*c)->type = &aes_cbc;
+ (*c)->state = pointer + sizeof(cipher_t);
+
+ /* increment ref_count */
+ aes_cbc.ref_count++;
+
+ /* set key size */
+ (*c)->key_len = key_len;
+
+ return err_status_ok;
+}
+
+err_status_t
+aes_cbc_dealloc(cipher_t *c) {
+ extern cipher_type_t aes_cbc;
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero((uint8_t *)c,
+ sizeof(aes_cbc_ctx_t) + sizeof(cipher_t));
+
+ /* free memory */
+ crypto_free(c);
+
+ /* decrement ref_count */
+ aes_cbc.ref_count--;
+
+ return err_status_ok;
+}
+
+err_status_t
+aes_cbc_context_init(aes_cbc_ctx_t *c, const uint8_t *key, int key_len,
+ cipher_direction_t dir) {
+ err_status_t status;
+
+ debug_print(mod_aes_cbc,
+ "key: %s", octet_string_hex_string(key, key_len));
+
+ /* expand key for the appropriate direction */
+ switch (dir) {
+ case (direction_encrypt):
+ status = aes_expand_encryption_key(key, key_len, &c->expanded_key);
+ if (status)
+ return status;
+ break;
+ case (direction_decrypt):
+ status = aes_expand_decryption_key(key, key_len, &c->expanded_key);
+ if (status)
+ return status;
+ break;
+ default:
+ return err_status_bad_param;
+ }
+
+
+ return err_status_ok;
+}
+
+
+err_status_t
+aes_cbc_set_iv(aes_cbc_ctx_t *c, void *iv) {
+ int i;
+/* v128_t *input = iv; */
+ uint8_t *input = (uint8_t*) iv;
+
+ /* set state and 'previous' block to iv */
+ for (i=0; i < 16; i++)
+ c->previous.v8[i] = c->state.v8[i] = input[i];
+
+ debug_print(mod_aes_cbc, "setting iv: %s", v128_hex_string(&c->state));
+
+ return err_status_ok;
+}
+
+err_status_t
+aes_cbc_encrypt(aes_cbc_ctx_t *c,
+ unsigned char *data,
+ unsigned int *bytes_in_data) {
+ int i;
+ unsigned char *input = data; /* pointer to data being read */
+ unsigned char *output = data; /* pointer to data being written */
+ int bytes_to_encr = *bytes_in_data;
+
+ /*
+ * verify that we're 16-octet aligned
+ */
+ if (*bytes_in_data & 0xf)
+ return err_status_bad_param;
+
+ /*
+ * note that we assume that the initialization vector has already
+ * been set, e.g. by calling aes_cbc_set_iv()
+ */
+ debug_print(mod_aes_cbc, "iv: %s",
+ v128_hex_string(&c->state));
+
+ /*
+ * loop over plaintext blocks, exoring state into plaintext then
+ * encrypting and writing to output
+ */
+ while (bytes_to_encr > 0) {
+
+ /* exor plaintext into state */
+ for (i=0; i < 16; i++)
+ c->state.v8[i] ^= *input++;
+
+ debug_print(mod_aes_cbc, "inblock: %s",
+ v128_hex_string(&c->state));
+
+ aes_encrypt(&c->state, &c->expanded_key);
+
+ debug_print(mod_aes_cbc, "outblock: %s",
+ v128_hex_string(&c->state));
+
+ /* copy ciphertext to output */
+ for (i=0; i < 16; i++)
+ *output++ = c->state.v8[i];
+
+ bytes_to_encr -= 16;
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+aes_cbc_decrypt(aes_cbc_ctx_t *c,
+ unsigned char *data,
+ unsigned int *bytes_in_data) {
+ int i;
+ v128_t state, previous;
+ unsigned char *input = data; /* pointer to data being read */
+ unsigned char *output = data; /* pointer to data being written */
+ int bytes_to_encr = *bytes_in_data;
+ uint8_t tmp;
+
+ /*
+ * verify that we're 16-octet aligned
+ */
+ if (*bytes_in_data & 0x0f)
+ return err_status_bad_param;
+
+ /* set 'previous' block to iv*/
+ for (i=0; i < 16; i++) {
+ previous.v8[i] = c->previous.v8[i];
+ }
+
+ debug_print(mod_aes_cbc, "iv: %s",
+ v128_hex_string(&previous));
+
+ /*
+ * loop over ciphertext blocks, decrypting then exoring with state
+ * then writing plaintext to output
+ */
+ while (bytes_to_encr > 0) {
+
+ /* set state to ciphertext input block */
+ for (i=0; i < 16; i++) {
+ state.v8[i] = *input++;
+ }
+
+ debug_print(mod_aes_cbc, "inblock: %s",
+ v128_hex_string(&state));
+
+ /* decrypt state */
+ aes_decrypt(&state, &c->expanded_key);
+
+ debug_print(mod_aes_cbc, "outblock: %s",
+ v128_hex_string(&state));
+
+ /*
+ * exor previous ciphertext block out of plaintext, and write new
+ * plaintext block to output, while copying old ciphertext block
+ * to the 'previous' block
+ */
+ for (i=0; i < 16; i++) {
+ tmp = *output;
+ *output++ = state.v8[i] ^ previous.v8[i];
+ previous.v8[i] = tmp;
+ }
+
+ bytes_to_encr -= 16;
+ }
+
+ return err_status_ok;
+}
+
+
+err_status_t
+aes_cbc_nist_encrypt(aes_cbc_ctx_t *c,
+ unsigned char *data,
+ unsigned int *bytes_in_data) {
+ int i;
+ unsigned char *pad_start;
+ int num_pad_bytes;
+ err_status_t status;
+
+ /*
+ * determine the number of padding bytes that we need to add -
+ * this value is always between 1 and 16, inclusive.
+ */
+ num_pad_bytes = 16 - (*bytes_in_data & 0xf);
+ pad_start = data;
+ pad_start += *bytes_in_data;
+ *pad_start++ = 0xa0;
+ for (i=0; i < num_pad_bytes; i++)
+ *pad_start++ = 0x00;
+
+ /*
+ * increment the data size
+ */
+ *bytes_in_data += num_pad_bytes;
+
+ /*
+ * now cbc encrypt the padded data
+ */
+ status = aes_cbc_encrypt(c, data, bytes_in_data);
+ if (status)
+ return status;
+
+ return err_status_ok;
+}
+
+
+err_status_t
+aes_cbc_nist_decrypt(aes_cbc_ctx_t *c,
+ unsigned char *data,
+ unsigned int *bytes_in_data) {
+ unsigned char *pad_end;
+ int num_pad_bytes;
+ err_status_t status;
+
+ /*
+ * cbc decrypt the padded data
+ */
+ status = aes_cbc_decrypt(c, data, bytes_in_data);
+ if (status)
+ return status;
+
+ /*
+ * determine the number of padding bytes in the decrypted plaintext
+ * - this value is always between 1 and 16, inclusive.
+ */
+ num_pad_bytes = 1;
+ pad_end = data + (*bytes_in_data - 1);
+ while (*pad_end != 0xa0) { /* note: should check padding correctness */
+ pad_end--;
+ num_pad_bytes++;
+ }
+
+ /* decrement data size */
+ *bytes_in_data -= num_pad_bytes;
+
+ return err_status_ok;
+}
+
+
+char
+aes_cbc_description[] = "aes cipher block chaining (cbc) mode";
+
+/*
+ * Test case 0 is derived from FIPS 197 Appendix C; it uses an
+ * all-zero IV, so that the first block encryption matches the test
+ * case in that appendix. This property provides a check of the base
+ * AES encryption and decryption algorithms; if CBC fails on some
+ * particular platform, then you should print out AES intermediate
+ * data and compare with the detailed info provided in that appendix.
+ *
+ */
+
+
+uint8_t aes_cbc_test_case_0_key[16] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+};
+
+uint8_t aes_cbc_test_case_0_plaintext[64] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
+};
+
+uint8_t aes_cbc_test_case_0_ciphertext[80] = {
+ 0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30,
+ 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a,
+ 0x03, 0x35, 0xed, 0x27, 0x67, 0xf2, 0x6d, 0xf1,
+ 0x64, 0x83, 0x2e, 0x23, 0x44, 0x38, 0x70, 0x8b
+
+};
+
+uint8_t aes_cbc_test_case_0_iv[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+
+cipher_test_case_t aes_cbc_test_case_0 = {
+ 16, /* octets in key */
+ aes_cbc_test_case_0_key, /* key */
+ aes_cbc_test_case_0_iv, /* initialization vector */
+ 16, /* octets in plaintext */
+ aes_cbc_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ aes_cbc_test_case_0_ciphertext, /* ciphertext */
+ NULL /* pointer to next testcase */
+};
+
+
+/*
+ * this test case is taken directly from Appendix F.2 of NIST Special
+ * Publication SP 800-38A
+ */
+
+uint8_t aes_cbc_test_case_1_key[16] = {
+ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+ 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
+};
+
+uint8_t aes_cbc_test_case_1_plaintext[64] = {
+ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+ 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+ 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
+ 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
+ 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
+ 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
+ 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
+ 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
+};
+
+uint8_t aes_cbc_test_case_1_ciphertext[80] = {
+ 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46,
+ 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d,
+ 0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee,
+ 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2,
+ 0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b,
+ 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16,
+ 0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09,
+ 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7,
+ 0x39, 0x34, 0x07, 0x03, 0x36, 0xd0, 0x77, 0x99,
+ 0xe0, 0xc4, 0x2f, 0xdd, 0xa8, 0xdf, 0x4c, 0xa3
+};
+
+uint8_t aes_cbc_test_case_1_iv[16] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+};
+
+cipher_test_case_t aes_cbc_test_case_1 = {
+ 16, /* octets in key */
+ aes_cbc_test_case_1_key, /* key */
+ aes_cbc_test_case_1_iv, /* initialization vector */
+ 64, /* octets in plaintext */
+ aes_cbc_test_case_1_plaintext, /* plaintext */
+ 80, /* octets in ciphertext */
+ aes_cbc_test_case_1_ciphertext, /* ciphertext */
+ &aes_cbc_test_case_0 /* pointer to next testcase */
+};
+
+/*
+ * Test case 2 is like test case 0, but for 256-bit keys. (FIPS 197
+ * appendix C.3).
+ */
+
+
+uint8_t aes_cbc_test_case_2_key[32] = {
+ 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
+};
+
+uint8_t aes_cbc_test_case_2_plaintext[64] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
+};
+
+uint8_t aes_cbc_test_case_2_ciphertext[80] = {
+ 0x8e, 0xa2, 0xb7, 0xca, 0x51, 0x67, 0x45, 0xbf,
+ 0xea, 0xfc, 0x49, 0x90, 0x4b, 0x49, 0x60, 0x89,
+ 0x72, 0x72, 0x6e, 0xe7, 0x71, 0x39, 0xbf, 0x11,
+ 0xe5, 0x40, 0xe2, 0x7c, 0x54, 0x65, 0x1d, 0xee
+};
+
+uint8_t aes_cbc_test_case_2_iv[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+cipher_test_case_t aes_cbc_test_case_2 = {
+ 32, /* octets in key */
+ aes_cbc_test_case_2_key, /* key */
+ aes_cbc_test_case_2_iv, /* initialization vector */
+ 16, /* octets in plaintext */
+ aes_cbc_test_case_2_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ aes_cbc_test_case_2_ciphertext, /* ciphertext */
+ &aes_cbc_test_case_1 /* pointer to next testcase */
+};
+
+
+/*
+ * this test case is taken directly from Appendix F.2 of NIST Special
+ * Publication SP 800-38A
+ */
+
+uint8_t aes_cbc_test_case_3_key[32] = {
+ 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe,
+ 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
+ 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7,
+ 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4
+};
+
+uint8_t aes_cbc_test_case_3_plaintext[64] = {
+ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+ 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+ 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
+ 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
+ 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
+ 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
+ 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
+ 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
+};
+
+uint8_t aes_cbc_test_case_3_ciphertext[80] = {
+ 0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba,
+ 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6,
+ 0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d,
+ 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d,
+ 0x39, 0xf2, 0x33, 0x69, 0xa9, 0xd9, 0xba, 0xcf,
+ 0xa5, 0x30, 0xe2, 0x63, 0x04, 0x23, 0x14, 0x61,
+ 0xb2, 0xeb, 0x05, 0xe2, 0xc3, 0x9b, 0xe9, 0xfc,
+ 0xda, 0x6c, 0x19, 0x07, 0x8c, 0x6a, 0x9d, 0x1b,
+ 0xfb, 0x98, 0x20, 0x2c, 0x45, 0xb2, 0xe4, 0xa0,
+ 0x63, 0xc4, 0x68, 0xba, 0x84, 0x39, 0x16, 0x5a
+};
+
+uint8_t aes_cbc_test_case_3_iv[16] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+};
+
+cipher_test_case_t aes_cbc_test_case_3 = {
+ 32, /* octets in key */
+ aes_cbc_test_case_3_key, /* key */
+ aes_cbc_test_case_3_iv, /* initialization vector */
+ 64, /* octets in plaintext */
+ aes_cbc_test_case_3_plaintext, /* plaintext */
+ 80, /* octets in ciphertext */
+ aes_cbc_test_case_3_ciphertext, /* ciphertext */
+ &aes_cbc_test_case_2 /* pointer to next testcase */
+};
+
+cipher_type_t aes_cbc = {
+ (cipher_alloc_func_t) aes_cbc_alloc,
+ (cipher_dealloc_func_t) aes_cbc_dealloc,
+ (cipher_init_func_t) aes_cbc_context_init,
+ (cipher_encrypt_func_t) aes_cbc_nist_encrypt,
+ (cipher_decrypt_func_t) aes_cbc_nist_decrypt,
+ (cipher_set_iv_func_t) aes_cbc_set_iv,
+ (char *) aes_cbc_description,
+ (int) 0, /* instance count */
+ (cipher_test_case_t *) &aes_cbc_test_case_3,
+ (debug_module_t *) &mod_aes_cbc,
+ (cipher_type_id_t) AES_CBC
+};
+
+
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..8ec5845876
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes_icm.c
@@ -0,0 +1,567 @@
+/*
+ * aes_icm.c
+ *
+ * AES Integer Counter Mode
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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.
+ *
+ */
+
+
+#define ALIGN_32 0
+
+#include "aes_icm.h"
+#include "alloc.h"
+
+
+debug_module_t 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)
+ *
+ */
+
+err_status_t
+aes_icm_alloc_ismacryp(cipher_t **c, int key_len, int forIsmacryp) {
+ extern cipher_type_t aes_icm;
+ uint8_t *pointer;
+ int tmp;
+
+ debug_print(mod_aes_icm,
+ "allocating cipher with key length %d", key_len);
+
+ /*
+ * Ismacryp, for example, uses 16 byte key + 8 byte
+ * salt so this function is called with key_len = 24.
+ * The check for key_len = 30/38/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 (!(forIsmacryp && key_len > 16 && key_len < 30) &&
+ key_len != 30 && key_len != 38 && key_len != 46)
+ return err_status_bad_param;
+
+ /* allocate memory a cipher of type aes_icm */
+ tmp = (sizeof(aes_icm_ctx_t) + sizeof(cipher_t));
+ pointer = (uint8_t*)crypto_alloc(tmp);
+ if (pointer == NULL)
+ return err_status_alloc_fail;
+
+ /* set pointers */
+ *c = (cipher_t *)pointer;
+ (*c)->type = &aes_icm;
+ (*c)->state = pointer + sizeof(cipher_t);
+
+ /* increment ref_count */
+ aes_icm.ref_count++;
+
+ /* set key size */
+ (*c)->key_len = key_len;
+
+ return err_status_ok;
+}
+
+err_status_t aes_icm_alloc(cipher_t **c, int key_len, int forIsmacryp) {
+ return aes_icm_alloc_ismacryp(c, key_len, 0);
+}
+
+err_status_t
+aes_icm_dealloc(cipher_t *c) {
+ extern cipher_type_t aes_icm;
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero((uint8_t *)c,
+ sizeof(aes_icm_ctx_t) + sizeof(cipher_t));
+
+ /* free memory */
+ crypto_free(c);
+
+ /* decrement ref_count */
+ aes_icm.ref_count--;
+
+ return 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
+ */
+
+err_status_t
+aes_icm_context_init(aes_icm_ctx_t *c, const uint8_t *key, int key_len) {
+ err_status_t status;
+ int base_key_len, copy_len;
+
+ if (key_len > 16 && key_len < 30) /* Ismacryp */
+ base_key_len = 16;
+ else if (key_len == 30 || key_len == 38 || key_len == 46)
+ base_key_len = key_len - 14;
+ else
+ return 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 = key_len - base_key_len;
+ /* force last two octets of the offset to be left zero (for srtp compatibility) */
+ if (copy_len > 14)
+ copy_len = 14;
+
+ memcpy(&c->counter, key + base_key_len, copy_len);
+ memcpy(&c->offset, key + base_key_len, copy_len);
+
+ debug_print(mod_aes_icm,
+ "key: %s", octet_string_hex_string(key, base_key_len));
+ debug_print(mod_aes_icm,
+ "offset: %s", v128_hex_string(&c->offset));
+
+ /* expand key */
+ status = 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 err_status_ok;
+}
+
+/*
+ * aes_icm_set_octet(c, i) sets the counter of the context which it is
+ * passed so that the next octet of keystream that will be generated
+ * is the ith octet
+ */
+
+err_status_t
+aes_icm_set_octet(aes_icm_ctx_t *c,
+ uint64_t octet_num) {
+
+#ifdef NO_64BIT_MATH
+ int tail_num = low32(octet_num) & 0x0f;
+ /* 64-bit right-shift 4 */
+ uint64_t block_num = make64(high32(octet_num) >> 4,
+ ((high32(octet_num) & 0x0f)<<(32-4)) |
+ (low32(octet_num) >> 4));
+#else
+ int tail_num = (int)(octet_num % 16);
+ uint64_t block_num = octet_num / 16;
+#endif
+
+
+ /* set counter value */
+ /* FIX - There's no way this is correct */
+ c->counter.v64[0] = c->offset.v64[0];
+#ifdef NO_64BIT_MATH
+ c->counter.v64[0] = make64(high32(c->offset.v64[0]) ^ high32(block_num),
+ low32(c->offset.v64[0]) ^ low32(block_num));
+#else
+ c->counter.v64[0] = c->offset.v64[0] ^ block_num;
+#endif
+
+ debug_print(mod_aes_icm,
+ "set_octet: %s", v128_hex_string(&c->counter));
+
+ /* fill keystream buffer, if needed */
+ if (tail_num) {
+ v128_copy(&c->keystream_buffer, &c->counter);
+ aes_encrypt(&c->keystream_buffer, &c->expanded_key);
+ c->bytes_in_buffer = sizeof(v128_t);
+
+ debug_print(mod_aes_icm, "counter: %s",
+ v128_hex_string(&c->counter));
+ debug_print(mod_aes_icm, "ciphertext: %s",
+ v128_hex_string(&c->keystream_buffer));
+
+ /* indicate number of bytes in keystream_buffer */
+ c->bytes_in_buffer = sizeof(v128_t) - tail_num;
+
+ } else {
+
+ /* indicate that keystream_buffer is empty */
+ c->bytes_in_buffer = 0;
+ }
+
+ return err_status_ok;
+}
+
+/*
+ * aes_icm_set_iv(c, iv) sets the counter value to the exor of iv with
+ * the offset
+ */
+
+err_status_t
+aes_icm_set_iv(aes_icm_ctx_t *c, void *iv) {
+ v128_t *nonce = (v128_t *) iv;
+
+ debug_print(mod_aes_icm,
+ "setting iv: %s", v128_hex_string(nonce));
+
+ v128_xor(&c->counter, &c->offset, nonce);
+
+ debug_print(mod_aes_icm,
+ "set_counter: %s", v128_hex_string(&c->counter));
+
+ /* indicate that the keystream_buffer is empty */
+ c->bytes_in_buffer = 0;
+
+ return 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 inline void
+aes_icm_advance_ismacryp(aes_icm_ctx_t *c, uint8_t forIsmacryp) {
+ /* fill buffer with new keystream */
+ v128_copy(&c->keystream_buffer, &c->counter);
+ aes_encrypt(&c->keystream_buffer, &c->expanded_key);
+ c->bytes_in_buffer = sizeof(v128_t);
+
+ debug_print(mod_aes_icm, "counter: %s",
+ v128_hex_string(&c->counter));
+ debug_print(mod_aes_icm, "ciphertext: %s",
+ v128_hex_string(&c->keystream_buffer));
+
+ /* clock counter forward */
+
+ if (forIsmacryp) {
+ uint32_t temp;
+ //alex's clock counter forward
+ temp = ntohl(c->counter.v32[3]);
+ c->counter.v32[3] = htonl(++temp);
+ } else {
+ if (!++(c->counter.v8[15]))
+ ++(c->counter.v8[14]);
+ }
+}
+
+static inline void aes_icm_advance(aes_icm_ctx_t *c) {
+ aes_icm_advance_ismacryp(c, 0);
+}
+
+
+/*e
+ * 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
+ */
+
+err_status_t
+aes_icm_encrypt_ismacryp(aes_icm_ctx_t *c,
+ unsigned char *buf, unsigned int *enc_len,
+ int forIsmacryp) {
+ unsigned int bytes_to_encr = *enc_len;
+ unsigned int i;
+ uint32_t *b;
+
+ /* check that there's enough segment left but not for ismacryp*/
+ if (!forIsmacryp && (bytes_to_encr + htons(c->counter.v16[7])) > 0xffff)
+ return err_status_terminus;
+
+ debug_print(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 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 */
+ aes_icm_advance_ismacryp(c, forIsmacryp);
+
+ /*
+ * 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 */
+ aes_icm_advance_ismacryp(c, forIsmacryp);
+
+ 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 err_status_ok;
+}
+
+err_status_t
+aes_icm_encrypt(aes_icm_ctx_t *c, unsigned char *buf, unsigned int *enc_len) {
+ return aes_icm_encrypt_ismacryp(c, buf, enc_len, 0);
+}
+
+err_status_t
+aes_icm_output(aes_icm_ctx_t *c, uint8_t *buffer, int num_octets_to_output) {
+ unsigned int len = num_octets_to_output;
+
+ /* zeroize the buffer */
+ octet_string_set_to_zero(buffer, num_octets_to_output);
+
+ /* exor keystream into buffer */
+ return aes_icm_encrypt(c, buffer, &len);
+}
+
+
+char
+aes_icm_description[] = "aes integer counter mode";
+
+uint8_t aes_icm_test_case_0_key[30] = {
+ 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
+};
+
+uint8_t aes_icm_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+uint8_t aes_icm_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,
+};
+
+uint8_t aes_icm_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
+};
+
+cipher_test_case_t aes_icm_test_case_0 = {
+ 30, /* octets in key */
+ aes_icm_test_case_0_key, /* key */
+ aes_icm_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ aes_icm_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ aes_icm_test_case_0_ciphertext, /* ciphertext */
+ NULL /* pointer to next testcase */
+};
+
+uint8_t aes_icm_test_case_1_key[46] = {
+ 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
+};
+
+uint8_t aes_icm_test_case_1_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+uint8_t aes_icm_test_case_1_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,
+};
+
+uint8_t aes_icm_test_case_1_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
+};
+
+cipher_test_case_t aes_icm_test_case_1 = {
+ 46, /* octets in key */
+ aes_icm_test_case_1_key, /* key */
+ aes_icm_test_case_1_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ aes_icm_test_case_1_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ aes_icm_test_case_1_ciphertext, /* ciphertext */
+ &aes_icm_test_case_0 /* pointer to next testcase */
+};
+
+
+
+/*
+ * note: the encrypt function is identical to the decrypt function
+ */
+
+cipher_type_t aes_icm = {
+ (cipher_alloc_func_t) aes_icm_alloc,
+ (cipher_dealloc_func_t) aes_icm_dealloc,
+ (cipher_init_func_t) aes_icm_context_init,
+ (cipher_encrypt_func_t) aes_icm_encrypt,
+ (cipher_decrypt_func_t) aes_icm_encrypt,
+ (cipher_set_iv_func_t) aes_icm_set_iv,
+ (char *) aes_icm_description,
+ (int) 0, /* instance count */
+ (cipher_test_case_t *) &aes_icm_test_case_1,
+ (debug_module_t *) &mod_aes_icm,
+ (cipher_type_id_t) AES_ICM
+};
+
diff --git a/netwerk/srtp/src/crypto/cipher/cipher.c b/netwerk/srtp/src/crypto/cipher/cipher.c
new file mode 100644
index 0000000000..f668fc1664
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/cipher.c
@@ -0,0 +1,421 @@
+/*
+ * cipher.c
+ *
+ * cipher meta-functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "cipher.h"
+#include "rand_source.h" /* used in invertibiltiy tests */
+#include "alloc.h" /* for crypto_alloc(), crypto_free() */
+
+debug_module_t mod_cipher = {
+ 0, /* debugging is off by default */
+ "cipher" /* printable module name */
+};
+
+err_status_t
+cipher_output(cipher_t *c, uint8_t *buffer, int num_octets_to_output) {
+
+ /* zeroize the buffer */
+ octet_string_set_to_zero(buffer, num_octets_to_output);
+
+ /* exor keystream into buffer */
+ return cipher_encrypt(c, buffer, (unsigned int *) &num_octets_to_output);
+}
+
+/* some bookkeeping functions */
+
+int
+cipher_get_key_length(const cipher_t *c) {
+ return c->key_len;
+}
+
+/*
+ * 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
+ */
+
+#define SELF_TEST_BUF_OCTETS 128
+#define NUM_RAND_TESTS 128
+#define MAX_KEY_LEN 64
+
+err_status_t
+cipher_type_test(const cipher_type_t *ct, const cipher_test_case_t *test_data) {
+ const cipher_test_case_t *test_case = test_data;
+ cipher_t *c;
+ err_status_t status;
+ uint8_t buffer[SELF_TEST_BUF_OCTETS];
+ uint8_t buffer2[SELF_TEST_BUF_OCTETS];
+ unsigned int len;
+ int i, j, case_num = 0;
+
+ debug_print(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 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 = cipher_type_alloc(ct, &c, test_case->key_length_octets);
+ if (status)
+ return status;
+
+ /*
+ * test the encrypt function
+ */
+ debug_print(mod_cipher, "testing encryption", NULL);
+
+ /* initialize cipher */
+ status = cipher_init(c, test_case->key, direction_encrypt);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ /* copy plaintext into test buffer */
+ if (test_case->ciphertext_length_octets > SELF_TEST_BUF_OCTETS) {
+ cipher_dealloc(c);
+ return err_status_bad_param;
+ }
+ for (i=0; i < test_case->plaintext_length_octets; i++)
+ buffer[i] = test_case->plaintext[i];
+
+ debug_print(mod_cipher, "plaintext: %s",
+ octet_string_hex_string(buffer,
+ test_case->plaintext_length_octets));
+
+ /* set the initialization vector */
+ status = cipher_set_iv(c, test_case->idx);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ /* encrypt */
+ len = test_case->plaintext_length_octets;
+ status = cipher_encrypt(c, buffer, &len);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ debug_print(mod_cipher, "ciphertext: %s",
+ octet_string_hex_string(buffer,
+ test_case->ciphertext_length_octets));
+
+ /* compare the resulting ciphertext with that in the test case */
+ if (len != (unsigned int)test_case->ciphertext_length_octets)
+ return err_status_algo_fail;
+ status = err_status_ok;
+ for (i=0; i < test_case->ciphertext_length_octets; i++)
+ if (buffer[i] != test_case->ciphertext[i]) {
+ status = err_status_algo_fail;
+ debug_print(mod_cipher, "test case %d failed", case_num);
+ debug_print(mod_cipher, "(failure at byte %d)", i);
+ break;
+ }
+ if (status) {
+
+ debug_print(mod_cipher, "c computed: %s",
+ octet_string_hex_string(buffer,
+ 2*test_case->plaintext_length_octets));
+ debug_print(mod_cipher, "c expected: %s",
+ octet_string_hex_string(test_case->ciphertext,
+ 2*test_case->plaintext_length_octets));
+
+ cipher_dealloc(c);
+ return err_status_algo_fail;
+ }
+
+ /*
+ * test the decrypt function
+ */
+ debug_print(mod_cipher, "testing decryption", NULL);
+
+ /* re-initialize cipher for decryption */
+ status = cipher_init(c, test_case->key, direction_decrypt);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ /* copy ciphertext into test buffer */
+ if (test_case->ciphertext_length_octets > SELF_TEST_BUF_OCTETS) {
+ cipher_dealloc(c);
+ return err_status_bad_param;
+ }
+ for (i=0; i < test_case->ciphertext_length_octets; i++)
+ buffer[i] = test_case->ciphertext[i];
+
+ debug_print(mod_cipher, "ciphertext: %s",
+ octet_string_hex_string(buffer,
+ test_case->plaintext_length_octets));
+
+ /* set the initialization vector */
+ status = cipher_set_iv(c, test_case->idx);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ /* decrypt */
+ len = test_case->ciphertext_length_octets;
+ status = cipher_decrypt(c, buffer, &len);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ debug_print(mod_cipher, "plaintext: %s",
+ octet_string_hex_string(buffer,
+ test_case->plaintext_length_octets));
+
+ /* compare the resulting plaintext with that in the test case */
+ if (len != (unsigned int)test_case->plaintext_length_octets)
+ return err_status_algo_fail;
+ status = err_status_ok;
+ for (i=0; i < test_case->plaintext_length_octets; i++)
+ if (buffer[i] != test_case->plaintext[i]) {
+ status = err_status_algo_fail;
+ debug_print(mod_cipher, "test case %d failed", case_num);
+ debug_print(mod_cipher, "(failure at byte %d)", i);
+ }
+ if (status) {
+
+ debug_print(mod_cipher, "p computed: %s",
+ octet_string_hex_string(buffer,
+ 2*test_case->plaintext_length_octets));
+ debug_print(mod_cipher, "p expected: %s",
+ octet_string_hex_string(test_case->plaintext,
+ 2*test_case->plaintext_length_octets));
+
+ cipher_dealloc(c);
+ return err_status_algo_fail;
+ }
+
+ /* deallocate the cipher */
+ status = 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 = cipher_type_alloc(ct, &c, test_case->key_length_octets);
+ if (status)
+ return status;
+
+ rand_source_init();
+
+ for (j=0; j < NUM_RAND_TESTS; j++) {
+ unsigned length;
+ 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(mod_cipher, "random plaintext length %d\n", length);
+ status = rand_source_get_octet_string(buffer, length);
+ if (status) return status;
+
+ debug_print(mod_cipher, "plaintext: %s",
+ 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)
+ return err_status_cant_check;
+ status = rand_source_get_octet_string(key, test_case->key_length_octets);
+ if (status) return status;
+
+ /* chose a random initialization vector */
+ status = rand_source_get_octet_string(iv, MAX_KEY_LEN);
+ if (status) return status;
+
+ /* initialize cipher */
+ status = cipher_init(c, key, direction_encrypt);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ /* set initialization vector */
+ status = cipher_set_iv(c, test_case->idx);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ /* encrypt buffer with cipher */
+ plaintext_len = length;
+ status = cipher_encrypt(c, buffer, &length);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+ debug_print(mod_cipher, "ciphertext: %s",
+ octet_string_hex_string(buffer, length));
+
+ /*
+ * re-initialize cipher for decryption, re-set the iv, then
+ * decrypt the ciphertext
+ */
+ status = cipher_init(c, key, direction_decrypt);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+ status = cipher_set_iv(c, test_case->idx);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+ status = cipher_decrypt(c, buffer, &length);
+ if (status) {
+ cipher_dealloc(c);
+ return status;
+ }
+
+ debug_print(mod_cipher, "plaintext[2]: %s",
+ octet_string_hex_string(buffer, length));
+
+ /* compare the resulting plaintext with the original one */
+ if (length != (unsigned)plaintext_len)
+ return err_status_algo_fail;
+ status = err_status_ok;
+ for (i=0; i < plaintext_len; i++)
+ if (buffer[i] != buffer2[i]) {
+ status = err_status_algo_fail;
+ debug_print(mod_cipher, "random test case %d failed", case_num);
+ debug_print(mod_cipher, "(failure at byte %d)", i);
+ }
+ if (status) {
+ cipher_dealloc(c);
+ return err_status_algo_fail;
+ }
+
+ }
+
+ status = cipher_dealloc(c);
+ if (status)
+ return status;
+
+ return err_status_ok;
+}
+
+
+/*
+ * cipher_type_self_test(ct) performs cipher_type_test on ct's internal
+ * list of test data.
+ */
+
+err_status_t
+cipher_type_self_test(const cipher_type_t *ct) {
+ return 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
+cipher_bits_per_second(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*) 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) {
+ cipher_set_iv(c, &nonce);
+ cipher_encrypt(c, enc_buf, &len);
+ }
+ timer = clock() - timer;
+
+ 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..724f4df3a8
--- /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-2006, 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 "datatypes.h"
+#include "null_cipher.h"
+#include "alloc.h"
+
+/* the null_cipher uses the cipher debug module */
+
+extern debug_module_t mod_cipher;
+
+err_status_t
+null_cipher_alloc(cipher_t **c, int key_len) {
+ extern cipher_type_t null_cipher;
+ uint8_t *pointer;
+
+ debug_print(mod_cipher,
+ "allocating cipher with key length %d", key_len);
+
+ /* allocate memory a cipher of type null_cipher */
+ pointer = (uint8_t*)crypto_alloc(sizeof(null_cipher_ctx_t) + sizeof(cipher_t));
+ if (pointer == NULL)
+ return err_status_alloc_fail;
+
+ /* set pointers */
+ *c = (cipher_t *)pointer;
+ (*c)->type = &null_cipher;
+ (*c)->state = pointer + sizeof(cipher_t);
+
+ /* set key size */
+ (*c)->key_len = key_len;
+
+ /* increment ref_count */
+ null_cipher.ref_count++;
+
+ return err_status_ok;
+
+}
+
+err_status_t
+null_cipher_dealloc(cipher_t *c) {
+ extern cipher_type_t null_cipher;
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero((uint8_t *)c,
+ sizeof(null_cipher_ctx_t) + sizeof(cipher_t));
+
+ /* free memory of type null_cipher */
+ crypto_free(c);
+
+ /* decrement reference count */
+ null_cipher.ref_count--;
+
+ return err_status_ok;
+
+}
+
+err_status_t
+null_cipher_init(null_cipher_ctx_t *ctx, const uint8_t *key, int key_len) {
+
+ debug_print(mod_cipher, "initializing null cipher", NULL);
+
+ return err_status_ok;
+}
+
+err_status_t
+null_cipher_set_iv(null_cipher_ctx_t *c, void *iv) {
+ return err_status_ok;
+}
+
+err_status_t
+null_cipher_encrypt(null_cipher_ctx_t *c,
+ unsigned char *buf, unsigned int *bytes_to_encr) {
+ return err_status_ok;
+}
+
+char
+null_cipher_description[] = "null cipher";
+
+cipher_test_case_t
+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 */
+ NULL /* pointer to next testcase */
+};
+
+
+/*
+ * note: the decrypt function is idential to the encrypt function
+ */
+
+cipher_type_t null_cipher = {
+ (cipher_alloc_func_t) null_cipher_alloc,
+ (cipher_dealloc_func_t) null_cipher_dealloc,
+ (cipher_init_func_t) null_cipher_init,
+ (cipher_encrypt_func_t) null_cipher_encrypt,
+ (cipher_decrypt_func_t) null_cipher_encrypt,
+ (cipher_set_iv_func_t) null_cipher_set_iv,
+ (char *) null_cipher_description,
+ (int) 0,
+ (cipher_test_case_t *) &null_cipher_test_0,
+ (debug_module_t *) NULL,
+ (cipher_type_id_t) 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..aaf0269c71
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/auth.c
@@ -0,0 +1,183 @@
+/*
+ * auth.c
+ *
+ * some bookkeeping functions for authentication functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "auth.h"
+
+/* the debug module for authentiation */
+
+debug_module_t mod_auth = {
+ 0, /* debugging is off by default */
+ "auth func" /* printable name for module */
+};
+
+
+int
+auth_get_key_length(const auth_t *a) {
+ return a->key_len;
+}
+
+int
+auth_get_tag_length(const auth_t *a) {
+ return a->out_len;
+}
+
+int
+auth_get_prefix_length(const auth_t *a) {
+ return a->prefix_len;
+}
+
+int
+auth_type_get_ref_count(const auth_type_t *at) {
+ return at->ref_count;
+}
+
+/*
+ * 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
+
+err_status_t
+auth_type_test(const auth_type_t *at, const auth_test_case_t *test_data) {
+ const auth_test_case_t *test_case = test_data;
+ auth_t *a;
+ err_status_t status;
+ uint8_t tag[SELF_TEST_TAG_BUF_OCTETS];
+ int i, case_num = 0;
+
+ debug_print(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 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 err_status_bad_param;
+
+ /* allocate auth */
+ status = auth_type_alloc(at, &a, test_case->key_length_octets,
+ test_case->tag_length_octets);
+ if (status)
+ return status;
+
+ /* initialize auth */
+ status = auth_init(a, test_case->key);
+ if (status) {
+ auth_dealloc(a);
+ return status;
+ }
+
+ /* zeroize tag then compute */
+ octet_string_set_to_zero(tag, test_case->tag_length_octets);
+ status = auth_compute(a, test_case->data,
+ test_case->data_length_octets, tag);
+ if (status) {
+ auth_dealloc(a);
+ return status;
+ }
+
+ debug_print(mod_auth, "key: %s",
+ octet_string_hex_string(test_case->key,
+ test_case->key_length_octets));
+ debug_print(mod_auth, "data: %s",
+ octet_string_hex_string(test_case->data,
+ test_case->data_length_octets));
+ debug_print(mod_auth, "tag computed: %s",
+ octet_string_hex_string(tag, test_case->tag_length_octets));
+ debug_print(mod_auth, "tag expected: %s",
+ octet_string_hex_string(test_case->tag,
+ test_case->tag_length_octets));
+
+ /* check the result */
+ status = err_status_ok;
+ for (i=0; i < test_case->tag_length_octets; i++)
+ if (tag[i] != test_case->tag[i]) {
+ status = err_status_algo_fail;
+ debug_print(mod_auth, "test case %d failed", case_num);
+ debug_print(mod_auth, " (mismatch at octet %d)", i);
+ }
+ if (status) {
+ auth_dealloc(a);
+ return err_status_algo_fail;
+ }
+
+ /* deallocate the auth function */
+ status = 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 err_status_ok;
+}
+
+
+/*
+ * auth_type_self_test(at) performs auth_type_test on at's internal
+ * list of test data.
+ */
+
+err_status_t
+auth_type_self_test(const auth_type_t *at) {
+ return 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..4f389fe18e
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/hmac.c
@@ -0,0 +1,268 @@
+/*
+ * hmac.c
+ *
+ * implementation of hmac auth_type_t
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 "hmac.h"
+#include "alloc.h"
+
+/* the debug module for authentiation */
+
+debug_module_t mod_hmac = {
+ 0, /* debugging is off by default */
+ "hmac sha-1" /* printable name for module */
+};
+
+
+err_status_t
+hmac_alloc(auth_t **a, int key_len, int out_len) {
+ extern auth_type_t hmac;
+ uint8_t *pointer;
+
+ debug_print(mod_hmac, "allocating auth func with key length %d", key_len);
+ debug_print(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 err_status_bad_param;
+
+ /* check output length - should be less than 20 bytes */
+ if (out_len > 20)
+ return err_status_bad_param;
+
+ /* allocate memory for auth and hmac_ctx_t structures */
+ pointer = (uint8_t*)crypto_alloc(sizeof(hmac_ctx_t) + sizeof(auth_t));
+ if (pointer == NULL)
+ return err_status_alloc_fail;
+
+ /* set pointers */
+ *a = (auth_t *)pointer;
+ (*a)->type = &hmac;
+ (*a)->state = pointer + sizeof(auth_t);
+ (*a)->out_len = out_len;
+ (*a)->key_len = key_len;
+ (*a)->prefix_len = 0;
+
+ /* increment global count of all hmac uses */
+ hmac.ref_count++;
+
+ return err_status_ok;
+}
+
+err_status_t
+hmac_dealloc(auth_t *a) {
+ extern auth_type_t hmac;
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero((uint8_t *)a,
+ sizeof(hmac_ctx_t) + sizeof(auth_t));
+
+ /* free memory */
+ crypto_free(a);
+
+ /* decrement global count of all hmac uses */
+ hmac.ref_count--;
+
+ return err_status_ok;
+}
+
+err_status_t
+hmac_init(hmac_ctx_t *state, const uint8_t *key, int key_len) {
+ 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 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(mod_hmac, "ipad: %s", octet_string_hex_string(ipad, 64));
+
+ /* initialize sha1 context */
+ sha1_init(&state->init_ctx);
+
+ /* hash ipad ^ key */
+ sha1_update(&state->init_ctx, ipad, 64);
+ memcpy(&state->ctx, &state->init_ctx, sizeof(sha1_ctx_t));
+
+ return err_status_ok;
+}
+
+err_status_t
+hmac_start(hmac_ctx_t *state) {
+
+ memcpy(&state->ctx, &state->init_ctx, sizeof(sha1_ctx_t));
+
+ return err_status_ok;
+}
+
+err_status_t
+hmac_update(hmac_ctx_t *state, const uint8_t *message, int msg_octets) {
+
+ debug_print(mod_hmac, "input: %s",
+ octet_string_hex_string(message, msg_octets));
+
+ /* hash message into sha1 context */
+ sha1_update(&state->ctx, message, msg_octets);
+
+ return err_status_ok;
+}
+
+err_status_t
+hmac_compute(hmac_ctx_t *state, const void *message,
+ int msg_octets, int tag_len, uint8_t *result) {
+ 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 err_status_bad_param;
+
+ /* hash message, copy output into H */
+ hmac_update(state, (const uint8_t*)message, msg_octets);
+ 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(mod_hmac, "intermediate state: %s",
+ octet_string_hex_string((uint8_t *)H, 20));
+
+ /* re-initialize hash context */
+ sha1_init(&state->ctx);
+
+ /* hash opad ^ key */
+ sha1_update(&state->ctx, (uint8_t *)state->opad, 64);
+
+ /* hash the result of the inner hash */
+ sha1_update(&state->ctx, (uint8_t *)H, 20);
+
+ /* the result is returned in the array hash_value[] */
+ 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(mod_hmac, "output: %s",
+ octet_string_hex_string((uint8_t *)hash_value, tag_len));
+
+ return err_status_ok;
+}
+
+
+/* begin test case 0 */
+
+uint8_t
+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
+};
+
+uint8_t
+hmac_test_case_0_data[8] = {
+ 0x48, 0x69, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65 /* "Hi There" */
+};
+
+uint8_t
+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
+};
+
+auth_test_case_t
+hmac_test_case_0 = {
+ 20, /* octets in key */
+ hmac_test_case_0_key, /* key */
+ 8, /* octets in data */
+ hmac_test_case_0_data, /* data */
+ 20, /* octets in tag */
+ hmac_test_case_0_tag, /* tag */
+ NULL /* pointer to next testcase */
+};
+
+/* end test case 0 */
+
+char hmac_description[] = "hmac sha-1 authentication function";
+
+/*
+ * auth_type_t hmac is the hmac metaobject
+ */
+
+auth_type_t
+hmac = {
+ (auth_alloc_func) hmac_alloc,
+ (auth_dealloc_func) hmac_dealloc,
+ (auth_init_func) hmac_init,
+ (auth_compute_func) hmac_compute,
+ (auth_update_func) hmac_update,
+ (auth_start_func) hmac_start,
+ (char *) hmac_description,
+ (int) 0, /* instance count */
+ (auth_test_case_t *) &hmac_test_case_0,
+ (debug_module_t *) &mod_hmac,
+ (auth_type_id_t) 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..103444bc53
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/null_auth.c
@@ -0,0 +1,162 @@
+/*
+ * null_auth.c
+ *
+ * implements the do-nothing auth algorithm
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "null_auth.h"
+#include "alloc.h"
+
+/* null_auth uses the auth debug module */
+
+extern debug_module_t mod_auth;
+
+err_status_t
+null_auth_alloc(auth_t **a, int key_len, int out_len) {
+ extern auth_type_t null_auth;
+ uint8_t *pointer;
+
+ debug_print(mod_auth, "allocating auth func with key length %d", key_len);
+ debug_print(mod_auth, " tag length %d", out_len);
+
+ /* allocate memory for auth and null_auth_ctx_t structures */
+ pointer = (uint8_t*)crypto_alloc(sizeof(null_auth_ctx_t) + sizeof(auth_t));
+ if (pointer == NULL)
+ return err_status_alloc_fail;
+
+ /* set pointers */
+ *a = (auth_t *)pointer;
+ (*a)->type = &null_auth;
+ (*a)->state = pointer + sizeof (auth_t);
+ (*a)->out_len = out_len;
+ (*a)->prefix_len = out_len;
+ (*a)->key_len = key_len;
+
+ /* increment global count of all null_auth uses */
+ null_auth.ref_count++;
+
+ return err_status_ok;
+}
+
+err_status_t
+null_auth_dealloc(auth_t *a) {
+ extern auth_type_t null_auth;
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero((uint8_t *)a,
+ sizeof(null_auth_ctx_t) + sizeof(auth_t));
+
+ /* free memory */
+ crypto_free(a);
+
+ /* decrement global count of all null_auth uses */
+ null_auth.ref_count--;
+
+ return err_status_ok;
+}
+
+err_status_t
+null_auth_init(null_auth_ctx_t *state, const uint8_t *key, int key_len) {
+
+ /* accept any length of key, and do nothing */
+
+ return err_status_ok;
+}
+
+err_status_t
+null_auth_compute(null_auth_ctx_t *state, uint8_t *message,
+ int msg_octets, int tag_len, uint8_t *result) {
+
+ return err_status_ok;
+}
+
+err_status_t
+null_auth_update(null_auth_ctx_t *state, uint8_t *message,
+ int msg_octets) {
+
+ return err_status_ok;
+}
+
+err_status_t
+null_auth_start(null_auth_ctx_t *state) {
+ return err_status_ok;
+}
+
+/*
+ * auth_type_t - defines description, test case, and null_auth
+ * metaobject
+ */
+
+/* begin test case 0 */
+
+auth_test_case_t
+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 */
+
+char null_auth_description[] = "null authentication function";
+
+auth_type_t
+null_auth = {
+ (auth_alloc_func) null_auth_alloc,
+ (auth_dealloc_func) null_auth_dealloc,
+ (auth_init_func) null_auth_init,
+ (auth_compute_func) null_auth_compute,
+ (auth_update_func) null_auth_update,
+ (auth_start_func) null_auth_start,
+ (char *) null_auth_description,
+ (int) 0, /* instance count */
+ (auth_test_case_t *) &null_auth_test_case_0,
+ (debug_module_t *) NULL,
+ (auth_type_id_t) 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..b9a8d10509
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/sha1.c
@@ -0,0 +1,405 @@
+/*
+ * 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-2006, 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 "sha1.h"
+
+debug_module_t 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
+sha1(const uint8_t *msg, int octets_in_msg, uint32_t hash_value[5]) {
+ sha1_ctx_t ctx;
+
+ sha1_init(&ctx);
+ sha1_update(&ctx, msg, octets_in_msg);
+ sha1_final(&ctx, hash_value);
+
+}
+
+/*
+ * 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
+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
+sha1_init(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
+sha1_update(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(mod_sha1, "(update) running sha1_core()", NULL);
+
+ sha1_core(ctx->M, ctx->H);
+
+ } else {
+
+ debug_print(mod_sha1, "(update) not running 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;
+ }
+
+ }
+
+}
+
+/*
+ * sha1_final(ctx, output) computes the result for ctx and copies it
+ * into the twenty octets located at *output
+ */
+
+void
+sha1_final(sha1_ctx_t *ctx, uint32_t *output) {
+ 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(mod_sha1, "(final) running sha1_core()", NULL);
+
+ if (ctx->octets_in_buffer >= 56) {
+
+ debug_print(mod_sha1, "(final) running 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..9d3149683b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/aes.h
@@ -0,0 +1,89 @@
+/*
+ * aes.h
+ *
+ * header file for the AES block cipher
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "config.h"
+
+#include "datatypes.h"
+#include "err.h"
+
+/* aes internals */
+
+typedef struct {
+ v128_t round[15];
+ int num_rounds;
+} aes_expanded_key_t;
+
+err_status_t
+aes_expand_encryption_key(const uint8_t *key,
+ int key_len,
+ aes_expanded_key_t *expanded_key);
+
+err_status_t
+aes_expand_decryption_key(const uint8_t *key,
+ int key_len,
+ aes_expanded_key_t *expanded_key);
+
+void
+aes_encrypt(v128_t *plaintext, const aes_expanded_key_t *exp_key);
+
+void
+aes_decrypt(v128_t *plaintext, const aes_expanded_key_t *exp_key);
+
+#if 0
+/*
+ * internal functions
+ */
+
+void
+aes_init_sbox(void);
+
+void
+aes_compute_tables(void);
+#endif
+
+#endif /* _AES_H */
diff --git a/netwerk/srtp/src/crypto/include/aes_cbc.h b/netwerk/srtp/src/crypto/include/aes_cbc.h
new file mode 100644
index 0000000000..6436b5b179
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/aes_cbc.h
@@ -0,0 +1,85 @@
+/*
+ * aes_cbc.h
+ *
+ * Header for AES Cipher Blobk Chaining Mode.
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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_CBC_H
+#define AES_CBC_H
+
+#include "aes.h"
+#include "cipher.h"
+
+typedef struct {
+ v128_t state; /* cipher chaining state */
+ v128_t previous; /* previous ciphertext block */
+ aes_expanded_key_t expanded_key; /* the cipher key */
+} aes_cbc_ctx_t;
+
+err_status_t
+aes_cbc_set_key(aes_cbc_ctx_t *c,
+ const unsigned char *key);
+
+err_status_t
+aes_cbc_encrypt(aes_cbc_ctx_t *c,
+ unsigned char *buf,
+ unsigned int *bytes_in_data);
+
+err_status_t
+aes_cbc_context_init(aes_cbc_ctx_t *c, const uint8_t *key,
+ int key_len, cipher_direction_t dir);
+
+err_status_t
+aes_cbc_set_iv(aes_cbc_ctx_t *c, void *iv);
+
+err_status_t
+aes_cbc_nist_encrypt(aes_cbc_ctx_t *c,
+ unsigned char *data,
+ unsigned int *bytes_in_data);
+
+err_status_t
+aes_cbc_nist_decrypt(aes_cbc_ctx_t *c,
+ unsigned char *data,
+ unsigned int *bytes_in_data);
+
+#endif /* AES_CBC_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..6b4aea6998
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/aes_icm.h
@@ -0,0 +1,92 @@
+/*
+ * aes_icm.h
+ *
+ * Header for AES Integer Counter Mode.
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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 */
+ aes_expanded_key_t expanded_key; /* the cipher key */
+ int bytes_in_buffer; /* number of unused bytes in buffer */
+} aes_icm_ctx_t;
+
+
+err_status_t
+aes_icm_context_init(aes_icm_ctx_t *c,
+ const unsigned char *key,
+ int key_len);
+
+err_status_t
+aes_icm_set_iv(aes_icm_ctx_t *c, void *iv);
+
+err_status_t
+aes_icm_encrypt(aes_icm_ctx_t *c,
+ unsigned char *buf, unsigned int *bytes_to_encr);
+
+err_status_t
+aes_icm_output(aes_icm_ctx_t *c,
+ unsigned char *buf, int bytes_to_output);
+
+err_status_t
+aes_icm_dealloc(cipher_t *c);
+
+err_status_t
+aes_icm_encrypt_ismacryp(aes_icm_ctx_t *c,
+ unsigned char *buf,
+ unsigned int *enc_len,
+ int forIsmacryp);
+
+err_status_t
+aes_icm_alloc_ismacryp(cipher_t **c,
+ int key_len,
+ int forIsmacryp);
+
+#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..5980eed6c1
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/alloc.h
@@ -0,0 +1,57 @@
+/*
+ * alloc.h
+ *
+ * interface to memory allocation and deallocation, with optional debugging
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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"
+
+void *
+crypto_alloc(size_t size);
+
+void
+crypto_free(void *ptr);
+
+#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..5b5e4b21d4
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/auth.h
@@ -0,0 +1,171 @@
+/*
+ * auth.h
+ *
+ * common interface to authentication functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 AUTH_H
+#define AUTH_H
+
+#include "datatypes.h"
+#include "err.h" /* error codes */
+#include "crypto.h" /* for auth_type_id_t */
+#include "crypto_types.h" /* for values of auth_type_id_t */
+
+typedef struct auth_type_t *auth_type_pointer;
+typedef struct auth_t *auth_pointer_t;
+
+typedef err_status_t (*auth_alloc_func)
+ (auth_pointer_t *ap, int key_len, int out_len);
+
+typedef err_status_t (*auth_init_func)
+ (void *state, const uint8_t *key, int key_len);
+
+typedef err_status_t (*auth_dealloc_func)(auth_pointer_t ap);
+
+typedef err_status_t (*auth_compute_func)
+ (void *state, uint8_t *buffer, int octets_to_auth,
+ int tag_len, uint8_t *tag);
+
+typedef err_status_t (*auth_update_func)
+ (void *state, uint8_t *buffer, int octets_to_auth);
+
+typedef err_status_t (*auth_start_func)(void *state);
+
+/* some syntactic sugar on these function types */
+
+#define auth_type_alloc(at, a, klen, outlen) \
+ ((at)->alloc((a), (klen), (outlen)))
+
+#define auth_init(a, key) \
+ (((a)->type)->init((a)->state, (key), ((a)->key_len)))
+
+#define auth_compute(a, buf, len, res) \
+ (((a)->type)->compute((a)->state, (buf), (len), (a)->out_len, (res)))
+
+#define auth_update(a, buf, len) \
+ (((a)->type)->update((a)->state, (buf), (len)))
+
+#define auth_start(a)(((a)->type)->start((a)->state))
+
+#define auth_dealloc(c) (((c)->type)->dealloc(c))
+
+/* functions to get information about a particular auth_t */
+
+int
+auth_get_key_length(const struct auth_t *a);
+
+int
+auth_get_tag_length(const struct auth_t *a);
+
+int
+auth_get_prefix_length(const struct auth_t *a);
+
+/*
+ * 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
+ * correcness of the implementation. (see the auth_type_self_test()
+ * function below)
+ */
+
+typedef struct auth_test_case_t {
+ int key_length_octets; /* octets in key */
+ uint8_t *key; /* key */
+ int data_length_octets; /* octets in data */
+ uint8_t *data; /* data */
+ int tag_length_octets; /* octets in tag */
+ uint8_t *tag; /* tag */
+ struct auth_test_case_t *next_test_case; /* pointer to next testcase */
+} auth_test_case_t;
+
+/* auth_type_t */
+
+typedef struct auth_type_t {
+ auth_alloc_func alloc;
+ auth_dealloc_func dealloc;
+ auth_init_func init;
+ auth_compute_func compute;
+ auth_update_func update;
+ auth_start_func start;
+ char *description;
+ int ref_count;
+ auth_test_case_t *test_data;
+ debug_module_t *debug;
+ auth_type_id_t id;
+} auth_type_t;
+
+typedef struct auth_t {
+ 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 */
+} auth_t;
+
+/*
+ * 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
+ */
+
+err_status_t
+auth_type_self_test(const auth_type_t *at);
+
+/*
+ * 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
+ */
+
+err_status_t
+auth_type_test(const auth_type_t *at, const auth_test_case_t *test_data);
+
+/*
+ * auth_type_get_ref_count(at) returns the reference count (the number
+ * of instantiations) of the auth_type_t at
+ */
+
+int
+auth_type_get_ref_count(const auth_type_t *at);
+
+#endif /* 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..eff6dd154b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/cipher.h
@@ -0,0 +1,230 @@
+/*
+ * cipher.h
+ *
+ * common interface to ciphers
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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 CIPHER_H
+#define CIPHER_H
+
+#include "datatypes.h"
+#include "rdbx.h" /* for xtd_seq_num_t */
+#include "err.h" /* for error codes */
+#include "crypto.h" /* for cipher_type_id_t */
+#include "crypto_types.h" /* for values of cipher_type_id_t */
+
+
+/**
+ * @brief cipher_direction_t defines a particular cipher operation.
+ *
+ * A 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 {
+ direction_encrypt, /**< encryption (convert plaintext to ciphertext) */
+ direction_decrypt, /**< decryption (convert ciphertext to plaintext) */
+ direction_any /**< encryption or decryption */
+} cipher_direction_t;
+
+/*
+ * the cipher_pointer and cipher_type_pointer definitions are needed
+ * as cipher_t and cipher_type_t are not yet defined
+ */
+
+typedef struct cipher_type_t *cipher_type_pointer_t;
+typedef struct cipher_t *cipher_pointer_t;
+
+/*
+ * a cipher_alloc_func_t allocates (but does not initialize) a cipher_t
+ */
+
+typedef err_status_t (*cipher_alloc_func_t)
+ (cipher_pointer_t *cp, int key_len);
+
+/*
+ * a cipher_init_func_t [re-]initializes a cipher_t with a given key
+ * and direction (i.e., encrypt or decrypt)
+ */
+
+typedef err_status_t (*cipher_init_func_t)
+(void *state, const uint8_t *key, int key_len, cipher_direction_t dir);
+
+/* a cipher_dealloc_func_t de-allocates a cipher_t */
+
+typedef err_status_t (*cipher_dealloc_func_t)(cipher_pointer_t cp);
+
+/* a cipher_set_segment_func_t sets the segment index of a cipher_t */
+
+typedef err_status_t (*cipher_set_segment_func_t)
+ (void *state, xtd_seq_num_t idx);
+
+/* a cipher_encrypt_func_t encrypts data in-place */
+
+typedef err_status_t (*cipher_encrypt_func_t)
+ (void *state, uint8_t *buffer, unsigned int *octets_to_encrypt);
+
+/* a cipher_decrypt_func_t decrypts data in-place */
+
+typedef err_status_t (*cipher_decrypt_func_t)
+ (void *state, uint8_t *buffer, unsigned int *octets_to_decrypt);
+
+/*
+ * a cipher_set_iv_func_t function sets the current initialization vector
+ */
+
+typedef err_status_t (*cipher_set_iv_func_t)
+ (cipher_pointer_t cp, void *iv);
+
+/*
+ * cipher_test_case_t is a (list of) key, salt, xtd_seq_num_t,
+ * plaintext, and ciphertext 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 correcness of the implementation.
+ * (see the cipher_type_self_test() function below)
+ */
+
+typedef struct cipher_test_case_t {
+ int key_length_octets; /* octets in key */
+ uint8_t *key; /* key */
+ uint8_t *idx; /* packet index */
+ int plaintext_length_octets; /* octets in plaintext */
+ uint8_t *plaintext; /* plaintext */
+ int ciphertext_length_octets; /* octets in plaintext */
+ uint8_t *ciphertext; /* ciphertext */
+ struct cipher_test_case_t *next_test_case; /* pointer to next testcase */
+} cipher_test_case_t;
+
+/* cipher_type_t defines the 'metadata' for a particular cipher type */
+
+typedef struct cipher_type_t {
+ cipher_alloc_func_t alloc;
+ cipher_dealloc_func_t dealloc;
+ cipher_init_func_t init;
+ cipher_encrypt_func_t encrypt;
+ cipher_encrypt_func_t decrypt;
+ cipher_set_iv_func_t set_iv;
+ char *description;
+ int ref_count;
+ cipher_test_case_t *test_data;
+ debug_module_t *debug;
+ cipher_type_id_t id;
+} cipher_type_t;
+
+/*
+ * cipher_t defines an instantiation of a particular cipher, with fixed
+ * key length, key and salt values
+ */
+
+typedef struct cipher_t {
+ cipher_type_t *type;
+ void *state;
+ int key_len;
+#ifdef FORCE_64BIT_ALIGN
+ int pad;
+#endif
+} cipher_t;
+
+/* some syntactic sugar on these function types */
+
+#define cipher_type_alloc(ct, c, klen) ((ct)->alloc((c), (klen)))
+
+#define cipher_dealloc(c) (((c)->type)->dealloc(c))
+
+#define cipher_init(c, k, dir) (((c)->type)->init(((c)->state), (k), ((c)->key_len), (dir)))
+
+#define cipher_encrypt(c, buf, len) \
+ (((c)->type)->encrypt(((c)->state), (buf), (len)))
+
+#define cipher_decrypt(c, buf, len) \
+ (((c)->type)->decrypt(((c)->state), (buf), (len)))
+
+#define cipher_set_iv(c, n) \
+ ((c) ? (((c)->type)->set_iv(((cipher_pointer_t)(c)->state), (n))) : \
+ err_status_no_such_op)
+
+err_status_t
+cipher_output(cipher_t *c, uint8_t *buffer, int num_octets_to_output);
+
+
+/* some bookkeeping functions */
+
+int
+cipher_get_key_length(const cipher_t *c);
+
+
+/*
+ * cipher_type_self_test() tests a cipher against test cases provided in
+ * an array of values of key/xtd_seq_num_t/plaintext/ciphertext
+ * that is known to be good
+ */
+
+err_status_t
+cipher_type_self_test(const cipher_type_t *ct);
+
+
+/*
+ * cipher_type_test() tests a cipher against external test cases provided in
+ * an array of values of key/xtd_seq_num_t/plaintext/ciphertext
+ * that is known to be good
+ */
+
+err_status_t
+cipher_type_test(const cipher_type_t *ct, const cipher_test_case_t *test_data);
+
+
+/*
+ * cipher_bits_per_second(c, l, t) computes (and 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
+cipher_bits_per_second(cipher_t *c, int octets_in_buffer, int num_trials);
+
+#endif /* CIPHER_H */
diff --git a/netwerk/srtp/src/crypto/include/crypto.h b/netwerk/srtp/src/crypto/include/crypto.h
new file mode 100644
index 0000000000..1daf8ead17
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/crypto.h
@@ -0,0 +1,78 @@
+/*
+ * crypto.h
+ *
+ * API for libcrypto
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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_H
+#define CRYPTO_H
+
+/**
+ * @brief A cipher_type_id_t is an identifier for a particular cipher
+ * type.
+ *
+ * A cipher_type_id_t is an integer that represents a particular
+ * cipher type, e.g. the Advanced Encryption Standard (AES). A
+ * 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 cipher_type_id_t;
+
+/**
+ * @brief An auth_type_id_t is an identifier for a particular authentication
+ * function.
+ *
+ * An auth_type_id_t is an integer that represents a particular
+ * authentication function type, e.g. HMAC-SHA1. A 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 auth_type_id_t;
+
+#endif /* CRYPTO_H */
+
+
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..1acf4978d0
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/crypto_kernel.h
@@ -0,0 +1,280 @@
+/*
+ * crypto_kernel.h
+ *
+ * header for the cryptographic kernel
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 "rand_source.h"
+#include "prng.h"
+#include "cipher.h"
+#include "auth.h"
+#include "cryptoalg.h"
+#include "stat.h"
+#include "err.h"
+#include "crypto_types.h"
+#include "key.h"
+#include "crypto.h"
+
+/*
+ * crypto_kernel_state_t defines the possible states:
+ *
+ * insecure - not yet initialized
+ * secure - initialized and passed self-tests
+ */
+
+typedef enum {
+ crypto_kernel_state_insecure,
+ crypto_kernel_state_secure
+} crypto_kernel_state_t;
+
+/*
+ * linked list of cipher types
+ */
+
+typedef struct kernel_cipher_type {
+ cipher_type_id_t id;
+ cipher_type_t *cipher_type;
+ struct kernel_cipher_type *next;
+} kernel_cipher_type_t;
+
+/*
+ * linked list of auth types
+ */
+
+typedef struct kernel_auth_type {
+ auth_type_id_t id;
+ auth_type_t *auth_type;
+ struct kernel_auth_type *next;
+} kernel_auth_type_t;
+
+/*
+ * linked list of debug modules
+ */
+
+typedef struct kernel_debug_module {
+ debug_module_t *mod;
+ struct kernel_debug_module *next;
+} 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 {
+ crypto_kernel_state_t state; /* current state of kernel */
+ kernel_cipher_type_t *cipher_type_list; /* list of all cipher types */
+ kernel_auth_type_t *auth_type_list; /* list of all auth func types */
+ kernel_debug_module_t *debug_module_list; /* list of all debug modules */
+} crypto_kernel_t;
+
+
+/*
+ * crypto_kernel_t external api
+ */
+
+
+/*
+ * The function 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:
+ *
+ * err_status_ok initialization successful
+ * <other> init failure
+ *
+ * If any value other than err_status_ok is returned, the
+ * crypto_kernel MUST NOT be used.
+ */
+
+err_status_t
+crypto_kernel_init(void);
+
+
+/*
+ * The function crypto_kernel_shutdown() de-initializes the
+ * crypto_kernel, zeroizes keys and other cryptographic material, and
+ * deallocates any dynamically allocated memory. Possible return
+ * values are:
+ *
+ * err_status_ok shutdown successful
+ * <other> shutdown failure
+ *
+ */
+
+err_status_t
+crypto_kernel_shutdown(void);
+
+/*
+ * The function 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:
+ *
+ * err_status_ok all tests were passed
+ * <other> a test failed
+ *
+ */
+
+err_status_t
+crypto_kernel_status(void);
+
+
+/*
+ * crypto_kernel_list_debug_modules() outputs a list of debugging modules
+ *
+ */
+
+err_status_t
+crypto_kernel_list_debug_modules(void);
+
+/*
+ * crypto_kernel_load_cipher_type()
+ *
+ */
+
+err_status_t
+crypto_kernel_load_cipher_type(cipher_type_t *ct, cipher_type_id_t id);
+
+err_status_t
+crypto_kernel_load_auth_type(auth_type_t *ct, auth_type_id_t id);
+
+/*
+ * crypto_kernel_replace_cipher_type(ct, id)
+ *
+ * replaces the crypto kernel's existing cipher 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.
+ */
+err_status_t
+crypto_kernel_replace_cipher_type(cipher_type_t *ct, cipher_type_id_t id);
+
+
+/*
+ * crypto_kernel_replace_auth_type(ct, id)
+ *
+ * replaces the crypto kernel's existing cipher 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.
+ */
+err_status_t
+crypto_kernel_replace_auth_type(auth_type_t *ct, auth_type_id_t id);
+
+
+err_status_t
+crypto_kernel_load_debug_module(debug_module_t *new_dm);
+
+/*
+ * 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:
+ *
+ * err_status_ok no problems
+ * err_status_alloc_fail an allocation failure occured
+ * err_status_fail couldn't find cipher with identifier 'id'
+ */
+
+err_status_t
+crypto_kernel_alloc_cipher(cipher_type_id_t id,
+ cipher_pointer_t *cp,
+ int key_len);
+
+/*
+ * 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:
+ *
+ * err_status_ok no problems
+ * err_status_alloc_fail an allocation failure occured
+ * err_status_fail couldn't find auth with identifier 'id'
+ */
+
+err_status_t
+crypto_kernel_alloc_auth(auth_type_id_t id,
+ auth_pointer_t *ap,
+ int key_len,
+ int tag_len);
+
+
+/*
+ * 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 err_status_ok on success, err_status_fail otherwise
+ */
+
+err_status_t
+crypto_kernel_set_debug_module(char *mod_name, int v);
+
+/**
+ * @brief writes a random octet string.
+ *
+ * The function call crypto_get_random(dest, len) writes len octets of
+ * random data to the location to which dest points, and returns an
+ * error code. This error code @b must be checked, and if a failure is
+ * reported, the data in the buffer @b must @b not be used.
+ *
+ * @warning If the return code is not checked, then non-random
+ * data may be in the buffer. This function will fail
+ * unless it is called after crypto_kernel_init().
+ *
+ * @return
+ * - err_status_ok if no problems occured.
+ * - [other] a problem occured, and no assumptions should
+ * be made about the contents of the destination
+ * buffer.
+ *
+ * @ingroup SRTP
+ */
+err_status_t
+crypto_get_random(unsigned char *buffer, unsigned int length);
+
+#endif /* CRYPTO_KERNEL */
diff --git a/netwerk/srtp/src/crypto/include/crypto_math.h b/netwerk/srtp/src/crypto/include/crypto_math.h
new file mode 100644
index 0000000000..52f083721d
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/crypto_math.h
@@ -0,0 +1,239 @@
+/*
+ * math.h
+ *
+ * crypto math operations and data types
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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 MATH_H
+#define MATH_H
+
+#include "datatypes.h"
+
+unsigned char
+v32_weight(v32_t a);
+
+unsigned char
+v32_distance(v32_t x, v32_t y);
+
+unsigned int
+v32_dot_product(v32_t a, v32_t b);
+
+char *
+v16_bit_string(v16_t x);
+
+char *
+v32_bit_string(v32_t x);
+
+char *
+v64_bit_string(const v64_t *x);
+
+char *
+octet_hex_string(uint8_t x);
+
+char *
+v16_hex_string(v16_t x);
+
+char *
+v32_hex_string(v32_t x);
+
+char *
+v64_hex_string(const v64_t *x);
+
+int
+hex_char_to_nibble(uint8_t c);
+
+int
+is_hex_string(char *s);
+
+v16_t
+hex_string_to_v16(char *s);
+
+v32_t
+hex_string_to_v32(char *s);
+
+v64_t
+hex_string_to_v64(char *s);
+
+/* the matrix A[] is stored in column format, i.e., A[i] is
+ the ith column of the matrix */
+
+uint8_t
+A_times_x_plus_b(uint8_t A[8], uint8_t x, uint8_t b);
+
+void
+v16_copy_octet_string(v16_t *x, const uint8_t s[2]);
+
+void
+v32_copy_octet_string(v32_t *x, const uint8_t s[4]);
+
+void
+v64_copy_octet_string(v64_t *x, const uint8_t s[8]);
+
+void
+v128_add(v128_t *z, v128_t *x, v128_t *y);
+
+int
+octet_string_is_eq(uint8_t *a, uint8_t *b, int len);
+
+void
+octet_string_set_to_zero(uint8_t *s, int len);
+
+
+
+/*
+ * the matrix A[] is stored in column format, i.e., A[i] is the ith
+ * column of the matrix
+*/
+uint8_t
+A_times_x_plus_b(uint8_t A[8], uint8_t x, uint8_t b);
+
+
+#if 0
+#if WORDS_BIGENDIAN
+
+#define _v128_add(z, x, y) { \
+ uint64_t tmp; \
+ \
+ tmp = x->v32[3] + y->v32[3]; \
+ z->v32[3] = (uint32_t) tmp; \
+ \
+ tmp = x->v32[2] + y->v32[2] + (tmp >> 32); \
+ z->v32[2] = (uint32_t) tmp; \
+ \
+ tmp = x->v32[1] + y->v32[1] + (tmp >> 32); \
+ z->v32[1] = (uint32_t) tmp; \
+ \
+ tmp = x->v32[0] + y->v32[0] + (tmp >> 32); \
+ z->v32[0] = (uint32_t) tmp; \
+}
+
+#else /* assume little endian architecture */
+
+#define _v128_add(z, x, y) { \
+ uint64_t tmp; \
+ \
+ tmp = htonl(x->v32[3]) + htonl(y->v32[3]); \
+ z->v32[3] = ntohl((uint32_t) tmp); \
+ \
+ tmp = htonl(x->v32[2]) + htonl(y->v32[2]) \
+ + htonl(tmp >> 32); \
+ z->v32[2] = ntohl((uint32_t) tmp); \
+ \
+ tmp = htonl(x->v32[1]) + htonl(y->v32[1]) \
+ + htonl(tmp >> 32); \
+ z->v32[1] = ntohl((uint32_t) tmp); \
+ \
+ tmp = htonl(x->v32[0]) + htonl(y->v32[0]) \
+ + htonl(tmp >> 32); \
+ z->v32[0] = ntohl((uint32_t) tmp); \
+}
+
+#endif /* WORDS_BIGENDIAN */
+#endif
+
+#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, returns 0 otherwise
+ */
+
+int
+octet_string_is_eq(uint8_t *a, uint8_t *b, int len);
+
+void
+octet_string_set_to_zero(uint8_t *s, int len);
+
+
+#endif /* MATH_H */
+
+
+
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..35317108c3
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/crypto_types.h
@@ -0,0 +1,220 @@
+/*
+ * crypto_types.h
+ *
+ * constants for cipher types and auth func types
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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_TYPES_H
+#define CRYPTO_TYPES_H
+
+/**
+ * @defgroup Algos Cryptographic Algorithms
+ *
+ *
+ * This library provides several different cryptographic algorithms,
+ * each of which can be selected by using the cipher_type_id_t and
+ * auth_type_id_t. These algorithms are documented below.
+ *
+ * Authentication functions that use the Universal Security Transform
+ * (UST) must be used in conjunction with a cipher other than the null
+ * cipher. These functions require a per-message pseudorandom input
+ * that is generated by the cipher.
+ *
+ * The identifiers STRONGHOLD_AUTH and STRONGHOLD_CIPHER identify the
+ * strongest available authentication function and cipher,
+ * respectively. They are resolved at compile time to the strongest
+ * available algorithm. The stronghold algorithms can serve as did
+ * the keep of a medieval fortification; they provide the strongest
+ * defense (or the last refuge).
+ *
+ * @{
+ */
+
+/**
+ * @defgroup Ciphers Cipher Types
+ *
+ * @brief Each cipher type is identified by an unsigned integer. The
+ * cipher types available in this edition of libSRTP are given
+ * by the #defines below.
+ *
+ * A cipher_type_id_t is an identifier for a cipher_type; only values
+ * given by the #defines above (or those present in the file
+ * crypto_types.h) should be used.
+ *
+ * The identifier STRONGHOLD_CIPHER indicates the strongest available
+ * cipher, allowing an application to choose the strongest available
+ * algorithm without any advance knowledge about the avaliable
+ * algorithms.
+ *
+ * @{
+ */
+
+/**
+ * @brief The null cipher performs no encryption.
+ *
+ * The 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 NULL_CIPHER 0
+
+/**
+ * @brief AES Integer Counter Mode (AES ICM)
+ *
+ * AES ICM is the variant of counter mode that is used by Secure RTP.
+ * This cipher uses a 16-, 24-, or 32-octet key concatenated with a
+ * 14-octet offset (or salt) value.
+ */
+#define AES_ICM 1
+
+/**
+ * @brief AES-128 Integer Counter Mode (AES ICM)
+ * AES-128 ICM is a deprecated alternate name for AES ICM.
+ */
+#define AES_128_ICM AES_ICM
+
+/**
+ * @brief SEAL 3.0
+ *
+ * SEAL is the Software-Optimized Encryption Algorithm of Coppersmith
+ * and Rogaway. Nota bene: this cipher is IBM proprietary.
+ */
+#define SEAL 2
+
+/**
+ * @brief AES Cipher Block Chaining mode (AES CBC)
+ *
+ * AES CBC is the AES Cipher Block Chaining mode.
+ * This cipher uses a 16-, 24-, or 32-octet key.
+ */
+#define AES_CBC 3
+
+/**
+ * @brief AES-128 Cipher Block Chaining mode (AES CBC)
+ *
+ * AES-128 CBC is a deprecated alternate name for AES CBC.
+ */
+#define AES_128_CBC AES_CBC
+
+/**
+ * @brief Strongest available cipher.
+ *
+ * This identifier resolves to the strongest cipher type available.
+ */
+#define STRONGHOLD_CIPHER AES_ICM
+
+/**
+ * @}
+ */
+
+
+
+/**
+ * @defgroup Authentication Authentication Function Types
+ *
+ * @brief Each authentication function type is identified by an
+ * unsigned integer. The authentication function types available in
+ * this edition of libSRTP are given by the #defines below.
+ *
+ * An auth_type_id_t is an identifier for an authentication function type;
+ * only values given by the #defines above (or those present in the
+ * file crypto_types.h) should be used.
+ *
+ * The identifier STRONGHOLD_AUTH indicates the strongest available
+ * authentication function, allowing an application to choose the
+ * strongest available algorithm without any advance knowledge about
+ * the avaliable algorithms. The stronghold algorithms can serve as
+ * did the keep of a medieval fortification; they provide the
+ * strongest defense (or the last refuge).
+ *
+ * @{
+ */
+
+/**
+ * @brief 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 NULL_AUTH 0
+
+/**
+ * @brief UST with TMMH Version 2
+ *
+ * UST_TMMHv2 implements the Truncated Multi-Modular Hash using
+ * UST. This function must be used in conjunction with a cipher other
+ * than the null cipher.
+ * with a cipher.
+ */
+#define UST_TMMHv2 1
+
+/**
+ * @brief (UST) AES-128 XORMAC
+ *
+ * UST_AES_128_XMAC implements AES-128 XORMAC, using UST. Nota bene:
+ * the XORMAC algorithm is IBM proprietary.
+ */
+#define UST_AES_128_XMAC 2
+
+/**
+ * @brief HMAC-SHA1
+ *
+ * HMAC_SHA1 implements the Hash-based MAC using the NIST Secure
+ * Hash Algorithm version 1 (SHA1).
+ */
+#define HMAC_SHA1 3
+
+/**
+ * @brief Strongest available authentication function.
+ *
+ * This identifier resolves to the strongest available authentication
+ * function.
+ */
+#define STRONGHOLD_AUTH HMAC_SHA1
+
+/**
+ * @}
+ */
+/**
+ * @}
+ */
+
+#endif /* CRYPTO_TYPES_H */
diff --git a/netwerk/srtp/src/crypto/include/cryptoalg.h b/netwerk/srtp/src/crypto/include/cryptoalg.h
new file mode 100644
index 0000000000..d9f0441e0e
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/cryptoalg.h
@@ -0,0 +1,133 @@
+/*
+ * cryptoalg.h
+ *
+ * API for authenticated encryption crypto algorithms
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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 CRYPTOALG_H
+#define CRYPTOALG_H
+
+#include "err.h"
+
+/**
+ * @defgroup Crypto Cryptography
+ *
+ * Zed uses a simple interface to a cryptographic transform.
+ *
+ * @{
+ */
+
+/**
+ * @brief applies a crypto algorithm
+ *
+ * The function pointer cryptoalg_func_t points to a function that
+ * implements a crypto transform, and provides a uniform API for
+ * accessing crypto mechanisms.
+ *
+ * @param key location of secret key
+ *
+ * @param clear data to be authenticated but not encrypted
+ *
+ * @param clear_len length of data to be authenticated but not encrypted
+ *
+ * @param iv location to write the Initialization Vector (IV)
+ *
+ * @param protect location of the data to be encrypted and
+ * authenticated (before the function call), and the ciphertext
+ * and authentication tag (after the call)
+ *
+ * @param protected_len location of the length of the data to be
+ * encrypted and authenticated (before the function call), and the
+ * length of the ciphertext (after the call)
+ *
+ */
+
+typedef err_status_t (*cryptoalg_func_t)
+ (void *key,
+ const void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *protect,
+ unsigned *protected_len);
+
+typedef
+err_status_t (*cryptoalg_inv_t)
+ (void *key, /* location of secret key */
+ const void *clear, /* data to be authenticated only */
+ unsigned clear_len, /* length of data to be authenticated only */
+ void *iv, /* location of iv */
+ void *opaque, /* data to be decrypted and authenticated */
+ unsigned *opaque_len /* location of the length of data to be
+ * decrypted and authd (before and after)
+ */
+ );
+
+typedef struct cryptoalg_ctx_t {
+ cryptoalg_func_t enc;
+ cryptoalg_inv_t dec;
+ unsigned key_len;
+ unsigned iv_len;
+ unsigned auth_tag_len;
+ unsigned max_expansion;
+} cryptoalg_ctx_t;
+
+typedef cryptoalg_ctx_t *cryptoalg_t;
+
+#define cryptoalg_get_key_len(cryptoalg) ((cryptoalg)->key_len)
+
+#define cryptoalg_get_iv_len(cryptoalg) ((cryptoalg)->iv_len)
+
+#define cryptoalg_get_auth_tag_len(cryptoalg) ((cryptoalg)->auth_tag_len)
+
+int
+cryptoalg_get_id(cryptoalg_t c);
+
+cryptoalg_t
+cryptoalg_find_by_id(int id);
+
+
+/**
+ * @}
+ */
+
+#endif /* CRYPTOALG_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..803d75a3d9
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/datatypes.h
@@ -0,0 +1,508 @@
+/*
+ * datatypes.h
+ *
+ * data types for bit vectors and finite fields
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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>
+
+#ifndef SRTP_KERNEL
+# 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>
+# endif
+#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;
+
+
+
+/* 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);
+
+char *
+octet_bit_string(uint8_t x);
+
+#define MAX_PRINT_STRING_LEN 1024
+
+char *
+octet_string_hex_string(const void *str, int length);
+
+char *
+v128_bit_string(v128_t *x);
+
+char *
+v128_hex_string(v128_t *x);
+
+uint8_t
+nibble_to_hex_char(uint8_t nibble);
+
+char *
+char_to_hex_string(char *x, int num_char);
+
+uint8_t
+hex_string_to_octet(char *s);
+
+/*
+ * hex_string_to_octet_string(raw, hex, len) converts the hexadecimal
+ * string at *hex (of length len octets) to the equivalent raw data
+ * and writes it to *raw.
+ *
+ * if a character in the hex string that is not a hexadeciaml digit
+ * (0123456789abcdefABCDEF) is encountered, the function stops writing
+ * data to *raw
+ *
+ * the number of hex digits copied (which is two times the number of
+ * octets in *raw) is returned
+ */
+
+int
+hex_string_to_octet_string(char *raw, char *hex, int len);
+
+v128_t
+hex_string_to_v128(char *s);
+
+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) \
+)
+
+
+#if 0
+/* nothing uses this */
+#ifdef WORDS_BIGENDIAN
+
+#define _v128_add(z, x, y) { \
+ uint64_t tmp; \
+ \
+ tmp = x->v32[3] + y->v32[3]; \
+ z->v32[3] = (uint32_t) tmp; \
+ \
+ tmp = x->v32[2] + y->v32[2] + (tmp >> 32); \
+ z->v32[2] = (uint32_t) tmp; \
+ \
+ tmp = x->v32[1] + y->v32[1] + (tmp >> 32); \
+ z->v32[1] = (uint32_t) tmp; \
+ \
+ tmp = x->v32[0] + y->v32[0] + (tmp >> 32); \
+ z->v32[0] = (uint32_t) tmp; \
+}
+
+#else /* assume little endian architecture */
+
+#define _v128_add(z, x, y) { \
+ uint64_t tmp; \
+ \
+ tmp = htonl(x->v32[3]) + htonl(y->v32[3]); \
+ z->v32[3] = ntohl((uint32_t) tmp); \
+ \
+ tmp = htonl(x->v32[2]) + htonl(y->v32[2]) \
+ + htonl(tmp >> 32); \
+ z->v32[2] = ntohl((uint32_t) tmp); \
+ \
+ tmp = htonl(x->v32[1]) + htonl(y->v32[1]) \
+ + htonl(tmp >> 32); \
+ z->v32[1] = ntohl((uint32_t) tmp); \
+ \
+ tmp = htonl(x->v32[0]) + htonl(y->v32[0]) \
+ + htonl(tmp >> 32); \
+ z->v32[0] = ntohl((uint32_t) tmp); \
+}
+#endif /* WORDS_BIGENDIAN */
+#endif /* 0 */
+
+
+#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, returns 0 otherwise
+ */
+
+int
+octet_string_is_eq(uint8_t *a, uint8_t *b, int len);
+
+void
+octet_string_set_to_zero(uint8_t *s, int len);
+
+
+#ifndef SRTP_KERNEL_LINUX
+
+/*
+ * 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
+
+#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>
+# else
+# error "Platform not recognized"
+# endif
+# 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
+ /* 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
+ return v;
+}
+
+#endif /* ! SRTP_KERNEL_LINUX */
+
+#endif /* WORDS_BIGENDIAN */
+
+/*
+ * 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);
+
+#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..1a6e170182
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/err.h
@@ -0,0 +1,174 @@
+/*
+ * err.h
+ *
+ * error status codes
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "datatypes.h"
+
+/**
+ * @defgroup Error Error Codes
+ *
+ * Error status codes are represented by the enumeration err_status_t.
+ *
+ * @{
+ */
+
+
+/*
+ * @brief err_status_t defines error codes.
+ *
+ * The enumeration err_status_t defines error codes. Note that the
+ * value of err_status_ok is equal to zero, which can simplify error
+ * checking somewhat.
+ *
+ */
+typedef enum {
+ err_status_ok = 0, /**< nothing to report */
+ err_status_fail = 1, /**< unspecified failure */
+ err_status_bad_param = 2, /**< unsupported parameter */
+ err_status_alloc_fail = 3, /**< couldn't allocate memory */
+ err_status_dealloc_fail = 4, /**< couldn't deallocate properly */
+ err_status_init_fail = 5, /**< couldn't initialize */
+ err_status_terminus = 6, /**< can't process as much data as requested */
+ err_status_auth_fail = 7, /**< authentication failure */
+ err_status_cipher_fail = 8, /**< cipher failure */
+ err_status_replay_fail = 9, /**< replay check failed (bad index) */
+ err_status_replay_old = 10, /**< replay check failed (index too old) */
+ err_status_algo_fail = 11, /**< algorithm failed test routine */
+ err_status_no_such_op = 12, /**< unsupported operation */
+ err_status_no_ctx = 13, /**< no appropriate context found */
+ err_status_cant_check = 14, /**< unable to perform desired validation */
+ err_status_key_expired = 15, /**< can't use key any more */
+ err_status_socket_err = 16, /**< error in use of socket */
+ err_status_signal_err = 17, /**< error in use POSIX signals */
+ err_status_nonce_bad = 18, /**< nonce check failed */
+ err_status_read_fail = 19, /**< couldn't read data */
+ err_status_write_fail = 20, /**< couldn't write data */
+ err_status_parse_err = 21, /**< error pasring data */
+ err_status_encode_err = 22, /**< error encoding data */
+ err_status_semaphore_err = 23,/**< error while using semaphores */
+ err_status_pfkey_err = 24 /**< error while using pfkey */
+} err_status_t;
+
+/**
+ * @}
+ */
+
+typedef enum {
+ err_level_emergency = 0,
+ err_level_alert,
+ err_level_critical,
+ err_level_error,
+ err_level_warning,
+ err_level_notice,
+ err_level_info,
+ err_level_debug,
+ err_level_none
+} err_reporting_level_t;
+
+/*
+ * err_reporting_init prepares the error system. If
+ * ERR_REPORTING_SYSLOG is defined, it will open syslog.
+ *
+ * The ident argument is a string that will be prepended to
+ * all syslog messages. It is conventionally argv[0].
+ */
+
+err_status_t
+err_reporting_init(char *ident);
+
+#ifdef SRTP_KERNEL_LINUX
+extern err_reporting_level_t err_level;
+#else
+
+/*
+ * keydaemon_report_error reports a 'printf' formatted error
+ * string, followed by a an arg list. The priority argument
+ * is equivalent to that defined for syslog.
+ *
+ * Errors will be reported to ERR_REPORTING_FILE, if defined, and to
+ * syslog, if ERR_REPORTING_SYSLOG is defined.
+ *
+ */
+
+void
+err_report(int priority, char *format, ...);
+#endif /* ! SRTP_KERNEL_LINUX */
+
+
+/*
+ * debug_module_t defines a debug module
+ */
+
+typedef struct {
+ int on; /* 1 if debugging is on, 0 if it is off */
+ char *name; /* printable name for debug module */
+} debug_module_t;
+
+#ifdef ENABLE_DEBUGGING
+
+#define debug_on(mod) (mod).on = 1
+
+#define debug_off(mod) (mod).on = 0
+
+/* use err_report() to report debug message */
+#define debug_print(mod, format, arg) \
+ if (mod.on) err_report(err_level_debug, ("%s: " format "\n"), mod.name, arg)
+#define debug_print2(mod, format, arg1,arg2) \
+ if (mod.on) err_report(err_level_debug, ("%s: " format "\n"), mod.name, arg1,arg2)
+
+#else
+
+/* define macros to do nothing */
+#define debug_print(mod, format, arg)
+
+#define debug_on(mod)
+
+#define debug_off(mod)
+
+#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..262c0e2d6e
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/hmac.h
@@ -0,0 +1,78 @@
+/*
+ * hmac.h
+ *
+ * interface to hmac auth_type_t
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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];
+ sha1_ctx_t ctx;
+ sha1_ctx_t init_ctx;
+} hmac_ctx_t;
+
+err_status_t
+hmac_alloc(auth_t **a, int key_len, int out_len);
+
+err_status_t
+hmac_dealloc(auth_t *a);
+
+err_status_t
+hmac_init(hmac_ctx_t *state, const uint8_t *key, int key_len);
+
+err_status_t
+hmac_start(hmac_ctx_t *state);
+
+err_status_t
+hmac_update(hmac_ctx_t *state, const uint8_t *message, int msg_octets);
+
+err_status_t
+hmac_compute(hmac_ctx_t *state, const void *message,
+ int msg_octets, int tag_len, uint8_t *result);
+
+
+#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..3d7589ebc8
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/integers.h
@@ -0,0 +1,155 @@
+/*
+ * integers.h
+ *
+ * defines integer types (or refers to their definitions)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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
+
+#include "config.h" /* configuration file, using autoconf */
+
+#ifdef SRTP_KERNEL
+
+#include "kernel_compat.h"
+
+#else /* SRTP_KERNEL */
+
+/* use standard integer definitions, if they're available */
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+#ifdef INTEGER_TYPES_H
+/* Let configure tell us where to get the equivalent to <stdint.h> */
+#include INTEGER_TYPES_H
+
+#if !defined(HAVE_UINT64_T)
+#define NO_64BIT_MATH 1
+#endif
+#else
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#endif /* INTEGER_TYPES_H */
+#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
+
+/* 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
+
+#ifdef NO_64BIT_MATH
+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
+
+#endif /* SRTP_KERNEL */
+
+/* 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
+
+#endif /* INTEGERS_H */
diff --git a/netwerk/srtp/src/crypto/include/kernel_compat.h b/netwerk/srtp/src/crypto/include/kernel_compat.h
new file mode 100644
index 0000000000..59d1898e1b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/kernel_compat.h
@@ -0,0 +1,84 @@
+/*
+ * kernel_compat.h
+ *
+ * Compatibility stuff for building in kernel context where standard
+ * C headers and library are not available.
+ *
+ * Marcus Sundberg
+ * Ingate Systems AB
+ */
+/*
+ *
+ * Copyright(c) 2005 Ingate Systems AB
+ * 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 author(s) 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 KERNEL_COMPAT_H
+#define KERNEL_COMPAT_H
+
+#ifdef SRTP_KERNEL_LINUX
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/random.h>
+#include <linux/byteorder/generic.h>
+
+
+#define err_report(priority, ...) \
+ do {\
+ if (priority <= err_level) {\
+ printk(__VA_ARGS__);\
+ }\
+ }while(0)
+
+#define clock() (jiffies)
+#define time(x) (jiffies)
+
+/* rand() implementation. */
+#define RAND_MAX 32767
+
+static inline int rand(void)
+{
+ uint32_t temp;
+ get_random_bytes(&temp, sizeof(temp));
+ return temp % (RAND_MAX+1);
+}
+
+/* stdio/stdlib implementation. */
+#define printf(...) printk(__VA_ARGS__)
+#define exit(n) panic("%s:%d: exit(%d)\n", __FILE__, __LINE__, (n))
+
+#endif /* SRTP_KERNEL_LINUX */
+
+#endif /* KERNEL_COMPAT_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..e7e07448e7
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/key.h
@@ -0,0 +1,82 @@
+/*
+ * key.h
+ *
+ * key usage limits enforcement
+ *
+ * David A. Mcgrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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 xtd_seq_num_t */
+#include "err.h"
+
+typedef struct key_limit_ctx_t *key_limit_t;
+
+typedef enum {
+ key_event_normal,
+ key_event_soft_limit,
+ key_event_hard_limit
+} key_event_t;
+
+err_status_t
+key_limit_set(key_limit_t key, const xtd_seq_num_t s);
+
+err_status_t
+key_limit_clone(key_limit_t original, key_limit_t *new_key);
+
+err_status_t
+key_limit_check(const key_limit_t key);
+
+key_event_t
+key_limit_update(key_limit_t key);
+
+typedef enum {
+ key_state_normal,
+ key_state_past_soft_limit,
+ key_state_expired
+} key_state_t;
+
+typedef struct key_limit_ctx_t {
+ xtd_seq_num_t num_left;
+ key_state_t state;
+} key_limit_ctx_t;
+
+#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..44f9a4a2ba
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/null_auth.h
@@ -0,0 +1,68 @@
+/*
+ * null-auth.h
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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"
+
+typedef struct {
+ char foo;
+} null_auth_ctx_t;
+
+err_status_t
+null_auth_alloc(auth_t **a, int key_len, int out_len);
+
+err_status_t
+null_auth_dealloc(auth_t *a);
+
+err_status_t
+null_auth_init(null_auth_ctx_t *state, const uint8_t *key, int key_len);
+
+err_status_t
+null_auth_compute (null_auth_ctx_t *state, uint8_t *message,
+ int msg_octets, int tag_len, uint8_t *result);
+
+
+#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..39da59a812
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/null_cipher.h
@@ -0,0 +1,80 @@
+/*
+ * null-cipher.h
+ *
+ * header file for the null cipher
+ *
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 */
+} null_cipher_ctx_t;
+
+
+/*
+ * none of these functions do anything (though future versions may keep
+ * track of bytes encrypted, number of instances, and/or other info).
+ */
+
+err_status_t
+null_cipher_init(null_cipher_ctx_t *c, const uint8_t *key, int key_len);
+
+err_status_t
+null_cipher_set_segment(null_cipher_ctx_t *c,
+ unsigned long segment_index);
+
+err_status_t
+null_cipher_encrypt(null_cipher_ctx_t *c,
+ unsigned char *buf, unsigned int *bytes_to_encr);
+
+
+err_status_t
+null_cipher_encrypt_aligned(null_cipher_ctx_t *c,
+ unsigned char *buf, int bytes_to_encr);
+
+#endif /* NULL_CIPHER_H */
diff --git a/netwerk/srtp/src/crypto/include/prng.h b/netwerk/srtp/src/crypto/include/prng.h
new file mode 100644
index 0000000000..ad0a49c228
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/prng.h
@@ -0,0 +1,89 @@
+/*
+ * prng.h
+ *
+ * pseudorandom source
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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 PRNG_H
+#define PRNG_H
+
+#include "rand_source.h" /* for rand_source_func_t definition */
+#include "aes.h" /* for aes */
+#include "aes_icm.h" /* for aes ctr */
+
+#define MAX_PRNG_OUT_LEN 0xffffffffU
+
+/*
+ * x917_prng is an ANSI X9.17-like AES-based PRNG
+ */
+
+typedef struct {
+ v128_t state; /* state data */
+ aes_expanded_key_t key; /* secret key */
+ uint32_t octet_count; /* number of octets output since last init */
+ rand_source_func_t rand; /* random source for re-initialization */
+} x917_prng_t;
+
+err_status_t
+x917_prng_init(rand_source_func_t random_source);
+
+err_status_t
+x917_prng_get_octet_string(uint8_t *dest, uint32_t len);
+
+
+/*
+ * ctr_prng is an AES-CTR based PRNG
+ */
+
+typedef struct {
+ uint32_t octet_count; /* number of octets output since last init */
+ aes_icm_ctx_t state; /* state data */
+ rand_source_func_t rand; /* random source for re-initialization */
+} ctr_prng_t;
+
+err_status_t
+ctr_prng_init(rand_source_func_t random_source);
+
+err_status_t
+ctr_prng_get_octet_string(void *dest, uint32_t len);
+
+
+#endif
diff --git a/netwerk/srtp/src/crypto/include/rand_source.h b/netwerk/srtp/src/crypto/include/rand_source.h
new file mode 100644
index 0000000000..b4c21103ac
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/rand_source.h
@@ -0,0 +1,91 @@
+/*
+ * rand_source.h
+ *
+ * implements a random source based on /dev/random
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 RAND_SOURCE
+#define RAND_SOURCE
+
+#include "err.h"
+#include "datatypes.h"
+
+err_status_t
+rand_source_init(void);
+
+/*
+ * rand_source_get_octet_string() writes a random octet string.
+ *
+ * The function call rand_source_get_octet_string(dest, len) writes
+ * len octets of random data to the location to which dest points,
+ * and returns an error code. This error code should be checked,
+ * and if a failure is reported, the data in the buffer MUST NOT
+ * be used.
+ *
+ * warning: If the return code is not checked, then non-random
+ * data may inadvertently be used.
+ *
+ * returns:
+ * - err_status_ok if no problems occured.
+ * - [other] a problem occured, and no assumptions should
+ * be made about the contents of the destination
+ * buffer.
+ */
+
+err_status_t
+rand_source_get_octet_string(void *dest, uint32_t length);
+
+err_status_t
+rand_source_deinit(void);
+
+/*
+ * function prototype for a random source function
+ *
+ * A rand_source_func_t writes num_octets at the location indicated by
+ * dest and returns err_status_ok. Any other return value indicates
+ * failure.
+ */
+
+typedef err_status_t (*rand_source_func_t)
+ (void *dest, uint32_t num_octets);
+
+#endif /* RAND_SOURCE */
diff --git a/netwerk/srtp/src/crypto/include/rdb.h b/netwerk/srtp/src/crypto/include/rdb.h
new file mode 100644
index 0000000000..07daec3d64
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/rdb.h
@@ -0,0 +1,129 @@
+/*
+ * replay-database.h
+ *
+ * interface for a replay database for packet security
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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 err_status_t */
+
+/*
+ * 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;
+} rdb_t;
+
+#define rdb_bits_in_bitmask (8*sizeof(v128_t))
+
+/*
+ * rdb init
+ *
+ * initalizes rdb
+ *
+ * returns err_status_ok on success, err_status_t_fail otherwise
+ */
+
+err_status_t
+rdb_init(rdb_t *rdb);
+
+
+/*
+ * rdb_check
+ *
+ * checks to see if index appears in rdb
+ *
+ * returns err_status_fail if the index already appears in rdb,
+ * returns err_status_ok otherwise
+ */
+
+err_status_t
+rdb_check(const rdb_t *rdb, uint32_t rdb_index);
+
+/*
+ * rdb_add_index
+ *
+ * adds index to rdb_t (and does *not* check if index appears in db)
+ *
+ * returns err_status_ok on success, err_status_fail otherwise
+ *
+ */
+
+err_status_t
+rdb_add_index(rdb_t *rdb, uint32_t rdb_index);
+
+/*
+ * the functions rdb_increment() and rdb_get_value() are for use by
+ * senders, not receivers - DO NOT use these functions on the same
+ * rdb_t upon which rdb_add_index is used!
+ */
+
+
+/*
+ * rdb_increment(db) increments the sequence number in db, if it is
+ * not too high
+ *
+ * return values:
+ *
+ * err_status_ok no problem
+ * err_status_key_expired sequence number too high
+ *
+ */
+err_status_t
+rdb_increment(rdb_t *rdb);
+
+/*
+ * rdb_get_value(db) returns the current sequence number of db
+ */
+
+uint32_t
+rdb_get_value(const rdb_t *rdb);
+
+
+#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..7b201e29b9
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/rdbx.h
@@ -0,0 +1,221 @@
+/*
+ * rdbx.h
+ *
+ * replay database with extended packet indices, using a rollover counter
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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"
+
+/* #define ROC_TEST */
+
+#ifndef ROC_TEST
+
+typedef uint16_t sequence_number_t; /* 16 bit sequence number */
+typedef uint32_t rollover_counter_t; /* 32 bit rollover counter */
+
+#else /* use small seq_num and roc datatypes for testing purposes */
+
+typedef unsigned char sequence_number_t; /* 8 bit sequence number */
+typedef uint16_t rollover_counter_t; /* 16 bit rollover counter */
+
+#endif
+
+#define seq_num_median (1 << (8*sizeof(sequence_number_t) - 1))
+#define seq_num_max (1 << (8*sizeof(sequence_number_t)))
+
+/*
+ * An xtd_seq_num_t is a 64-bit unsigned integer used as an 'extended'
+ * sequence number.
+ */
+
+typedef uint64_t xtd_seq_num_t;
+
+
+/*
+ * An 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 {
+ xtd_seq_num_t index;
+ bitvector_t bitmask;
+} rdbx_t;
+
+
+/*
+ * 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
+ */
+
+err_status_t
+rdbx_init(rdbx_t *rdbx, unsigned long ws);
+
+
+/*
+ * rdbx_dealloc(rdbx_ptr)
+ *
+ * frees memory associated with the rdbx
+ */
+
+err_status_t
+rdbx_dealloc(rdbx_t *rdbx);
+
+
+/*
+ * 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
+ */
+
+int
+rdbx_estimate_index(const rdbx_t *rdbx,
+ xtd_seq_num_t *guess,
+ sequence_number_t s);
+
+/*
+ * rdbx_check(rdbx, delta);
+ *
+ * rdbx_check(&r, delta) checks to see if the xtd_seq_num_t
+ * which is at rdbx->window_start + delta is in the rdb
+ *
+ */
+
+err_status_t
+rdbx_check(const rdbx_t *rdbx, int difference);
+
+/*
+ * replay_add_index(rdbx, delta)
+ *
+ * adds the 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.
+ */
+
+err_status_t
+rdbx_add_index(rdbx_t *rdbx, int delta);
+
+
+/*
+ * rdbx_set_roc(rdbx, roc) initalizes the 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
+ * err_status_replay_old; otherwise, err_status_ok is returned.
+ *
+ */
+
+err_status_t
+rdbx_set_roc(rdbx_t *rdbx, uint32_t roc);
+
+/*
+ * rdbx_get_roc(rdbx) returns the value of the rollover counter for
+ * the rdbx_t pointed to by rdbx
+ *
+ */
+
+xtd_seq_num_t
+rdbx_get_packet_index(const rdbx_t *rdbx);
+
+/*
+ * 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!
+ */
+
+/*
+ * rdbx_get_ws(rdbx_ptr)
+ *
+ * gets the window size which was used to initialize the rdbx
+ */
+
+unsigned long
+rdbx_get_window_size(const rdbx_t *rdbx);
+
+
+/* index_init(&pi) initializes a packet index pi (sets it to zero) */
+
+void
+index_init(xtd_seq_num_t *pi);
+
+/* index_advance(&pi, s) advances a xtd_seq_num_t forward by s */
+
+void
+index_advance(xtd_seq_num_t *pi, sequence_number_t s);
+
+
+/*
+ * index_guess(local, guess, s)
+ *
+ * given a 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
+ */
+
+int
+index_guess(const xtd_seq_num_t *local,
+ xtd_seq_num_t *guess,
+ sequence_number_t s);
+
+
+#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..e3af4d4b4b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/sha1.h
@@ -0,0 +1,108 @@
+/*
+ * 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-2006, 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
+
+#include "err.h"
+#include "datatypes.h"
+
+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 */
+} sha1_ctx_t;
+
+/*
+ * sha1(&ctx, msg, len, output) hashes the len octets starting at msg
+ * into the SHA1 context, then writes the result to the 20 octets at
+ * output
+ *
+ */
+
+void
+sha1(const uint8_t *message, int octets_in_msg, uint32_t output[5]);
+
+/*
+ * sha1_init(&ctx) initializes the SHA1 context ctx
+ *
+ * sha1_update(&ctx, msg, len) hashes the len octets starting at msg
+ * into the SHA1 context
+ *
+ * sha1_final(&ctx, output) performs the final processing of the SHA1
+ * context and writes the result to the 20 octets at output
+ *
+ */
+
+void
+sha1_init(sha1_ctx_t *ctx);
+
+void
+sha1_update(sha1_ctx_t *ctx, const uint8_t *M, int octets_in_msg);
+
+void
+sha1_final(sha1_ctx_t *ctx, uint32_t output[5]);
+
+/*
+ * The 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.
+ */
+
+/*
+ * 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
+sha1_core(const uint32_t M[16], uint32_t hash_value[5]);
+
+#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..e28b1314a8
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/stat.h
@@ -0,0 +1,69 @@
+/*
+ * stats.h
+ *
+ * interface to statistical test functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright(c) 2001-2006, 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 err_status_t */
+#include "rand_source.h" /* for rand_source_func_t definition */
+
+err_status_t
+stat_test_monobit(uint8_t *data);
+
+err_status_t
+stat_test_poker(uint8_t *data);
+
+err_status_t
+stat_test_runs(uint8_t *data);
+
+err_status_t
+stat_test_rand_source(rand_source_func_t rs);
+
+err_status_t
+stat_test_rand_source_with_repetition(rand_source_func_t source, unsigned num_trials);
+
+#endif /* STAT_H */
diff --git a/netwerk/srtp/src/crypto/include/xfm.h b/netwerk/srtp/src/crypto/include/xfm.h
new file mode 100644
index 0000000000..b43b33bc34
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/xfm.h
@@ -0,0 +1,174 @@
+/*
+ * xfm.h
+ *
+ * interface for abstract crypto transform
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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 XFM_H
+#define XFM_H
+
+#include "crypto_kernel.h"
+#include "err.h"
+
+/**
+ * @defgroup Crypto Cryptography
+ *
+ * A simple interface to an abstract cryptographic transform that
+ * provides both confidentiality and message authentication.
+ *
+ * @{
+ */
+
+/**
+ * @brief applies a crypto transform
+ *
+ * The function pointer xfm_func_t points to a function that
+ * implements a crypto transform, and provides a uniform API for
+ * accessing crypto mechanisms.
+ *
+ * @param key location of secret key
+ *
+ * @param clear data to be authenticated only
+ *
+ * @param clear_len length of data to be authenticated only
+ *
+ * @param iv location to write the Initialization Vector (IV)
+ *
+ * @param protect location of the data to be encrypted and
+ * authenticated (before the function call), and the ciphertext
+ * and authentication tag (after the call)
+ *
+ * @param protected_len location of the length of the data to be
+ * encrypted and authenticated (before the function call), and the
+ * length of the ciphertext (after the call)
+ *
+ * @param auth_tag location to write auth tag
+ */
+
+typedef err_status_t (*xfm_func_t)
+ (void *key,
+ void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *protect,
+ unsigned *protected_len,
+ void *auth_tag
+ );
+
+typedef
+err_status_t (*xfm_inv_t)
+ (void *key, /* location of secret key */
+ void *clear, /* data to be authenticated only */
+ unsigned clear_len, /* length of data to be authenticated only */
+ void *iv, /* location of iv */
+ void *opaque, /* data to be decrypted and authenticated */
+ unsigned *opaque_len, /* location of the length of data to be
+ * decrypted and authd (before and after)
+ */
+ void *auth_tag /* location of auth tag */
+ );
+
+typedef struct xfm_ctx_t {
+ xfm_func_t func;
+ xfm_inv_t inv;
+ unsigned key_len;
+ unsigned iv_len;
+ unsigned auth_tag_len;
+} xfm_ctx_t;
+
+typedef xfm_ctx_t *xfm_t;
+
+#define xfm_get_key_len(xfm) ((xfm)->key_len)
+
+#define xfm_get_iv_len(xfm) ((xfm)->iv_len)
+
+#define xfm_get_auth_tag_len(xfm) ((xfm)->auth_tag_len)
+
+
+/* cryptoalgo - 5/28 */
+
+typedef err_status_t (*cryptoalg_func_t)
+ (void *key,
+ void *clear,
+ unsigned clear_len,
+ void *iv,
+ void *opaque,
+ unsigned *opaque_len
+ );
+
+typedef
+err_status_t (*cryptoalg_inv_t)
+ (void *key, /* location of secret key */
+ void *clear, /* data to be authenticated only */
+ unsigned clear_len, /* length of data to be authenticated only */
+ void *iv, /* location of iv */
+ void *opaque, /* data to be decrypted and authenticated */
+ unsigned *opaque_len /* location of the length of data to be
+ * decrypted and authd (before and after)
+ */
+ );
+
+typedef struct cryptoalg_ctx_t {
+ cryptoalg_func_t enc;
+ cryptoalg_inv_t dec;
+ unsigned key_len;
+ unsigned iv_len;
+ unsigned auth_tag_len;
+ unsigned max_expansion;
+} cryptoalg_ctx_t;
+
+typedef cryptoalg_ctx_t *cryptoalg_t;
+
+#define cryptoalg_get_key_len(cryptoalg) ((cryptoalg)->key_len)
+
+#define cryptoalg_get_iv_len(cryptoalg) ((cryptoalg)->iv_len)
+
+#define cryptoalg_get_auth_tag_len(cryptoalg) ((cryptoalg)->auth_tag_len)
+
+
+
+/**
+ * @}
+ */
+
+#endif /* XFM_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..3384341912
--- /dev/null
+++ b/netwerk/srtp/src/crypto/kernel/alloc.c
@@ -0,0 +1,121 @@
+/*
+ * alloc.c
+ *
+ * memory allocation and deallocation
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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 "alloc.h"
+#include "crypto_kernel.h"
+
+/* the debug module for memory allocation */
+
+debug_module_t mod_alloc = {
+ 0, /* debugging is off by default */
+ "alloc" /* printable name for module */
+};
+
+/*
+ * Nota bene: the debugging statements for crypto_alloc() and
+ * 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.
+ */
+
+#ifdef SRTP_KERNEL_LINUX
+
+#include <linux/interrupt.h>
+
+void *
+crypto_alloc(size_t size) {
+ void *ptr;
+
+ ptr = kmalloc(size, in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
+
+ if (ptr) {
+ debug_print(mod_alloc, "(location: %p) allocated", ptr);
+ } else {
+ debug_print(mod_alloc, "allocation failed (asked for %d bytes)\n", size);
+ }
+
+ return ptr;
+}
+
+void
+crypto_free(void *ptr) {
+
+ debug_print(mod_alloc, "(location: %p) freed", ptr);
+
+ kfree(ptr);
+}
+
+
+#elif defined(HAVE_STDLIB_H)
+
+void *
+crypto_alloc(size_t size) {
+ void *ptr;
+
+ ptr = malloc(size);
+
+ if (ptr) {
+ debug_print(mod_alloc, "(location: %p) allocated", ptr);
+ } else {
+ debug_print(mod_alloc, "allocation failed (asked for %d bytes)\n", size);
+ }
+
+ return ptr;
+}
+
+void
+crypto_free(void *ptr) {
+
+ debug_print(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..0e969bc860
--- /dev/null
+++ b/netwerk/srtp/src/crypto/kernel/crypto_kernel.c
@@ -0,0 +1,573 @@
+/*
+ * crypto_kernel.c
+ *
+ * header for the cryptographic kernel
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 "alloc.h"
+
+#include "crypto_kernel.h"
+
+/* the debug module for the crypto_kernel */
+
+debug_module_t mod_crypto_kernel = {
+ 0, /* debugging is off by default */
+ "crypto kernel" /* printable name for module */
+};
+
+/*
+ * other debug modules that can be included in the kernel
+ */
+
+extern debug_module_t mod_auth;
+extern debug_module_t mod_cipher;
+extern debug_module_t mod_stat;
+extern debug_module_t mod_alloc;
+
+/*
+ * cipher types that can be included in the kernel
+ */
+
+extern cipher_type_t null_cipher;
+extern cipher_type_t aes_icm;
+extern cipher_type_t aes_cbc;
+
+
+/*
+ * auth func types that can be included in the kernel
+ */
+
+extern auth_type_t null_auth;
+extern auth_type_t hmac;
+
+/* crypto_kernel is a global variable, the only one of its datatype */
+
+crypto_kernel_t
+crypto_kernel = {
+ 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
+
+err_status_t
+crypto_kernel_init() {
+ err_status_t status;
+
+ /* check the security state */
+ if (crypto_kernel.state == 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 crypto_kernel_status();
+ }
+
+ /* initialize error reporting system */
+ status = err_reporting_init("crypto");
+ if (status)
+ return status;
+
+ /* load debug modules */
+ status = crypto_kernel_load_debug_module(&mod_crypto_kernel);
+ if (status)
+ return status;
+ status = crypto_kernel_load_debug_module(&mod_auth);
+ if (status)
+ return status;
+ status = crypto_kernel_load_debug_module(&mod_cipher);
+ if (status)
+ return status;
+ status = crypto_kernel_load_debug_module(&mod_stat);
+ if (status)
+ return status;
+ status = crypto_kernel_load_debug_module(&mod_alloc);
+ if (status)
+ return status;
+
+ /* initialize random number generator */
+ status = rand_source_init();
+ if (status)
+ return status;
+
+ /* run FIPS-140 statistical tests on rand_source */
+ status = stat_test_rand_source_with_repetition(rand_source_get_octet_string, MAX_RNG_TRIALS);
+ if (status)
+ return status;
+
+ /* initialize pseudorandom number generator */
+ status = ctr_prng_init(rand_source_get_octet_string);
+ if (status)
+ return status;
+
+ /* run FIPS-140 statistical tests on ctr_prng */
+ status = stat_test_rand_source_with_repetition(ctr_prng_get_octet_string, MAX_RNG_TRIALS);
+ if (status)
+ return status;
+
+ /* load cipher types */
+ status = crypto_kernel_load_cipher_type(&null_cipher, NULL_CIPHER);
+ if (status)
+ return status;
+ status = crypto_kernel_load_cipher_type(&aes_icm, AES_ICM);
+ if (status)
+ return status;
+ status = crypto_kernel_load_cipher_type(&aes_cbc, AES_CBC);
+ if (status)
+ return status;
+
+ /* load auth func types */
+ status = crypto_kernel_load_auth_type(&null_auth, NULL_AUTH);
+ if (status)
+ return status;
+ status = crypto_kernel_load_auth_type(&hmac, HMAC_SHA1);
+ if (status)
+ return status;
+
+ /* change state to secure */
+ crypto_kernel.state = crypto_kernel_state_secure;
+
+ return err_status_ok;
+}
+
+err_status_t
+crypto_kernel_status() {
+ err_status_t status;
+ kernel_cipher_type_t *ctype = crypto_kernel.cipher_type_list;
+ kernel_auth_type_t *atype = crypto_kernel.auth_type_list;
+ kernel_debug_module_t *dm = crypto_kernel.debug_module_list;
+
+ /* run FIPS-140 statistical tests on rand_source */
+ printf("testing rand_source...");
+ status = stat_test_rand_source_with_repetition(rand_source_get_octet_string, MAX_RNG_TRIALS);
+ if (status) {
+ printf("failed\n");
+ crypto_kernel.state = crypto_kernel_state_insecure;
+ return status;
+ }
+ printf("passed\n");
+
+ /* for each cipher type, describe and test */
+ while(ctype != NULL) {
+ printf("cipher: %s\n", ctype->cipher_type->description);
+ printf(" instance count: %d\n", ctype->cipher_type->ref_count);
+ printf(" self-test: ");
+ status = cipher_type_self_test(ctype->cipher_type);
+ if (status) {
+ printf("failed with error code %d\n", status);
+ exit(status);
+ }
+ printf("passed\n");
+ ctype = ctype->next;
+ }
+
+ /* for each auth type, describe and test */
+ while(atype != NULL) {
+ printf("auth func: %s\n", atype->auth_type->description);
+ printf(" instance count: %d\n", atype->auth_type->ref_count);
+ printf(" self-test: ");
+ status = auth_type_self_test(atype->auth_type);
+ if (status) {
+ printf("failed with error code %d\n", status);
+ exit(status);
+ }
+ printf("passed\n");
+ atype = atype->next;
+ }
+
+ /* describe each debug module */
+ printf("debug modules loaded:\n");
+ while (dm != NULL) {
+ printf(" %s ", dm->mod->name);
+ if (dm->mod->on)
+ printf("(on)\n");
+ else
+ printf("(off)\n");
+ dm = dm->next;
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+crypto_kernel_list_debug_modules() {
+ kernel_debug_module_t *dm = crypto_kernel.debug_module_list;
+
+ /* describe each debug module */
+ printf("debug modules loaded:\n");
+ while (dm != NULL) {
+ printf(" %s ", dm->mod->name);
+ if (dm->mod->on)
+ printf("(on)\n");
+ else
+ printf("(off)\n");
+ dm = dm->next;
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+crypto_kernel_shutdown() {
+ err_status_t status;
+
+ /*
+ * free dynamic memory used in crypto_kernel at present
+ */
+
+ /* walk down cipher type list, freeing memory */
+ while (crypto_kernel.cipher_type_list != NULL) {
+ kernel_cipher_type_t *ctype = crypto_kernel.cipher_type_list;
+ crypto_kernel.cipher_type_list = ctype->next;
+ debug_print(mod_crypto_kernel,
+ "freeing memory for cipher %s",
+ ctype->cipher_type->description);
+ crypto_free(ctype);
+ }
+
+ /* walk down authetication module list, freeing memory */
+ while (crypto_kernel.auth_type_list != NULL) {
+ kernel_auth_type_t *atype = crypto_kernel.auth_type_list;
+ crypto_kernel.auth_type_list = atype->next;
+ debug_print(mod_crypto_kernel,
+ "freeing memory for authentication %s",
+ atype->auth_type->description);
+ crypto_free(atype);
+ }
+
+ /* walk down debug module list, freeing memory */
+ while (crypto_kernel.debug_module_list != NULL) {
+ kernel_debug_module_t *kdm = crypto_kernel.debug_module_list;
+ crypto_kernel.debug_module_list = kdm->next;
+ debug_print(mod_crypto_kernel,
+ "freeing memory for debug module %s",
+ kdm->mod->name);
+ crypto_free(kdm);
+ }
+
+ /* de-initialize random number generator */ status = rand_source_deinit();
+ if (status)
+ return status;
+
+ /* return to insecure state */
+ crypto_kernel.state = crypto_kernel_state_insecure;
+
+ return err_status_ok;
+}
+
+static inline err_status_t
+crypto_kernel_do_load_cipher_type(cipher_type_t *new_ct, cipher_type_id_t id,
+ int replace) {
+ kernel_cipher_type_t *ctype, *new_ctype;
+ err_status_t status;
+
+ /* defensive coding */
+ if (new_ct == NULL)
+ return err_status_bad_param;
+
+ if (new_ct->id != id)
+ return err_status_bad_param;
+
+ /* check cipher type by running self-test */
+ status = 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 err_status_bad_param;
+ status = 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 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 = (kernel_cipher_type_t *) crypto_alloc(sizeof(kernel_cipher_type_t));
+ if (new_ctype == NULL)
+ return 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;
+
+ /* load debug module, if there is one present */
+ if (new_ct->debug != NULL)
+ crypto_kernel_load_debug_module(new_ct->debug);
+ /* we could check for errors here */
+
+ return err_status_ok;
+}
+
+err_status_t
+crypto_kernel_load_cipher_type(cipher_type_t *new_ct, cipher_type_id_t id) {
+ return crypto_kernel_do_load_cipher_type(new_ct, id, 0);
+}
+
+err_status_t
+crypto_kernel_replace_cipher_type(cipher_type_t *new_ct, cipher_type_id_t id) {
+ return crypto_kernel_do_load_cipher_type(new_ct, id, 1);
+}
+
+err_status_t
+crypto_kernel_do_load_auth_type(auth_type_t *new_at, auth_type_id_t id,
+ int replace) {
+ kernel_auth_type_t *atype, *new_atype;
+ err_status_t status;
+
+ /* defensive coding */
+ if (new_at == NULL)
+ return err_status_bad_param;
+
+ if (new_at->id != id)
+ return err_status_bad_param;
+
+ /* check auth type by running self-test */
+ status = 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 err_status_bad_param;
+ status = 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 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 = (kernel_auth_type_t *)crypto_alloc(sizeof(kernel_auth_type_t));
+ if (new_atype == NULL)
+ return 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;
+
+ /* load debug module, if there is one present */
+ if (new_at->debug != NULL)
+ crypto_kernel_load_debug_module(new_at->debug);
+ /* we could check for errors here */
+
+ return err_status_ok;
+
+}
+
+err_status_t
+crypto_kernel_load_auth_type(auth_type_t *new_at, auth_type_id_t id) {
+ return crypto_kernel_do_load_auth_type(new_at, id, 0);
+}
+
+err_status_t
+crypto_kernel_replace_auth_type(auth_type_t *new_at, auth_type_id_t id) {
+ return crypto_kernel_do_load_auth_type(new_at, id, 1);
+}
+
+
+cipher_type_t *
+crypto_kernel_get_cipher_type(cipher_type_id_t id) {
+ 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;
+}
+
+
+err_status_t
+crypto_kernel_alloc_cipher(cipher_type_id_t id,
+ cipher_pointer_t *cp,
+ int key_len) {
+ 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 != crypto_kernel_state_secure)
+ return err_status_init_fail;
+
+ ct = crypto_kernel_get_cipher_type(id);
+ if (!ct)
+ return err_status_fail;
+
+ return ((ct)->alloc(cp, key_len));
+}
+
+
+
+auth_type_t *
+crypto_kernel_get_auth_type(auth_type_id_t id) {
+ 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;
+}
+
+err_status_t
+crypto_kernel_alloc_auth(auth_type_id_t id,
+ auth_pointer_t *ap,
+ int key_len,
+ int tag_len) {
+ 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 != crypto_kernel_state_secure)
+ return err_status_init_fail;
+
+ at = crypto_kernel_get_auth_type(id);
+ if (!at)
+ return err_status_fail;
+
+ return ((at)->alloc(ap, key_len, tag_len));
+}
+
+err_status_t
+crypto_kernel_load_debug_module(debug_module_t *new_dm) {
+ kernel_debug_module_t *kdm, *new;
+
+ /* defensive coding */
+ if (new_dm == NULL)
+ return 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 err_status_bad_param;
+ kdm = kdm->next;
+ }
+
+ /* put new_dm at the head of the list */
+ /* allocate memory */
+ new = (kernel_debug_module_t *)crypto_alloc(sizeof(kernel_debug_module_t));
+ if (new == NULL)
+ return 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 err_status_ok;
+}
+
+err_status_t
+crypto_kernel_set_debug_module(char *name, int on) {
+ 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 err_status_ok;
+ }
+ kdm = kdm->next;
+ }
+
+ return err_status_fail;
+}
+
+err_status_t
+crypto_get_random(unsigned char *buffer, unsigned int length) {
+ if (crypto_kernel.state == crypto_kernel_state_secure)
+ return ctr_prng_get_octet_string(buffer, length);
+ else
+ return 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..3237965abd
--- /dev/null
+++ b/netwerk/srtp/src/crypto/kernel/err.c
@@ -0,0 +1,149 @@
+/*
+ * err.c
+ *
+ * error status reporting functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 "err.h"
+
+#ifdef ERR_REPORTING_SYSLOG
+# ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+# endif
+#endif
+
+
+/* err_level reflects the level of errors that are reported */
+
+err_reporting_level_t err_level = err_level_none;
+
+#ifdef SRTP_KERNEL_LINUX
+err_status_t
+err_reporting_init(char *ident) {
+
+ return err_status_ok;
+}
+
+#else /* SRTP_KERNEL_LINUX */
+
+/* err_file is the FILE to which errors are reported */
+
+static FILE *err_file = NULL;
+
+err_status_t
+err_reporting_init(char *ident) {
+#ifdef ERR_REPORTING_SYSLOG
+ openlog(ident, LOG_PID, LOG_AUTHPRIV);
+#endif
+
+ /*
+ * Believe it or not, openlog doesn't return an error on failure.
+ * But then, neither does the syslog() call...
+ */
+
+#ifdef ERR_REPORTING_STDOUT
+ err_file = stdout;
+#elif defined(USE_ERR_REPORTING_FILE)
+ /* open file for error reporting */
+ err_file = fopen(ERR_REPORTING_FILE, "w");
+ if (err_file == NULL)
+ return err_status_init_fail;
+#endif
+
+ return err_status_ok;
+}
+
+void
+err_report(int priority, char *format, ...) {
+ va_list args;
+
+ if ((err_reporting_level_t)priority <= err_level) {
+
+ va_start(args, format);
+ if (err_file != NULL) {
+ vfprintf(err_file, format, args);
+ /* fprintf(err_file, "\n"); */
+ }
+#ifdef ERR_REPORTING_SYSLOG
+ if (1) { /* FIXME: Make this a runtime option. */
+ int syslogpri;
+
+ switch (priority) {
+ case err_level_emergency:
+ syslogpri = LOG_EMERG;
+ break;
+ case err_level_alert:
+ syslogpri = LOG_ALERT;
+ break;
+ case err_level_critical:
+ syslogpri = LOG_CRIT;
+ break;
+ case err_level_error:
+ syslogpri = LOG_ERR;
+ break;
+ case err_level_warning:
+ syslogpri = LOG_WARNING;
+ break;
+ case err_level_notice:
+ syslogpri = LOG_NOTICE;
+ break;
+ case err_level_info:
+ syslogpri = LOG_INFO;
+ break;
+ case err_level_debug:
+ case err_level_none:
+ default:
+ syslogpri = LOG_DEBUG;
+ break;
+ }
+
+ vsyslog(syslogpri, format, args);
+ }
+#endif
+ va_end(args);
+ }
+}
+#endif /* SRTP_KERNEL_LINUX */
+
+void
+err_reporting_set_level(err_reporting_level_t lvl) {
+ err_level = lvl;
+}
diff --git a/netwerk/srtp/src/crypto/kernel/key.c b/netwerk/srtp/src/crypto/kernel/key.c
new file mode 100644
index 0000000000..9f63b22c21
--- /dev/null
+++ b/netwerk/srtp/src/crypto/kernel/key.c
@@ -0,0 +1,115 @@
+/*
+ * key.c
+ *
+ * key usage limits enforcement
+ *
+ * David A. Mcgrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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 "key.h"
+
+#define soft_limit 0x10000
+
+err_status_t
+key_limit_set(key_limit_t key, const xtd_seq_num_t s) {
+#ifdef NO_64BIT_MATH
+ if (high32(s) == 0 && low32(s) < soft_limit)
+ return err_status_bad_param;
+#else
+ if (s < soft_limit)
+ return err_status_bad_param;
+#endif
+ key->num_left = s;
+ key->state = key_state_normal;
+ return err_status_ok;
+}
+
+err_status_t
+key_limit_clone(key_limit_t original, key_limit_t *new_key) {
+ if (original == NULL)
+ return err_status_bad_param;
+ *new_key = original;
+ return err_status_ok;
+}
+
+err_status_t
+key_limit_check(const key_limit_t key) {
+ if (key->state == key_state_expired)
+ return err_status_key_expired;
+ return err_status_ok;
+}
+
+key_event_t
+key_limit_update(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 key_event_normal; /* we're above the soft limit */
+ }
+#else
+ key->num_left--;
+ if (key->num_left >= soft_limit) {
+ return key_event_normal; /* we're above the soft limit */
+ }
+#endif
+ if (key->state == key_state_normal) {
+ /* we just passed the soft limit, so change the state */
+ key->state = 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 = key_state_expired;
+ return key_event_hard_limit;
+ }
+ return 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..9e83e56abc
--- /dev/null
+++ b/netwerk/srtp/src/crypto/math/datatypes.c
@@ -0,0 +1,722 @@
+/*
+ * datatypes.c
+ *
+ * data types for finite fields and functions for input, output, and
+ * manipulation
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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 "datatypes.h"
+
+// MOZILLA: upstream code lacks |static const| and uses |int| elements, which
+// means it isn't read-only and so cannot be shared between processes, and is
+// four times bigger than necessary. Please preserve these changes until they
+// are upstreamed.
+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) {
+ // MOZILLA: upstream code here is slightly different due to the changes we've
+ // made to octet_weight's declaration above
+ 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
+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 *
+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-1;
+
+ 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 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 */
+}
+
+int
+is_hex_string(char *s) {
+ while(*s != 0)
+ if (hex_char_to_nibble(*s++) == -1)
+ return 0;
+ return 1;
+}
+
+/*
+ * 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 *
+v128_hex_string(v128_t *x) {
+ int i, j;
+
+ for (i=j=0; i < 16; i++) {
+ bit_string[j++] = nibble_to_hex_char(x->v8[i] >> 4);
+ bit_string[j++] = 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;
+ else {
+ v->word = (uint32_t*)crypto_alloc(l);
+ if (v->word == NULL) {
+ 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)
+ 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;
+ while (b < end)
+ if (*a++ != *b++)
+ return 1;
+ return 0;
+}
+
+void
+octet_string_set_to_zero(uint8_t *s, int len) {
+ uint8_t *end = s + len;
+
+ do {
+ *s = 0;
+ } while (++s < end);
+
+}
+
+
+/*
+ * From RFC 1521: The Base64 Alphabet
+ *
+ * Value Encoding Value Encoding Value Encoding Value Encoding
+ * 0 A 17 R 34 i 51 z
+ * 1 B 18 S 35 j 52 0
+ * 2 C 19 T 36 k 53 1
+ * 3 D 20 U 37 l 54 2
+ * 4 E 21 V 38 m 55 3
+ * 5 F 22 W 39 n 56 4
+ * 6 G 23 X 40 o 57 5
+ * 7 H 24 Y 41 p 58 6
+ * 8 I 25 Z 42 q 59 7
+ * 9 J 26 a 43 r 60 8
+ * 10 K 27 b 44 s 61 9
+ * 11 L 28 c 45 t 62 +
+ * 12 M 29 d 46 u 63 /
+ * 13 N 30 e 47 v
+ * 14 O 31 f 48 w (pad) =
+ * 15 P 32 g 49 x
+ * 16 Q 33 h 50 y
+ */
+
+int
+base64_char_to_sextet(uint8_t c) {
+ switch(c) {
+ case 'A':
+ return 0;
+ case 'B':
+ return 1;
+ case 'C':
+ return 2;
+ case 'D':
+ return 3;
+ case 'E':
+ return 4;
+ case 'F':
+ return 5;
+ case 'G':
+ return 6;
+ case 'H':
+ return 7;
+ case 'I':
+ return 8;
+ case 'J':
+ return 9;
+ case 'K':
+ return 10;
+ case 'L':
+ return 11;
+ case 'M':
+ return 12;
+ case 'N':
+ return 13;
+ case 'O':
+ return 14;
+ case 'P':
+ return 15;
+ case 'Q':
+ return 16;
+ case 'R':
+ return 17;
+ case 'S':
+ return 18;
+ case 'T':
+ return 19;
+ case 'U':
+ return 20;
+ case 'V':
+ return 21;
+ case 'W':
+ return 22;
+ case 'X':
+ return 23;
+ case 'Y':
+ return 24;
+ case 'Z':
+ return 25;
+ case 'a':
+ return 26;
+ case 'b':
+ return 27;
+ case 'c':
+ return 28;
+ case 'd':
+ return 29;
+ case 'e':
+ return 30;
+ case 'f':
+ return 31;
+ case 'g':
+ return 32;
+ case 'h':
+ return 33;
+ case 'i':
+ return 34;
+ case 'j':
+ return 35;
+ case 'k':
+ return 36;
+ case 'l':
+ return 37;
+ case 'm':
+ return 38;
+ case 'n':
+ return 39;
+ case 'o':
+ return 40;
+ case 'p':
+ return 41;
+ case 'q':
+ return 42;
+ case 'r':
+ return 43;
+ case 's':
+ return 44;
+ case 't':
+ return 45;
+ case 'u':
+ return 46;
+ case 'v':
+ return 47;
+ case 'w':
+ return 48;
+ case 'x':
+ return 49;
+ case 'y':
+ return 50;
+ case 'z':
+ return 51;
+ case '0':
+ return 52;
+ case '1':
+ return 53;
+ case '2':
+ return 54;
+ case '3':
+ return 55;
+ case '4':
+ return 56;
+ case '5':
+ return 57;
+ case '6':
+ return 58;
+ case '7':
+ return 59;
+ case '8':
+ return 60;
+ case '9':
+ return 61;
+ case '+':
+ return 62;
+ case '/':
+ return 63;
+ case '=':
+ return 64;
+ default:
+ break;
+ }
+ return -1;
+}
+
+/*
+ * base64_string_to_octet_string converts a hexadecimal string
+ * of length 2 * len to a raw octet string of length len
+ */
+
+int
+base64_string_to_octet_string(char *raw, char *base64, int len) {
+ uint8_t x;
+ int tmp;
+ int base64_len;
+
+ base64_len = 0;
+ while (base64_len < len) {
+ tmp = base64_char_to_sextet(base64[0]);
+ if (tmp == -1)
+ return base64_len;
+ x = (tmp << 6);
+ base64_len++;
+ tmp = base64_char_to_sextet(base64[1]);
+ if (tmp == -1)
+ return base64_len;
+ x |= (tmp & 0xffff);
+ base64_len++;
+ *raw++ = x;
+ base64 += 2;
+ }
+ return base64_len;
+}
diff --git a/netwerk/srtp/src/crypto/math/math.c b/netwerk/srtp/src/crypto/math/math.c
new file mode 100644
index 0000000000..9e3e78ed12
--- /dev/null
+++ b/netwerk/srtp/src/crypto/math/math.c
@@ -0,0 +1,802 @@
+/*
+ * math.c
+ *
+ * crypto math operations and data types
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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 "crypto_math.h"
+
+int
+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
+low_bit[256] = {
+ -1, 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
+};
+
+
+int
+high_bit[256] = {
+ -1, 0, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7
+};
+
+int
+octet_get_weight(uint8_t octet) {
+ extern int octet_weight[256];
+
+ return octet_weight[octet];
+}
+
+unsigned char
+v32_weight(v32_t a) {
+ unsigned int wt = 0;
+
+ wt += octet_weight[a.v8[0]]; /* note: endian-ness makes no difference */
+ wt += octet_weight[a.v8[1]];
+ wt += octet_weight[a.v8[2]];
+ wt += octet_weight[a.v8[3]];
+
+ return wt;
+}
+
+unsigned char
+v32_distance(v32_t x, v32_t y) {
+ x.value ^= y.value;
+ return v32_weight(x);
+}
+
+unsigned int
+v32_dot_product(v32_t a, v32_t b) {
+ a.value &= b.value;
+ return v32_weight(a) & 1;
+}
+
+/*
+ * _bit_string returns a NULL-terminated character string suitable for
+ * printing
+ */
+
+#define MAX_STRING_LENGTH 1024
+
+char bit_string[MAX_STRING_LENGTH];
+
+char *
+octet_bit_string(uint8_t x) {
+ int mask, index;
+
+ for (mask = 1, index = 0; mask < 256; mask <<= 1)
+ if ((x & mask) == 0)
+ bit_string[index++] = '0';
+ else
+ bit_string[index++] = '1';
+
+ bit_string[index++] = 0; /* NULL terminate string */
+
+ return bit_string;
+}
+
+char *
+v16_bit_string(v16_t x) {
+ int i, mask, index;
+
+ for (i = index = 0; i < 2; i++) {
+ for (mask = 1; mask < 256; mask <<= 1)
+ if ((x.v8[i] & mask) == 0)
+ bit_string[index++] = '0';
+ else
+ bit_string[index++] = '1';
+ }
+ bit_string[index++] = 0; /* NULL terminate string */
+ return bit_string;
+}
+
+char *
+v32_bit_string(v32_t x) {
+ int i, mask, index;
+
+ for (i = index = 0; i < 4; i++) {
+ for (mask = 128; mask > 0; mask >>= 1)
+ if ((x.v8[i] & mask) == 0)
+ bit_string[index++] = '0';
+ else
+ bit_string[index++] = '1';
+ }
+ bit_string[index++] = 0; /* NULL terminate string */
+ return bit_string;
+}
+
+char *
+v64_bit_string(const v64_t *x) {
+ int i, mask, index;
+
+ for (i = index = 0; i < 8; i++) {
+ for (mask = 1; mask < 256; mask <<= 1)
+ if ((x->v8[i] & mask) == 0)
+ bit_string[index++] = '0';
+ else
+ bit_string[index++] = '1';
+ }
+ bit_string[index++] = 0; /* NULL terminate string */
+ return bit_string;
+}
+
+char *
+v128_bit_string(v128_t *x) {
+ int j, index;
+ uint32_t mask;
+
+ for (j=index=0; j < 4; j++) {
+ for (mask=0x80000000; mask > 0; mask >>= 1) {
+ if (x->v32[j] & mask)
+ bit_string[index] = '1';
+ else
+ bit_string[index] = '0';
+ ++index;
+ }
+ }
+ bit_string[128] = 0; /* null terminate string */
+
+ return bit_string;
+}
+
+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];
+}
+
+char *
+octet_hex_string(uint8_t x) {
+
+ bit_string[0] = nibble_to_hex_char(x >> 4);
+ bit_string[1] = nibble_to_hex_char(x & 0xF);
+
+ bit_string[2] = 0; /* null terminate string */
+ return bit_string;
+}
+
+char *
+octet_string_hex_string(const void *str, int length) {
+ const uint8_t *s = str;
+ int i;
+
+ /* double length, since one octet takes two hex characters */
+ length *= 2;
+
+ /* truncate string if it would be too long */
+ if (length > MAX_STRING_LENGTH)
+ length = MAX_STRING_LENGTH-1;
+
+ for (i=0; i < length; i+=2) {
+ bit_string[i] = nibble_to_hex_char(*s >> 4);
+ bit_string[i+1] = nibble_to_hex_char(*s++ & 0xF);
+ }
+ bit_string[i] = 0; /* null terminate string */
+ return bit_string;
+}
+
+char *
+v16_hex_string(v16_t x) {
+ int i, j;
+
+ for (i=j=0; i < 2; i++) {
+ bit_string[j++] = nibble_to_hex_char(x.v8[i] >> 4);
+ bit_string[j++] = nibble_to_hex_char(x.v8[i] & 0xF);
+ }
+
+ bit_string[j] = 0; /* null terminate string */
+ return bit_string;
+}
+
+char *
+v32_hex_string(v32_t x) {
+ int i, j;
+
+ for (i=j=0; i < 4; i++) {
+ bit_string[j++] = nibble_to_hex_char(x.v8[i] >> 4);
+ bit_string[j++] = nibble_to_hex_char(x.v8[i] & 0xF);
+ }
+
+ bit_string[j] = 0; /* null terminate string */
+ return bit_string;
+}
+
+char *
+v64_hex_string(const v64_t *x) {
+ int i, j;
+
+ for (i=j=0; i < 8; i++) {
+ bit_string[j++] = nibble_to_hex_char(x->v8[i] >> 4);
+ bit_string[j++] = nibble_to_hex_char(x->v8[i] & 0xF);
+ }
+
+ bit_string[j] = 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++] = nibble_to_hex_char(x->v8[i] >> 4);
+ bit_string[j++] = nibble_to_hex_char(x->v8[i] & 0xF);
+ }
+
+ bit_string[j] = 0; /* null terminate string */
+ return bit_string;
+}
+
+char *
+char_to_hex_string(char *x, int num_char) {
+ int i, j;
+
+ if (num_char >= 16)
+ num_char = 16;
+ for (i=j=0; i < num_char; i++) {
+ bit_string[j++] = nibble_to_hex_char(x[i] >> 4);
+ bit_string[j++] = nibble_to_hex_char(x[i] & 0xF);
+ }
+
+ bit_string[j] = 0; /* null terminate string */
+ return bit_string;
+}
+
+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 */
+}
+
+int
+is_hex_string(char *s) {
+ while(*s != 0)
+ if (hex_char_to_nibble(*s++) == -1)
+ return 0;
+ return 1;
+}
+
+uint8_t
+hex_string_to_octet(char *s) {
+ uint8_t x;
+
+ x = (hex_char_to_nibble(s[0]) << 4)
+ | hex_char_to_nibble(s[1] & 0xFF);
+
+ return x;
+}
+
+/*
+ * 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;
+}
+
+v16_t
+hex_string_to_v16(char *s) {
+ v16_t x;
+ int i, j;
+
+ for (i=j=0; i < 4; i += 2, j++) {
+ x.v8[j] = (hex_char_to_nibble(s[i]) << 4)
+ | hex_char_to_nibble(s[i+1] & 0xFF);
+ }
+ return x;
+}
+
+v32_t
+hex_string_to_v32(char *s) {
+ v32_t x;
+ int i, j;
+
+ for (i=j=0; i < 8; i += 2, j++) {
+ x.v8[j] = (hex_char_to_nibble(s[i]) << 4)
+ | hex_char_to_nibble(s[i+1] & 0xFF);
+ }
+ return x;
+}
+
+v64_t
+hex_string_to_v64(char *s) {
+ v64_t x;
+ int i, j;
+
+ for (i=j=0; i < 16; i += 2, j++) {
+ x.v8[j] = (hex_char_to_nibble(s[i]) << 4)
+ | hex_char_to_nibble(s[i+1] & 0xFF);
+ }
+ return x;
+}
+
+v128_t
+hex_string_to_v128(char *s) {
+ v128_t x;
+ int i, j;
+
+ for (i=j=0; i < 32; i += 2, j++) {
+ x.v8[j] = (hex_char_to_nibble(s[i]) << 4)
+ | hex_char_to_nibble(s[i+1] & 0xFF);
+ }
+ return x;
+}
+
+
+
+/*
+ * the matrix A[] is stored in column format, i.e., A[i] is the ith
+ * column of the matrix
+ */
+
+uint8_t
+A_times_x_plus_b(uint8_t A[8], uint8_t x, uint8_t b) {
+ int index = 0;
+ unsigned mask;
+
+ for (mask=1; mask < 256; mask *= 2) {
+ if (x & mask)
+ b^= A[index];
+ ++index;
+ }
+
+ return b;
+}
+
+void
+v16_copy_octet_string(v16_t *x, const uint8_t s[2]) {
+ x->v8[0] = s[0];
+ x->v8[1] = s[1];
+}
+
+void
+v32_copy_octet_string(v32_t *x, const uint8_t s[4]) {
+ x->v8[0] = s[0];
+ x->v8[1] = s[1];
+ x->v8[2] = s[2];
+ x->v8[3] = s[3];
+}
+
+void
+v64_copy_octet_string(v64_t *x, const uint8_t s[8]) {
+ 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];
+}
+
+void
+v128_copy_octet_string(v128_t *x, const uint8_t s[16]) {
+ 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];
+
+}
+
+#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_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 */
+
+
+static inline void
+v128_left_shift2(v128_t *x, int num_bits) {
+ int i;
+ int word_shift = num_bits >> 5;
+ int bit_shift = num_bits & 31;
+
+ for (i=0; i < (4-word_shift); i++) {
+ x->v32[i] = x->v32[i+word_shift] << bit_shift;
+ }
+
+ for ( ; i < word_shift; i++) {
+ x->v32[i] = 0;
+ }
+
+}
+
+void
+v128_right_shift(v128_t *x, int index) {
+ const int base_index = index >> 5;
+ const int bit_index = index & 31;
+ int i, from;
+ uint32_t b;
+
+ if (index > 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 index) {
+ int i;
+ const int base_index = index >> 5;
+ const int bit_index = index & 31;
+
+ if (index > 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;
+
+}
+
+
+#if 0
+void
+v128_add(v128_t *z, v128_t *x, v128_t *y) {
+ /* integer addition modulo 2^128 */
+
+#ifdef WORDS_BIGENDIAN
+ uint64_t tmp;
+
+ tmp = x->v32[3] + y->v32[3];
+ z->v32[3] = (uint32_t) tmp;
+
+ tmp = x->v32[2] + y->v32[2] + (tmp >> 32);
+ z->v32[2] = (uint32_t) tmp;
+
+ tmp = x->v32[1] + y->v32[1] + (tmp >> 32);
+ z->v32[1] = (uint32_t) tmp;
+
+ tmp = x->v32[0] + y->v32[0] + (tmp >> 32);
+ z->v32[0] = (uint32_t) tmp;
+
+#else /* assume little endian architecture */
+ uint64_t tmp;
+
+ tmp = htonl(x->v32[3]) + htonl(y->v32[3]);
+ z->v32[3] = ntohl((uint32_t) tmp);
+
+ tmp = htonl(x->v32[2]) + htonl(y->v32[2]) + htonl(tmp >> 32);
+ z->v32[2] = ntohl((uint32_t) tmp);
+
+ tmp = htonl(x->v32[1]) + htonl(y->v32[1]) + htonl(tmp >> 32);
+ z->v32[1] = ntohl((uint32_t) tmp);
+
+ tmp = htonl(x->v32[0]) + htonl(y->v32[0]) + htonl(tmp >> 32);
+ z->v32[0] = ntohl((uint32_t) tmp);
+
+#endif /* WORDS_BIGENDIAN */
+
+}
+#endif
+
+int
+octet_string_is_eq(uint8_t *a, uint8_t *b, int len) {
+ uint8_t *end = b + len;
+ while (b < end)
+ if (*a++ != *b++)
+ return 1;
+ return 0;
+}
+
+void
+octet_string_set_to_zero(uint8_t *s, int len) {
+ uint8_t *end = s + len;
+
+ do {
+ *s = 0;
+ } while (++s < end);
+
+}
+
+
+/* functions below not yet tested! */
+
+int
+v32_low_bit(v32_t *w) {
+ int value;
+
+ value = low_bit[w->v8[0]];
+ if (value != -1)
+ return value;
+ value = low_bit[w->v8[1]];
+ if (value != -1)
+ return value + 8;
+ value = low_bit[w->v8[2]];
+ if (value != -1)
+ return value + 16;
+ value = low_bit[w->v8[3]];
+ if (value == -1)
+ return -1;
+ return value + 24;
+}
+
+/* high_bit not done yet */
+
+
+
+
+
diff --git a/netwerk/srtp/src/crypto/math/stat.c b/netwerk/srtp/src/crypto/math/stat.c
new file mode 100644
index 0000000000..8d1a5b9726
--- /dev/null
+++ b/netwerk/srtp/src/crypto/math/stat.c
@@ -0,0 +1,402 @@
+/*
+ * stats.c
+ *
+ * statistical tests for randomness (FIPS 140-2, Section 4.9)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "stat.h"
+
+debug_module_t 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
+
+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(mod_stat, "bit count: %d", ones_count);
+
+ if ((ones_count < 9725) || (ones_count > 10275))
+ return err_status_algo_fail;
+
+ return err_status_ok;
+}
+
+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(mod_stat, "poker test: %f\n", poker);
+
+ if ((poker < 2.16) || (poker > 46.17))
+ return err_status_algo_fail;
+
+ return err_status_ok;
+}
+
+
+/*
+ * runs[i] holds the number of runs of size (i-1)
+ */
+
+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(mod_stat, ">25 runs: %d", state);
+ return err_status_algo_fail;
+ }
+
+ } else if (state < 0) {
+
+ /* prefix is a gap */
+ if (state < -25) {
+ debug_print(mod_stat, ">25 gaps: %d", state);
+ return 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(mod_stat, ">25 runs (2): %d", state);
+ return 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(mod_stat, ">25 gaps (2): %d", state);
+ return err_status_algo_fail;
+ }
+
+ } else {
+
+ /* state is zero; this happens only at initialization */
+ state = -1;
+ }
+ }
+ }
+
+ /* move along to next octet */
+ data++;
+ }
+
+ if (mod_stat.on) {
+ debug_print(mod_stat, "runs test", NULL);
+ for (i=0; i < 6; i++)
+ debug_print(mod_stat, " runs[]: %d", runs[i]);
+ for (i=0; i < 6; i++)
+ debug_print(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 err_status_algo_fail;
+
+
+ return err_status_ok;
+}
+
+
+/*
+ * the function stat_test_rand_source applys the FIPS-140-2 statistical
+ * tests to the random source defined by rs
+ *
+ */
+
+#define RAND_SRC_BUF_OCTETS 50 /* this value MUST divide 2500! */
+
+err_status_t
+stat_test_rand_source(rand_source_func_t get_rand_bytes) {
+ int i;
+ double poker;
+ uint8_t *data, *data_end;
+ uint16_t f[16] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ uint8_t buffer[RAND_SRC_BUF_OCTETS];
+ err_status_t status;
+ int ones_count = 0;
+ 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;
+
+ /* counters for monobit, poker, and runs tests are initialized above */
+
+ /* main loop: fill buffer, update counters for stat tests */
+ for (i=0; i < 2500; i+=RAND_SRC_BUF_OCTETS) {
+
+ /* fill data buffer */
+ status = get_rand_bytes(buffer, RAND_SRC_BUF_OCTETS);
+ if (status) {
+ debug_print(mod_stat, "couldn't get rand bytes: %d",status);
+ return status;
+ }
+
+#if 0
+ debug_print(mod_stat, "%s",
+ octet_string_hex_string(buffer, RAND_SRC_BUF_OCTETS));
+#endif
+
+ data = buffer;
+ data_end = data + RAND_SRC_BUF_OCTETS;
+ while (data < data_end) {
+
+ /* update monobit test counter */
+ ones_count += octet_get_weight(*data);
+
+ /* update poker test counters */
+ f[*data & 0x0f]++; /* increment freq. count for low nibble */
+ f[(*data) >> 4]++; /* increment freq. count for high nibble */
+
+ /* update runs test counters */
+ /* 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(mod_stat, ">25 runs (3): %d", state);
+ return err_status_algo_fail;
+ }
+
+ } else if (state < 0) {
+
+ /* prefix is a gap */
+ if (state < -25) {
+ debug_print(mod_stat, ">25 gaps (3): %d", state);
+ return 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(mod_stat, ">25 runs (4): %d", state);
+ return 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(mod_stat, ">25 gaps (4): %d", state);
+ return err_status_algo_fail;
+ }
+
+ } else {
+
+ /* state is zero; this happens only at initialization */
+ state = -1;
+ }
+ }
+ }
+
+ /* advance data pointer */
+ data++;
+ }
+ }
+
+ /* check to see if test data is within bounds */
+
+ /* check monobit test data */
+
+ debug_print(mod_stat, "stat: bit count: %d", ones_count);
+
+ if ((ones_count < 9725) || (ones_count > 10275)) {
+ debug_print(mod_stat, "stat: failed monobit test %d", ones_count);
+ return err_status_algo_fail;
+ }
+
+ /* check poker test 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(mod_stat, "stat: poker test: %f", poker);
+
+ if ((poker < 2.16) || (poker > 46.17)) {
+ debug_print(mod_stat, "stat: failed poker test", NULL);
+ return err_status_algo_fail;
+ }
+
+ /* 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])) {
+ debug_print(mod_stat, "stat: failed run/gap test", NULL);
+ return err_status_algo_fail;
+ }
+
+ debug_print(mod_stat, "passed random stat test", NULL);
+ return err_status_ok;
+}
+
+err_status_t
+stat_test_rand_source_with_repetition(rand_source_func_t source, unsigned num_trials) {
+ unsigned int i;
+ err_status_t err = err_status_algo_fail;
+
+ for (i=0; i < num_trials; i++) {
+ err = stat_test_rand_source(source);
+ if (err == err_status_ok) {
+ return err_status_ok;
+ }
+ debug_print(mod_stat, "failed stat test (try number %d)\n", i);
+ }
+
+ return err;
+}
diff --git a/netwerk/srtp/src/crypto/replay/rdb.c b/netwerk/srtp/src/crypto/replay/rdb.c
new file mode 100644
index 0000000000..ce76f64cb4
--- /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-2006, 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 "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
+ *
+ */
+
+/* rdb_init initalizes rdb */
+
+err_status_t
+rdb_init(rdb_t *rdb) {
+ v128_set_to_zero(&rdb->bitmask);
+ rdb->window_start = 0;
+ return err_status_ok;
+}
+
+/*
+ * rdb_check checks to see if index appears in rdb
+ */
+
+err_status_t
+rdb_check(const 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 err_status_ok;
+
+ /* if the index appears before the window, its bad */
+ if (p_index < rdb->window_start)
+ return 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 err_status_replay_fail;
+
+ /* otherwise, the index is okay */
+ return err_status_ok;
+}
+
+/*
+ * rdb_add_index adds index to rdb_t (and does *not* check if
+ * index appears in db)
+ *
+ * this function should be called only after rdb_check has
+ * indicated that the index does not appear in the rdb, e.g., a mutex
+ * should protect the rdb between these calls
+ */
+
+err_status_t
+rdb_add_index(rdb_t *rdb, uint32_t p_index) {
+ int delta;
+
+ /* here we *assume* that p_index > rdb->window_start */
+
+ delta = (p_index - rdb->window_start);
+ if (delta < (int)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 err_status_ok;
+}
+
+err_status_t
+rdb_increment(rdb_t *rdb) {
+
+ if (rdb->window_start++ > 0x7fffffff)
+ return err_status_key_expired;
+ return err_status_ok;
+}
+
+uint32_t
+rdb_get_value(const 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..1a0f85a817
--- /dev/null
+++ b/netwerk/srtp/src/crypto/replay/rdbx.c
@@ -0,0 +1,346 @@
+/*
+ * rdbx.c
+ *
+ * a replay database with extended range, using a rollover counter
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "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 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 xtd_seq_num_t can be estimated by using a
+ * local xtd_seq_num_t as a basis. This is done using the function
+ * index_guess(&local, &guess, seq_from_packet). This function
+ * returns the difference of the guess and the local value. The local
+ * xtd_seq_num_t can be moved forward to the guess using the function
+ * index_advance(&guess, delta), where delta is the difference.
+ *
+ *
+ * A rdbx_t consists of a 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
+index_init(xtd_seq_num_t *pi) {
+#ifdef NO_64BIT_MATH
+ *pi = make64(0,0);
+#else
+ *pi = 0;
+#endif
+}
+
+void
+index_advance(xtd_seq_num_t *pi, 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
+}
+
+
+/*
+ * index_guess(local, guess, s)
+ *
+ * given a xtd_seq_num_t local (which represents the last
+ * known-to-be-good received 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!
+ */
+
+int
+index_guess(const xtd_seq_num_t *local,
+ xtd_seq_num_t *guess,
+ 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;
+ int difference;
+
+ if (local_seq < seq_num_median) {
+ if (s - local_seq > seq_num_median) {
+ guess_roc = local_roc - 1;
+ difference = seq_num_max - s + local_seq;
+ } else {
+ guess_roc = local_roc;
+ difference = s - local_seq;
+ }
+ } else {
+ if (local_seq - seq_num_median > s) {
+ guess_roc = local_roc+1;
+ difference = seq_num_max - local_seq + s;
+ } else {
+ difference = s - local_seq;
+ guess_roc = local_roc;
+ }
+ }
+ 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
+ *
+ */
+
+
+/*
+ * rdbx_init(&r, ws) initializes the rdbx_t pointed to by r with window size ws
+ */
+
+err_status_t
+rdbx_init(rdbx_t *rdbx, unsigned long ws) {
+ if (ws == 0)
+ return err_status_bad_param;
+
+ if (bitvector_alloc(&rdbx->bitmask, ws) != 0)
+ return err_status_alloc_fail;
+
+ index_init(&rdbx->index);
+
+ return err_status_ok;
+}
+
+/*
+ * rdbx_dealloc(&r) frees memory for the rdbx_t pointed to by r
+ */
+
+err_status_t
+rdbx_dealloc(rdbx_t *rdbx) {
+ bitvector_dealloc(&rdbx->bitmask);
+
+ return err_status_ok;
+}
+
+/*
+ * rdbx_set_roc(rdbx, roc) initalizes the 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
+ * err_status_replay_old; otherwise, err_status_ok is returned.
+ *
+ */
+
+err_status_t
+rdbx_set_roc(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 err_status_replay_old;
+
+ rdbx->index &= 0xffff; /* retain lowest 16 bits */
+ rdbx->index |= ((uint64_t)roc) << 16; /* set ROC */
+#endif
+
+ return err_status_ok;
+}
+
+/*
+ * rdbx_get_packet_index(rdbx) returns the value of the packet index
+ * for the rdbx_t pointed to by rdbx
+ *
+ */
+
+xtd_seq_num_t
+rdbx_get_packet_index(const rdbx_t *rdbx) {
+ return rdbx->index;
+}
+
+/*
+ * rdbx_get_window_size(rdbx) returns the value of the window size
+ * for the rdbx_t pointed to by rdbx
+ *
+ */
+
+unsigned long
+rdbx_get_window_size(const rdbx_t *rdbx) {
+ return bitvector_get_length(&rdbx->bitmask);
+}
+
+/*
+ * rdbx_check(&r, delta) checks to see if the xtd_seq_num_t
+ * which is at rdbx->index + delta is in the rdb
+ */
+
+err_status_t
+rdbx_check(const rdbx_t *rdbx, int delta) {
+
+ if (delta > 0) { /* if delta is positive, it's good */
+ return 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 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 err_status_replay_fail;
+ }
+ /* otherwise, the index is okay */
+
+ return err_status_ok;
+}
+
+/*
+ * rdbx_add_index adds the xtd_seq_num_t at rdbx->window_start + d 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, e.g., a mutex
+ * should protect the rdbx between these calls if need be
+ */
+
+err_status_t
+rdbx_add_index(rdbx_t *rdbx, int delta) {
+
+ if (delta > 0) {
+ /* shift forward by delta */
+ 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 err_status_ok;
+}
+
+
+
+/*
+ * 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
+ */
+
+int
+rdbx_estimate_index(const rdbx_t *rdbx,
+ xtd_seq_num_t *guess,
+ sequence_number_t s) {
+
+ /*
+ * if the sequence number and rollover counter in the rdbx are
+ * non-zero, then use the index_guess(...) function, otherwise, just
+ * set the rollover counter to zero (since the 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 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
+}
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..43c411e46b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/replay/ut_sim.c
@@ -0,0 +1,105 @@
+/*
+ * ut_sim.c
+ *
+ * an unreliable transport simulator
+ * (for testing replay databases and suchlike)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "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/rng/ctr_prng.c b/netwerk/srtp/src/crypto/rng/ctr_prng.c
new file mode 100644
index 0000000000..41d46a8f55
--- /dev/null
+++ b/netwerk/srtp/src/crypto/rng/ctr_prng.c
@@ -0,0 +1,108 @@
+/*
+ * ctr_prng.c
+ *
+ * counter mode based pseudorandom source
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 "prng.h"
+
+/* single, global prng structure */
+
+ctr_prng_t ctr_prng;
+
+err_status_t
+ctr_prng_init(rand_source_func_t random_source) {
+ uint8_t tmp_key[32];
+ err_status_t status;
+
+ /* initialize output count to zero */
+ ctr_prng.octet_count = 0;
+
+ /* set random source */
+ ctr_prng.rand = random_source;
+
+ /* initialize secret key from random source */
+ status = random_source(tmp_key, 32);
+ if (status)
+ return status;
+
+ /* initialize aes ctr context with random key */
+ status = aes_icm_context_init(&ctr_prng.state, tmp_key, 30);
+ if (status)
+ return status;
+
+ return err_status_ok;
+}
+
+err_status_t
+ctr_prng_get_octet_string(void *dest, uint32_t len) {
+ err_status_t status;
+
+ /*
+ * if we need to re-initialize the prng, do so now
+ *
+ * avoid 32-bit overflows by subtracting instead of adding
+ */
+ if (ctr_prng.octet_count > MAX_PRNG_OUT_LEN - len) {
+ status = ctr_prng_init(ctr_prng.rand);
+ if (status)
+ return status;
+ }
+ ctr_prng.octet_count += len;
+
+ /*
+ * write prng output
+ */
+ status = aes_icm_output(&ctr_prng.state, (uint8_t*)dest, len);
+ if (status)
+ return status;
+
+ return err_status_ok;
+}
+
+err_status_t
+ctr_prng_deinit(void) {
+
+ /* nothing */
+
+ return err_status_ok;
+}
diff --git a/netwerk/srtp/src/crypto/rng/prng.c b/netwerk/srtp/src/crypto/rng/prng.c
new file mode 100644
index 0000000000..62b5e11d4e
--- /dev/null
+++ b/netwerk/srtp/src/crypto/rng/prng.c
@@ -0,0 +1,180 @@
+/*
+ * prng.c
+ *
+ * pseudorandom source
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 "prng.h"
+
+/* single, global prng structure */
+
+x917_prng_t x917_prng;
+
+err_status_t
+x917_prng_init(rand_source_func_t random_source) {
+ uint8_t tmp_key[16];
+ err_status_t status;
+
+ /* initialize output count to zero */
+ x917_prng.octet_count = 0;
+
+ /* set random source */
+ x917_prng.rand = random_source;
+
+ /* initialize secret key from random source */
+ status = random_source(tmp_key, 16);
+ if (status)
+ return status;
+
+ /* expand aes key */
+ aes_expand_encryption_key(tmp_key, 16, &x917_prng.key);
+
+ /* initialize prng state from random source */
+ status = x917_prng.rand((uint8_t *)&x917_prng.state, 16);
+ if (status)
+ return status;
+
+ return err_status_ok;
+}
+
+err_status_t
+x917_prng_get_octet_string(uint8_t *dest, uint32_t len) {
+ uint32_t t;
+ v128_t buffer;
+ uint32_t i, tail_len;
+ err_status_t status;
+
+ /*
+ * if we need to re-initialize the prng, do so now
+ *
+ * avoid overflows by subtracting instead of adding
+ */
+ if (x917_prng.octet_count > MAX_PRNG_OUT_LEN - len) {
+ status = x917_prng_init(x917_prng.rand);
+ if (status)
+ return status;
+ }
+ x917_prng.octet_count += len;
+
+ /* find out the time */
+ t = (uint32_t)time(NULL);
+
+ /* loop until we have output enough data */
+ for (i=0; i < len/16; i++) {
+
+ /* exor time into state */
+ x917_prng.state.v32[0] ^= t;
+
+ /* copy state into buffer */
+ v128_copy(&buffer, &x917_prng.state);
+
+ /* apply aes to buffer */
+ aes_encrypt(&buffer, &x917_prng.key);
+
+ /* write data to output */
+ *dest++ = buffer.v8[0];
+ *dest++ = buffer.v8[1];
+ *dest++ = buffer.v8[2];
+ *dest++ = buffer.v8[3];
+ *dest++ = buffer.v8[4];
+ *dest++ = buffer.v8[5];
+ *dest++ = buffer.v8[6];
+ *dest++ = buffer.v8[7];
+ *dest++ = buffer.v8[8];
+ *dest++ = buffer.v8[9];
+ *dest++ = buffer.v8[10];
+ *dest++ = buffer.v8[11];
+ *dest++ = buffer.v8[12];
+ *dest++ = buffer.v8[13];
+ *dest++ = buffer.v8[14];
+ *dest++ = buffer.v8[15];
+
+ /* exor time into buffer */
+ buffer.v32[0] ^= t;
+
+ /* encrypt buffer */
+ aes_encrypt(&buffer, &x917_prng.key);
+
+ /* copy buffer into state */
+ v128_copy(&x917_prng.state, &buffer);
+
+ }
+
+ /* if we need to output any more octets, we'll do so now */
+ tail_len = len % 16;
+ if (tail_len) {
+
+ /* exor time into state */
+ x917_prng.state.v32[0] ^= t;
+
+ /* copy value into buffer */
+ v128_copy(&buffer, &x917_prng.state);
+
+ /* apply aes to buffer */
+ aes_encrypt(&buffer, &x917_prng.key);
+
+ /* write data to output */
+ for (i=0; i < tail_len; i++) {
+ *dest++ = buffer.v8[i];
+ }
+
+ /* now update the state one more time */
+
+ /* exor time into buffer */
+ buffer.v32[0] ^= t;
+
+ /* encrypt buffer */
+ aes_encrypt(&buffer, &x917_prng.key);
+
+ /* copy buffer into state */
+ v128_copy(&x917_prng.state, &buffer);
+
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+x917_prng_deinit(void) {
+
+ return err_status_ok;
+}
diff --git a/netwerk/srtp/src/crypto/rng/rand_linux_kernel.c b/netwerk/srtp/src/crypto/rng/rand_linux_kernel.c
new file mode 100644
index 0000000000..c51978e5e5
--- /dev/null
+++ b/netwerk/srtp/src/crypto/rng/rand_linux_kernel.c
@@ -0,0 +1,65 @@
+/*
+ * rand_linux_kernel.c
+ *
+ * implements a random source using Linux kernel functions
+ *
+ * Marcus Sundberg
+ * Ingate Systems AB
+ */
+/*
+ *
+ * Copyright(c) 2005 Ingate Systems AB
+ * 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 author(s) 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 "config.h"
+#include "rand_source.h"
+
+
+err_status_t
+rand_source_init(void) {
+ return err_status_ok;
+}
+
+err_status_t
+rand_source_get_octet_string(void *dest, uint32_t len) {
+
+ get_random_bytes(dest, len);
+
+ return err_status_ok;
+}
+
+err_status_t
+rand_source_deinit(void) {
+ return err_status_ok;
+}
diff --git a/netwerk/srtp/src/crypto/rng/rand_source.c b/netwerk/srtp/src/crypto/rng/rand_source.c
new file mode 100644
index 0000000000..1eb6fbb09c
--- /dev/null
+++ b/netwerk/srtp/src/crypto/rng/rand_source.c
@@ -0,0 +1,158 @@
+/*
+ * rand_source.c
+ *
+ * implements a random source based on /dev/random
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 "config.h"
+
+#ifdef DEV_URANDOM
+# include <fcntl.h> /* for open() */
+# include <unistd.h> /* for close() */
+#elif defined(HAVE_RAND_S)
+# define _CRT_RAND_S
+# include <stdlib.h>
+#else
+# include <stdio.h>
+#endif
+
+#include "rand_source.h"
+
+
+/*
+ * global dev_rand_fdes is file descriptor for /dev/random
+ *
+ * This variable is also used to indicate that the random source has
+ * been initialized. When this variable is set to the value of the
+ * #define RAND_SOURCE_NOT_READY, it indicates that the random source
+ * is not ready to be used. The value of the #define
+ * RAND_SOURCE_READY is for use whenever that variable is used as an
+ * indicator of the state of the random source, but not as a file
+ * descriptor.
+ */
+
+#define RAND_SOURCE_NOT_READY (-1)
+#define RAND_SOURCE_READY (17)
+
+static int dev_random_fdes = RAND_SOURCE_NOT_READY;
+
+
+err_status_t
+rand_source_init(void) {
+ if (dev_random_fdes >= 0) {
+ /* already open */
+ return err_status_ok;
+ }
+#ifdef DEV_URANDOM
+ /* open random source for reading */
+ dev_random_fdes = open(DEV_URANDOM, O_RDONLY);
+ if (dev_random_fdes < 0)
+ return err_status_init_fail;
+#elif defined(HAVE_RAND_S)
+ dev_random_fdes = RAND_SOURCE_READY;
+#else
+ /* no random source available; let the user know */
+ fprintf(stderr, "WARNING: no real random source present!\n");
+ dev_random_fdes = RAND_SOURCE_READY;
+#endif
+ return err_status_ok;
+}
+
+err_status_t
+rand_source_get_octet_string(void *dest, uint32_t len) {
+
+ /*
+ * read len octets from /dev/random to dest, and
+ * check return value to make sure enough octets were
+ * written
+ */
+#ifdef DEV_URANDOM
+ uint8_t *dst = (uint8_t *)dest;
+ while (len)
+ {
+ ssize_t num_read = read(dev_random_fdes, dst, len);
+ if (num_read <= 0 || num_read > len)
+ return err_status_fail;
+ len -= num_read;
+ dst += num_read;
+ }
+#elif 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 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 err_status_ok;
+}
+
+err_status_t
+rand_source_deinit(void) {
+ if (dev_random_fdes < 0)
+ return err_status_dealloc_fail; /* well, we haven't really failed, *
+ * but there is something wrong */
+#ifdef DEV_URANDOM
+ close(dev_random_fdes);
+#endif
+ dev_random_fdes = RAND_SOURCE_NOT_READY;
+
+ return err_status_ok;
+}
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..a5b390dd32
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/aes_calc.c
@@ -0,0 +1,154 @@
+/*
+ * aes_calc.c
+ *
+ * A simple AES calculator for generating AES encryption values
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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
+
+ */
+
+#include "aes.h"
+#include <stdio.h>
+#include <string.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];
+ aes_expanded_key_t exp_key;
+ int key_len, len;
+ int verbose;
+ 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 = aes_expand_encryption_key(key, key_len, &exp_key);
+ if (status) {
+ fprintf(stderr,
+ "error: AES key expansion failed.\n");
+ exit(1);
+ }
+
+ 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/auth_driver.c b/netwerk/srtp/src/crypto/test/auth_driver.c
new file mode 100644
index 0000000000..cd8a75dd09
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/auth_driver.c
@@ -0,0 +1,200 @@
+/*
+ * auth_driver.c
+ *
+ * a driver for auth functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 <stdlib.h> /* for xalloc() */
+#include <unistd.h> /* for getopt() */
+
+#include "auth.h"
+#include "null_auth.h"
+
+#define PRINT_DEBUG_DATA 0
+
+extern auth_type_t tmmhv2;
+
+const uint16_t msg0[9] = {
+ 0x6015, 0xf141, 0x5ba1, 0x29a0, 0xf604, 0xd1c, 0x2d9, 0xaa8a, 0x7931
+};
+
+/* key1 is for TAG_WORDS = 2 */
+
+const uint16_t key1[47] = {
+ 0xe627, 0x6a01, 0x5ea7, 0xf27a, 0xc536, 0x2192, 0x11be, 0xea35,
+ 0xdb9d, 0x63d6, 0xfa8a, 0xfc45, 0xe08b, 0xd216, 0xced2, 0x7853,
+ 0x1a82, 0x22f5, 0x90fb, 0x1c29, 0x708e, 0xd06f, 0x82c3, 0xbee6,
+ 0x4f21, 0x6f33, 0x65c0, 0xd211, 0xc25e, 0x9138, 0x4fa3, 0x7c1f,
+ 0x61ac, 0x3489, 0x2976, 0x8c19, 0x8252, 0xddbf, 0xcad3, 0xc28f,
+ 0x68d6, 0x58dd, 0x504f, 0x2bbf, 0x0278, 0x70b7, 0xcfca
+};
+
+double
+auth_bits_per_second(auth_t *h, int msg_len);
+
+
+void
+usage(char *prog_name) {
+ printf("usage: %s [ -t | -v ]\n", prog_name);
+ exit(255);
+}
+
+#define MAX_MSG_LEN 2048
+
+int
+main (int argc, char *argv[]) {
+ auth_t *a = NULL;
+ err_status_t status;
+ int i;
+ int c;
+ unsigned do_timing_test = 0;
+ unsigned do_validation = 0;
+
+ /* process input arguments */
+ while (1) {
+ c = getopt(argc, argv, "tv");
+ if (c == -1)
+ break;
+ switch (c) {
+ case 't':
+ do_timing_test = 1;
+ break;
+ case 'v':
+ do_validation = 1;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ printf("auth driver\nDavid A. McGrew\nCisco Systems, Inc.\n");
+
+ if (!do_validation && !do_timing_test)
+ usage(argv[0]);
+
+ if (do_validation) {
+ printf("running self-test for %s...", tmmhv2.description);
+ status = tmmhv2_add_big_test();
+ if (status) {
+ printf("tmmhv2_add_big_test failed with error code %d\n", status);
+ exit(status);
+ }
+ status = auth_type_self_test(&tmmhv2);
+ if (status) {
+ printf("failed with error code %d\n", status);
+ exit(status);
+ }
+ printf("passed\n");
+ }
+
+ if (do_timing_test) {
+
+ /* tmmhv2 timing test */
+ status = auth_type_alloc(&tmmhv2, &a, 94, 4);
+ if (status) {
+ fprintf(stderr, "can't allocate tmmhv2\n");
+ exit(status);
+ }
+ status = auth_init(a, (uint8_t *)key1);
+ if (status) {
+ printf("error initializaing auth function\n");
+ exit(status);
+ }
+
+ printf("timing %s (tag length %d)\n",
+ tmmhv2.description, auth_get_tag_length(a));
+ for (i=8; i <= MAX_MSG_LEN; i *= 2)
+ printf("msg len: %d\tgigabits per second: %f\n",
+ i, auth_bits_per_second(a, i) / 1E9);
+
+ status = auth_dealloc(a);
+ if (status) {
+ printf("error deallocating auth function\n");
+ exit(status);
+ }
+
+ }
+
+ return 0;
+}
+
+#define NUM_TRIALS 100000
+
+#include <time.h>
+
+double
+auth_bits_per_second(auth_t *a, int msg_len_octets) {
+ int i;
+ clock_t timer;
+ uint8_t *result;
+ int msg_len = (msg_len_octets + 1)/2;
+ uint16_t *msg_string;
+
+ /* create random message */
+ msg_string = (uint16_t *) crypto_alloc(msg_len_octets);
+ if (msg_string == NULL)
+ return 0.0; /* indicate failure */
+ for (i=0; i < msg_len; i++)
+ msg_string[i] = (uint16_t) random();
+
+ /* allocate temporary storage for authentication tag */
+ result = crypto_alloc(auth_get_tag_length(a));
+ if (result == NULL) {
+ free(msg_string);
+ return 0.0; /* indicate failure */
+ }
+
+ timer = clock();
+ for (i=0; i < NUM_TRIALS; i++) {
+ auth_compute(a, (uint8_t *)msg_string, msg_len_octets, (uint8_t *)result);
+ }
+ timer = clock() - timer;
+
+ free(msg_string);
+ free(result);
+
+ return (double) NUM_TRIALS * 8 * msg_len_octets * CLOCKS_PER_SEC / timer;
+}
+
+
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..ea41ff5a4c
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/cipher_driver.c
@@ -0,0 +1,531 @@
+/*
+ * cipher_driver.c
+ *
+ * A driver for the generic cipher type
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 <stdlib.h> /* for rand() */
+#include <string.h> /* for memset() */
+#include <unistd.h> /* for getopt() */
+#include "cipher.h"
+#include "aes_icm.h"
+#include "null_cipher.h"
+
+#define PRINT_DEBUG 0
+
+void
+cipher_driver_test_throughput(cipher_t *c);
+
+err_status_t
+cipher_driver_self_test(cipher_type_t *ct);
+
+
+/*
+ * cipher_driver_test_buffering(ct) tests the cipher's output
+ * buffering for correctness by checking the consistency of succesive
+ * calls
+ */
+
+err_status_t
+cipher_driver_test_buffering(cipher_t *c);
+
+
+/*
+ * functions for testing cipher cache thrash
+ */
+err_status_t
+cipher_driver_test_array_throughput(cipher_type_t *ct,
+ int klen, int num_cipher);
+
+void
+cipher_array_test_throughput(cipher_t *ca[], int num_cipher);
+
+uint64_t
+cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher,
+ unsigned octets_in_buffer, int num_trials);
+
+err_status_t
+cipher_array_delete(cipher_t *cipher_array[], int num_cipher);
+
+err_status_t
+cipher_array_alloc_init(cipher_t ***cipher_array, int num_ciphers,
+ 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(err_status_t s) {
+ if (s) {
+ printf("error (code %d)\n", s);
+ exit(s);
+ }
+ return;
+}
+
+/*
+ * null_cipher, aes_icm, and aes_cbc 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 cipher_type_t null_cipher;
+extern cipher_type_t aes_icm;
+extern cipher_type_t aes_cbc;
+
+int
+main(int argc, char *argv[]) {
+ cipher_t *c = NULL;
+ err_status_t status;
+ 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,
+ };
+ int q;
+ unsigned do_timing_test = 0;
+ unsigned do_validation = 0;
+ unsigned do_array_timing_test = 0;
+
+ /* process input arguments */
+ while (1) {
+ q = getopt(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(&null_cipher, 0, num_cipher);
+
+ for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
+ cipher_driver_test_array_throughput(&aes_icm, 30, num_cipher);
+
+ for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
+ cipher_driver_test_array_throughput(&aes_icm, 46, num_cipher);
+
+ for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
+ cipher_driver_test_array_throughput(&aes_cbc, 16, num_cipher);
+
+ for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8)
+ cipher_driver_test_array_throughput(&aes_cbc, 32, num_cipher);
+ }
+
+ if (do_validation) {
+ cipher_driver_self_test(&null_cipher);
+ cipher_driver_self_test(&aes_icm);
+ cipher_driver_self_test(&aes_cbc);
+ }
+
+ /* do timing and/or buffer_test on null_cipher */
+ status = cipher_type_alloc(&null_cipher, &c, 0);
+ check_status(status);
+
+ status = cipher_init(c, NULL, direction_encrypt);
+ 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 = cipher_dealloc(c);
+ check_status(status);
+
+
+ /* run the throughput test on the aes_icm cipher (128-bit key) */
+ status = cipher_type_alloc(&aes_icm, &c, 30);
+ if (status) {
+ fprintf(stderr, "error: can't allocate cipher\n");
+ exit(status);
+ }
+
+ status = cipher_init(c, test_key, direction_encrypt);
+ 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 = cipher_dealloc(c);
+ check_status(status);
+
+ /* repeat the tests with 256-bit keys */
+ status = cipher_type_alloc(&aes_icm, &c, 46);
+ if (status) {
+ fprintf(stderr, "error: can't allocate cipher\n");
+ exit(status);
+ }
+
+ status = cipher_init(c, test_key, direction_encrypt);
+ 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 = cipher_dealloc(c);
+ check_status(status);
+
+ return 0;
+}
+
+void
+cipher_driver_test_throughput(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, cipher_bits_per_second(c, i, num_trials) / 1e9);
+
+}
+
+err_status_t
+cipher_driver_self_test(cipher_type_t *ct) {
+ err_status_t status;
+
+ printf("running cipher self-test for %s...", ct->description);
+ status = cipher_type_self_test(ct);
+ if (status) {
+ printf("failed with error code %d\n", status);
+ exit(status);
+ }
+ printf("passed\n");
+
+ return err_status_ok;
+}
+
+/*
+ * cipher_driver_test_buffering(ct) tests the cipher's output
+ * buffering for correctness by checking the consistency of succesive
+ * calls
+ */
+
+err_status_t
+cipher_driver_test_buffering(cipher_t *c) {
+ int i, j, num_trials = 1000;
+ unsigned len, buflen = 1024;
+ uint8_t buffer0[buflen], buffer1[buflen], *current, *end;
+ uint8_t idx[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34
+ };
+ 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 < buflen; j++)
+ buffer0[j] = buffer1[j] = 0;
+
+ /* initialize cipher */
+ status = cipher_set_iv(c, idx);
+ if (status)
+ return status;
+
+ /* generate 'reference' value by encrypting all at once */
+ status = cipher_encrypt(c, buffer0, &buflen);
+ if (status)
+ return status;
+
+ /* re-initialize cipher */
+ status = cipher_set_iv(c, idx);
+ 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 = 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 < 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 err_status_algo_fail;
+ }
+ }
+
+ printf("passed\n");
+
+ return 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 cipher_t of type ctype
+ */
+
+err_status_t
+cipher_array_alloc_init(cipher_t ***ca, int num_ciphers,
+ cipher_type_t *ctype, int klen) {
+ int i, j;
+ err_status_t status;
+ uint8_t *key;
+ 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 = (cipher_t **) malloc(sizeof(cipher_t *) * num_ciphers);
+ if (cipher_array == NULL)
+ return err_status_alloc_fail;
+
+ /* set ca to location of cipher_array */
+ *ca = cipher_array;
+
+ /* allocate key */
+ key = crypto_alloc(klen_pad);
+ if (key == NULL) {
+ free(cipher_array);
+ return err_status_alloc_fail;
+ }
+
+ /* allocate and initialize an array of ciphers */
+ for (i=0; i < num_ciphers; i++) {
+
+ /* allocate cipher */
+ status = cipher_type_alloc(ctype, cipher_array, klen);
+ 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 = cipher_init(*cipher_array, key, direction_encrypt);
+ 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++;
+ }
+
+ crypto_free(key);
+
+ return err_status_ok;
+}
+
+err_status_t
+cipher_array_delete(cipher_t *cipher_array[], int num_cipher) {
+ int i;
+
+ for (i=0; i < num_cipher; i++) {
+ cipher_dealloc(cipher_array[i]);
+ }
+
+ free(cipher_array);
+
+ return 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(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 = crypto_alloc(octets_in_buffer+17);
+ if (enc_buf == NULL)
+ return 0; /* indicate bad parameters by returning null */
+ memset(enc_buf, 0, octets_in_buffer);
+
+ /* time repeated trials */
+ v128_set_to_zero(&nonce);
+ timer = clock();
+ for(i=0; i < num_trials; i++, nonce.v32[3] = i) {
+ /* length parameter to 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 */
+ cipher_set_iv(cipher_array[cipher_index], &nonce);
+ 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;
+
+ 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(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);
+
+}
+
+err_status_t
+cipher_driver_test_array_throughput(cipher_type_t *ct,
+ int klen, int num_cipher) {
+ cipher_t **ca = NULL;
+ 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 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..f1866524c7
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/datatypes_driver.c
@@ -0,0 +1,237 @@
+/*
+ * datatypes_driver.c
+ *
+ * a test driver for crypto/math datatypes
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 <string.h> /* for strlen() */
+#include "datatypes.h"
+
+void
+byte_order(void);
+
+void
+test_hex_string_funcs(void);
+
+void
+print_string(char *s);
+
+void
+test_bswap(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();
+
+ 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) {
+ int 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, 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));
+}
diff --git a/netwerk/srtp/src/crypto/test/env.c b/netwerk/srtp/src/crypto/test/env.c
new file mode 100644
index 0000000000..37a6e2731b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/env.c
@@ -0,0 +1,99 @@
+/*
+ * env.c
+ *
+ * prints out a brief report on the build environment
+ *
+ * David McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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;
+ char *str;
+
+#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
+
+#ifdef DEV_URANDOM
+ str = DEV_URANDOM;
+#else
+ str = "";
+#endif
+ printf("using %s as a random source\t(DEV_URANDOM == %s)\n",
+ str, str);
+ if (strcmp("", str) == 0) {
+ err_count++;
+ }
+
+ 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..8ef8a5f4b3
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/kernel_driver.c
@@ -0,0 +1,126 @@
+/*
+ * kernel_driver.c
+ *
+ * a test driver for the crypto_kernel
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 <unistd.h> /* for getopt() */
+#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[]) {
+ extern char *optarg;
+ int q;
+ int do_validation = 0;
+ err_status_t status;
+
+ if (argc == 1)
+ usage(argv[0]);
+
+ /* initialize kernel - we need to do this before anything else */
+ status = crypto_kernel_init();
+ if (status) {
+ printf("error: crypto_kernel init failed\n");
+ exit(1);
+ }
+ printf("crypto_kernel successfully initalized\n");
+
+ /* process input arguments */
+ while (1) {
+ q = getopt(argc, argv, "vd:");
+ if (q == -1)
+ break;
+ switch (q) {
+ case 'v':
+ do_validation = 1;
+ break;
+ case 'd':
+ status = crypto_kernel_set_debug_module(optarg, 1);
+ if (status) {
+ printf("error: set debug module (%s) failed\n", optarg);
+ exit(1);
+ }
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (do_validation) {
+ printf("checking crypto_kernel status...\n");
+ status = crypto_kernel_status();
+ if (status) {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("crypto_kernel passed self-tests\n");
+ }
+
+ status = crypto_kernel_shutdown();
+ if (status) {
+ printf("error: crypto_kernel shutdown failed\n");
+ exit(1);
+ }
+ printf("crypto_kernel successfully shut down\n");
+
+ return 0;
+}
+
+/*
+ * crypto_kernel_cipher_test() is a test of the cipher interface
+ * of the crypto_kernel
+ */
+
+err_status_t
+crypto_kernel_cipher_test(void) {
+
+ /* not implemented yet! */
+
+ return err_status_ok;
+}
diff --git a/netwerk/srtp/src/crypto/test/rand_gen.c b/netwerk/srtp/src/crypto/test/rand_gen.c
new file mode 100644
index 0000000000..ccea097f26
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/rand_gen.c
@@ -0,0 +1,140 @@
+/*
+ * rand_gen.c
+ *
+ * a random source (random number generator)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2006 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 <unistd.h> /* for getopt() */
+#include "crypto_kernel.h"
+
+/*
+ * MAX_PRINT_STRING_LEN is defined in datatypes.h, and is the length
+ * of the largest hexadecimal string that can be generated by the
+ * function octet_string_hex_string().
+ */
+
+#define BUF_LEN (MAX_PRINT_STRING_LEN/2)
+
+void
+usage(char *prog_name) {
+ printf("usage: %s -n <num_bytes> [-l][ -d debug_module ]*\n"
+ " -n <num> output <num> random bytes, where <num>"
+ " is between zero and %d\n"
+ " -l list the avaliable debug modules\n"
+ " -d <mod> turn on debugging module <mod>\n",
+ prog_name, BUF_LEN);
+ exit(255);
+}
+
+int
+main (int argc, char *argv[]) {
+ extern char *optarg;
+ int q;
+ int num_octets = 0;
+ unsigned do_list_mods = 0;
+ err_status_t status;
+
+ if (argc == 1)
+ usage(argv[0]);
+
+ /* initialize kernel - we need to do this before anything else */
+ status = crypto_kernel_init();
+ if (status) {
+ printf("error: crypto_kernel init failed\n");
+ exit(1);
+ }
+
+ /* process input arguments */
+ while (1) {
+ q = getopt(argc, argv, "ld:n:");
+ if (q == -1)
+ break;
+ switch (q) {
+ case 'd':
+ status = crypto_kernel_set_debug_module(optarg, 1);
+ if (status) {
+ printf("error: set debug module (%s) failed\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'l':
+ do_list_mods = 1;
+ break;
+ case 'n':
+ num_octets = atoi(optarg);
+ if (num_octets < 0 || num_octets > BUF_LEN)
+ usage(argv[0]);
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (do_list_mods) {
+ status = crypto_kernel_list_debug_modules();
+ if (status) {
+ printf("error: list of debug modules failed\n");
+ exit(1);
+ }
+ }
+
+ if (num_octets > 0) {
+ uint8_t buffer[BUF_LEN];
+
+ status = crypto_get_random(buffer, num_octets);
+ if (status) {
+ printf("error: failure in random source\n");
+ } else {
+ printf("%s\n", octet_string_hex_string(buffer, num_octets));
+ }
+ }
+
+ status = crypto_kernel_shutdown();
+ if (status) {
+ printf("error: crypto_kernel shutdown failed\n");
+ exit(1);
+ }
+
+ return 0;
+}
+
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..6036022e93
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/sha1_driver.c
@@ -0,0 +1,550 @@
+/*
+ * sha1_driver.c
+ *
+ * a test driver for SHA-1
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "sha1.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;
+
+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 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)
+ return 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)
+ return 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 err_status_ok;
+}
+
+err_status_t
+sha1_test_case_validate(const hash_test_case_t *test_case) {
+ sha1_ctx_t ctx;
+ uint32_t hash_value[5];
+
+ if (test_case == NULL)
+ return err_status_bad_param;
+
+ if (test_case->hash_len != 20)
+ return err_status_bad_param;
+ if (test_case->data_len > MAX_HASH_DATA_LEN)
+ return err_status_bad_param;
+
+ sha1_init(&ctx);
+ sha1_update(&ctx, test_case->data, test_case->data_len);
+ 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 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 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];
+};
+
+err_status_t
+sha1_add_test_cases(void) {
+ int i;
+ 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,
+ "d15976b23a1d712ad28fad04d805f572026b54dd64961fda94d5355a0cc98620cf77",
+ "aeaa40ba1717ed5439b1e6ea901b294ba500f9ad"
+ },
+ {
+ 280,
+ "09fce4d434f6bd32a44e04b848ff50ec9f642a8a85b37a264dc73f130f22838443328f",
+ "c6433791238795e34f080a5f1f1723f065463ca0"
+ },
+ {
+ 288, "f17af27d776ec82a257d8d46d2b46b639462c56984cc1be9c1222eadb8b26594a25c709d",
+ "e21e22b89c1bb944a32932e6b2a2f20d491982c3"
+ },
+ {
+ 296,
+ "b13ce635d6f8758143ffb114f2f601cb20b6276951416a2f94fbf4ad081779d79f4f195b22",
+ "575323a9661f5d28387964d2ba6ab92c17d05a8a"
+ },
+ {
+ 304,
+ "5498793f60916ff1c918dde572cdea76da8629ba4ead6d065de3dfb48de94d234cc1c5002910",
+ "feb44494af72f245bfe68e86c4d7986d57c11db7"
+ },
+ {
+ 312,
+ "498a1e0b39fa49582ae688cd715c86fbaf8a81b8b11b4d1594c49c902d197c8ba8a621fd6e3be5",
+ "cff2290b3648ba2831b98dde436a72f9ebf51eee"
+ },
+ {
+ 320,
+ "3a36ae71521f9af628b3e34dcb0d4513f84c78ee49f10416a98857150b8b15cb5c83afb4b570376e",
+ "9b4efe9d27b965905b0c3dab67b8d7c9ebacd56c"
+ },
+ {
+ 328,
+ "dcc76b40ae0ea3ba253e92ac50fcde791662c5b6c948538cffc2d95e9de99cac34dfca38910db2678f",
+ "afedb0ff156205bcd831cbdbda43db8b0588c113"
+ },
+ {
+ 336,
+ "5b5ec6ec4fd3ad9c4906f65c747fd4233c11a1736b6b228b92e90cddabb0c7c2fcf9716d3fad261dff33",
+ "8deb1e858f88293a5e5e4d521a34b2a4efa70fc4"
+ },
+ {
+ 344,
+ "df48a37b29b1d6de4e94717d60cdb4293fcf170bba388bddf7a9035a15d433f20fd697c3e4c8b8c5f590ab",
+ "95cbdac0f74afa69cebd0e5c7defbc6faf0cbeaf"
+ },
+ {
+ 352,
+ "1f179b3b82250a65e1b0aee949e218e2f45c7a8dbfd6ba08de05c55acfc226b48c68d7f7057e5675cd96fcfc",
+ "f0307bcb92842e5ae0cd4f4f14f3df7f877fbef2"
+ },
+ {
+ 360,
+ "ee3d72da3a44d971578972a8e6780ce64941267e0f7d0179b214fa97855e1790e888e09fbe3a70412176cb3b54",
+ "7b13bb0dbf14964bd63b133ac85e22100542ef55"
+ },
+ {
+ 368,
+ "d4d4c7843d312b30f610b3682254c8be96d5f6684503f8fbfbcd15774fc1b084d3741afb8d24aaa8ab9c104f7258",
+ "c314d2b6cf439be678d2a74e890d96cfac1c02ed"
+ },
+ {
+ 376,
+ "32c094944f5936a190a0877fb9178a7bf60ceae36fd530671c5b38c5dbd5e6a6c0d615c2ac8ad04b213cc589541cf6",
+ "4d0be361e410b47a9d67d8ce0bb6a8e01c53c078"
+ },
+ {
+ 384,
+ "e5d3180c14bf27a5409fa12b104a8fd7e9639609bfde6ee82bbf9648be2546d29688a65e2e3f3da47a45ac14343c9c02",
+ "e5353431ffae097f675cbf498869f6fbb6e1c9f2"
+ },
+ {
+ 392,
+ "e7b6e4b69f724327e41e1188a37f4fe38b1dba19cbf5a7311d6e32f1038e97ab506ee05aebebc1eed09fc0e357109818b9",
+ "b8720a7068a085c018ab18961de2765aa6cd9ac4"
+ },
+ {
+ 400,
+ "bc880cb83b8ac68ef2fedc2da95e7677ce2aa18b0e2d8b322701f67af7d5e7a0d96e9e33326ccb7747cfff0852b961bfd475",
+ "b0732181568543ba85f2b6da602b4b065d9931aa"
+ },
+ {
+ 408,
+ "235ea9c2ba7af25400f2e98a47a291b0bccdaad63faa2475721fda5510cc7dad814bce8dabb611790a6abe56030b798b75c944",
+ "9c22674cf3222c3ba921672694aafee4ce67b96b"
+ },
+ {
+ 416,
+ "07e3e29fed63104b8410f323b975fd9fba53f636af8c4e68a53fb202ca35dd9ee07cb169ec5186292e44c27e5696a967f5e67709",
+ "d128335f4cecca9066cdae08958ce656ff0b4cfc"
+ },
+ {
+ 424,
+ "65d2a1dd60a517eb27bfbf530cf6a5458f9d5f4730058bd9814379547f34241822bf67e6335a6d8b5ed06abf8841884c636a25733f",
+ "0b67c57ac578de88a2ae055caeaec8bb9b0085a0"
+ },
+ {
+ 432,
+ "dcc86b3bd461615bab739d8daafac231c0f462e819ad29f9f14058f3ab5b75941d4241ea2f17ebb8a458831b37a9b16dead4a76a9b0e",
+ "c766f912a89d4ccda88e0cce6a713ef5f178b596"
+ },
+ {
+ 440,
+ "4627d54f0568dc126b62a8c35fb46a9ac5024400f2995e51635636e1afc4373dbb848eb32df23914230560b82477e9c3572647a7f2bb92",
+ "9aa3925a9dcb177b15ccff9b78e70cf344858779"
+ },
+ {
+ 448,
+ "ba531affd4381168ef24d8b275a84d9254c7f5cc55fded53aa8024b2c5c5c8aa7146fe1d1b83d62b70467e9a2e2cb67b3361830adbab28d7",
+ "4811fa30042fc076acf37c8e2274d025307e5943"
+ },
+ {
+ 456,
+ "8764dcbcf89dcf4282eb644e3d568bdccb4b13508bfa7bfe0ffc05efd1390be22109969262992d377691eb4f77f3d59ea8466a74abf57b2ef4",
+ "6743018450c9730761ee2b130df9b91c1e118150"
+ },
+ {
+ 464,
+ "497d9df9ddb554f3d17870b1a31986c1be277bc44feff713544217a9f579623d18b5ffae306c25a45521d2759a72c0459b58957255ab592f3be4",
+ "71ad4a19d37d92a5e6ef3694ddbeb5aa61ada645"
+ },
+ {
+ 472,
+ "72c3c2e065aefa8d9f7a65229e818176eef05da83f835107ba90ec2e95472e73e538f783b416c04654ba8909f26a12db6e5c4e376b7615e4a25819",
+ "a7d9dc68dacefb7d6116186048cb355cc548e11d"
+ },
+ {
+ 480,
+ "7cc9894454d0055ab5069a33984e2f712bef7e3124960d33559f5f3b81906bb66fe64da13c153ca7f5cabc89667314c32c01036d12ecaf5f9a78de98",
+ "142e429f0522ba5abf5131fa81df82d355b96909"
+ },
+ {
+ 488,
+ "74e8404d5a453c5f4d306f2cfa338ca65501c840ddab3fb82117933483afd6913c56aaf8a0a0a6b2a342fc3d9dc7599f4a850dfa15d06c61966d74ea59",
+ "ef72db70dcbcab991e9637976c6faf00d22caae9"
+ },
+ {
+ 496,
+ "46fe5ed326c8fe376fcc92dc9e2714e2240d3253b105adfbb256ff7a19bc40975c604ad7c0071c4fd78a7cb64786e1bece548fa4833c04065fe593f6fb10",
+ "f220a7457f4588d639dc21407c942e9843f8e26b"
+ },
+ {
+ 504,
+ "836dfa2524d621cf07c3d2908835de859e549d35030433c796b81272fd8bc0348e8ddbc7705a5ad1fdf2155b6bc48884ac0cd376925f069a37849c089c8645",
+ "ddd2117b6e309c233ede85f962a0c2fc215e5c69"
+ },
+ {
+ 512,
+ "7e3a4c325cb9c52b88387f93d01ae86d42098f5efa7f9457388b5e74b6d28b2438d42d8b64703324d4aa25ab6aad153ae30cd2b2af4d5e5c00a8a2d0220c6116",
+ "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 err_status_ok;
+}
+
+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 err_status_ok;
+}
+
+
+
+err_status_t
+sha1_validate(void) {
+ hash_test_case_t *test_case;
+ 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 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 err_status_ok;
+}
+
+
+
+int
+main (void) {
+ 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..4ff2b14261
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/stat_driver.c
@@ -0,0 +1,174 @@
+/*
+ * stat-driver.c
+ *
+ * test driver for the stat_test functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 "err.h"
+#include "stat.h"
+
+#include "cipher.h"
+
+typedef struct {
+ void *state;
+} random_source_t;
+
+err_status_t
+random_source_alloc(void);
+
+void
+err_check(err_status_t s) {
+ if (s) {
+ printf("error (code %d)\n", s);
+ exit(1);
+ }
+}
+
+int
+main (int argc, char *argv[]) {
+ uint8_t buffer[2500];
+ unsigned int buf_len = 2500;
+ int i, j;
+ extern cipher_type_t aes_icm;
+ cipher_t *c;
+ 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
+ };
+ v128_t nonce;
+ int num_trials = 500;
+ int num_fail;
+
+ printf("statistical tests driver\n");
+
+ 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(cipher_type_alloc(&aes_icm, &c, 30));
+ err_check(cipher_init(c, key, direction_encrypt));
+ err_check(cipher_set_iv(c, &nonce));
+ err_check(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(cipher_set_iv(c, &nonce));
+ err_check(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(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(cipher_type_alloc(&aes_icm, &c, 46));
+ err_check(cipher_init(c, key, direction_encrypt));
+ err_check(cipher_set_iv(c, &nonce));
+ err_check(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(cipher_set_iv(c, &nonce));
+ err_check(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(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..b0d888bac9
--- /dev/null
+++ b/netwerk/srtp/src/include/ekt.h
@@ -0,0 +1,201 @@
+/*
+ * ekt.h
+ *
+ * interface to Encrypted Key Transport for SRTP
+ *
+ * David McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2005 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 EKT_H
+#define EKT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "srtp_priv.h"
+
+#define EKT_CIPHER_DEFAULT 1
+#define EKT_CIPHER_AES_128_ECB 1
+#define EKT_CIPHER_AES_192_KEY_WRAP 2
+#define EKT_CIPHER_AES_256_KEY_WRAP 3
+
+typedef uint16_t ekt_spi_t;
+
+
+unsigned
+ekt_octets_after_base_tag(ekt_stream_t ekt);
+
+/*
+ * an srtp_policy_t structure can contain a pointer to an
+ * 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 ekt_policy_ctx_t {
+ ekt_spi_t spi; /* security parameter index */
+ uint8_t ekt_cipher_type;
+ uint8_t *ekt_key;
+ struct ekt_policy_ctx_t *next_ekt_policy;
+} ekt_policy_ctx_t;
+
+
+/*
+ * an ekt_data_t structure holds the data corresponding to an ekt key,
+ * spi, and so on
+ */
+
+typedef struct ekt_data_t {
+ ekt_spi_t spi;
+ uint8_t ekt_cipher_type;
+ aes_expanded_key_t ekt_enc_key;
+ aes_expanded_key_t ekt_dec_key;
+ struct ekt_data_t *next_ekt_data;
+} ekt_data_t;
+
+/*
+ * an srtp_stream_ctx_t can contain an ekt_stream_ctx_t
+ *
+ * an ekt_stream_ctx_t structure holds all of the EKT information for
+ * a specific SRTP stream
+ */
+
+typedef struct ekt_stream_ctx_t {
+ ekt_data_t *data;
+ uint16_t isn; /* initial sequence number */
+ uint8_t encrypted_master_key[SRTP_MAX_KEY_LEN];
+} ekt_stream_ctx_t;
+
+
+
+err_status_t
+ekt_alloc(ekt_stream_t *stream_data, ekt_policy_t policy);
+
+err_status_t
+ekt_stream_init(ekt_stream_t e,
+ ekt_spi_t spi,
+ void *ekt_key,
+ unsigned ekt_cipher_type);
+
+err_status_t
+ekt_stream_init_from_policy(ekt_stream_t e, ekt_policy_t p);
+
+
+
+err_status_t
+srtp_stream_init_from_ekt(srtp_stream_t stream,
+ const void *srtcp_hdr,
+ unsigned pkt_octet_len);
+
+
+void
+ekt_write_data(ekt_stream_t ekt,
+ uint8_t *base_tag,
+ unsigned base_tag_len,
+ int *packet_len,
+ 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
+ */
+
+err_status_t
+ekt_tag_verification_preproces(uint8_t *pkt_tag,
+ uint8_t *pkt_tag_copy,
+ unsigned tag_len);
+
+err_status_t
+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.
+ *
+ */
+
+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 */
+
+err_status_t
+srtcp_auth_tag_generation_postprocess(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* 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..2a6ece34eb
--- /dev/null
+++ b/netwerk/srtp/src/include/getopt_s.h
@@ -0,0 +1,60 @@
+/*
+ * 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-2006 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
+
+/*
+ * 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 */
+
+#endif /* GETOPT_S_H */
diff --git a/netwerk/srtp/src/include/rtp.h b/netwerk/srtp/src/include/rtp.h
new file mode 100644
index 0000000000..0e0119cf7b
--- /dev/null
+++ b/netwerk/srtp/src/include/rtp.h
@@ -0,0 +1,139 @@
+/*
+ * 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-2006, 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_H
+#define RTP_H
+
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#elif defined HAVE_WINSOCK2_H
+# include <winsock2.h>
+#endif
+
+#include "srtp.h"
+
+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 */
+ 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 */
+ 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);
+
+
+/*
+ * 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
+
+
+#endif /* RTP_H */
diff --git a/netwerk/srtp/src/include/rtp_priv.h b/netwerk/srtp/src/include/rtp_priv.h
new file mode 100644
index 0000000000..1421386673
--- /dev/null
+++ b/netwerk/srtp/src/include/rtp_priv.h
@@ -0,0 +1,74 @@
+/*
+ * rtp_priv.h
+ *
+ * private, internal header file for RTP
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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_PRIV_H
+#define RTP_PRIV_H
+
+#include "srtp_priv.h"
+#include "rtp.h"
+
+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;
+
+
+#endif /* RTP_PRIV_H */
diff --git a/netwerk/srtp/src/include/srtp.h b/netwerk/srtp/src/include/srtp.h
new file mode 100644
index 0000000000..eb95e7d477
--- /dev/null
+++ b/netwerk/srtp/src/include/srtp.h
@@ -0,0 +1,1006 @@
+/*
+ * srtp.h
+ *
+ * interface to libsrtp
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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_H
+#define SRTP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "crypto_kernel.h"
+
+/**
+ * @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 12
+
+/**
+ * SRTP_MAX_TRAILER_LEN is the maximum length of the SRTP trailer
+ * (authentication tag and MKI) supported by libSRTP. This value is
+ * the maximum 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
+
+/*
+ * nota bene: since libSRTP doesn't support the use of the MKI, the
+ * SRTP_MAX_TRAILER_LEN value is just the maximum tag length
+ */
+
+/**
+ * @brief sec_serv_t describes a set of security services.
+ *
+ * A 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 */
+} sec_serv_t;
+
+/**
+ * @brief crypto_policy_t describes a particular crypto policy that
+ * can be applied to an SRTP stream.
+ *
+ * A 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 crypto_policy_t {
+ 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. */
+ 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. */
+ sec_serv_t sec_serv; /**< The flag indicating the security
+ * services to be applied. */
+} crypto_policy_t;
+
+
+/**
+ * @brief ssrc_type_t describes the type of an SSRC.
+ *
+ * An 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()) */
+} ssrc_type_t;
+
+/**
+ * @brief An ssrc_t represents a particular SSRC value, or a `wildcard' SSRC.
+ *
+ * An 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 {
+ ssrc_type_t type; /**< The type of this particular SSRC */
+ unsigned int value; /**< The value of this SSRC, if it is not a wildcard */
+} ssrc_t;
+
+
+/**
+ * @brief points to an EKT policy
+ */
+typedef struct ekt_policy_ctx_t *ekt_policy_t;
+
+
+/**
+ * @brief points to EKT stream data
+ */
+typedef struct ekt_stream_ctx_t *ekt_stream_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 {
+ 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.
+ */
+ crypto_policy_t rtp; /**< SRTP crypto policy. */
+ crypto_policy_t rtcp; /**< SRTCP crypto policy. */
+ unsigned char *key; /**< Pointer to the SRTP master key for
+ * this stream. */
+ 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!) */
+ 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 struct srtp_ctx_t *srtp_t;
+
+
+/**
+ * @brief An srtp_stream_t points to an SRTP stream structure.
+ *
+ * The typedef srtp_stream_t is a pointer to a structure that
+ * represents an SRTP stream. This datatype is intentionally
+ * opaque in order to separate the interface from the implementation.
+ *
+ * An SRTP stream consists of all of the traffic sent to an SRTP
+ * session by a single participant. A session can be viewed as
+ * a set of streams.
+ *
+ */
+typedef struct srtp_stream_ctx_t *srtp_stream_t;
+
+
+
+/**
+ * @brief srtp_init() initializes the srtp library.
+ *
+ * @warning This function @b must be called before any other srtp
+ * functions.
+ */
+
+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.
+ */
+
+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 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.
+ *
+ * @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 err_status_ok was returned.
+ * Otherwise, the value of the data to which it points is undefined.
+ *
+ * @return
+ * - err_status_ok no problems
+ * - err_status_replay_fail rtp sequence number was non-increasing
+ * - @e other failure in cryptographic mechanisms
+ */
+
+err_status_t
+srtp_protect(srtp_t ctx, void *rtp_hdr, int *len_ptr);
+
+/**
+ * @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
+ * 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 a pointer to the srtp_t 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 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 err_status_ok was returned.
+ * Otherwise, the value of the data to which it points is undefined.
+ *
+ * @return
+ * - err_status_ok if the RTP packet is valid.
+ * - err_status_auth_fail if the SRTP packet failed the message
+ * authentication check.
+ * - 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.
+ *
+ */
+
+err_status_t
+srtp_unprotect(srtp_t ctx, void *srtp_hdr, int *len_ptr);
+
+
+/**
+ * @brief srtp_create() allocates and initializes an SRTP session.
+
+ * The function call srtp_create(session, policy, key) allocates and
+ * initializes an SRTP session context, applying the given policy and
+ * key.
+ *
+ * @param session is 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
+ * - err_status_ok if creation succeded.
+ * - err_status_alloc_fail if allocation failed.
+ * - err_status_init_fail if initialization failed.
+ */
+
+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:
+ * - err_status_ok if stream creation succeded.
+ * - err_status_alloc_fail if stream allocation failed
+ * - err_status_init_fail if stream initialization failed.
+ */
+
+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.
+ *
+ * @warning Wildcard SSRC values cannot be removed from a
+ * session.
+ *
+ * @return
+ * - err_status_ok if the stream deallocation succeded.
+ * - [other] otherwise.
+ *
+ */
+
+err_status_t
+srtp_remove_stream(srtp_t session, unsigned int ssrc);
+
+/**
+ * @brief 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
+crypto_policy_set_rtp_default(crypto_policy_t *p);
+
+/**
+ * @brief 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 crypto_policy_set_rtcp_default(&p) sets the
+ * 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 crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+
+void
+crypto_policy_set_rtcp_default(crypto_policy_t *p);
+
+/**
+ * @brief 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 crypto_policy_set_aes_cm_128_hmac_sha1_80() is a
+ * synonym for crypto_policy_set_rtp_default(). It conforms to the
+ * naming convention used in RFC 4568 (SDP Security Descriptions for
+ * Media Streams).
+ *
+ * @return void.
+ *
+ */
+
+#define crypto_policy_set_aes_cm_128_hmac_sha1_80(p) crypto_policy_set_rtp_default(p)
+
+
+/**
+ * @brief 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 crypto_policy_set_aes_cm_128_hmac_sha1_32(&p)
+ * sets the 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 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
+crypto_policy_set_aes_cm_128_hmac_sha1_32(crypto_policy_t *p);
+
+
+
+/**
+ * @brief 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 crypto_policy_set_aes_cm_128_null_auth(&p) sets
+ * the 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 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
+crypto_policy_set_aes_cm_128_null_auth(crypto_policy_t *p);
+
+
+/**
+ * @brief 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 crypto_policy_set_null_cipher_hmac_sha1_80(&p)
+ * sets the 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 crypto_policy_t datatype.
+ *
+ * @warning This policy is NOT RECOMMENDED for SRTP unless there is a
+ * requirement to forego encryption.
+ *
+ * @return void.
+ *
+ */
+
+void
+crypto_policy_set_null_cipher_hmac_sha1_80(crypto_policy_t *p);
+
+
+/**
+ * @brief 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 crypto_policy_set_aes_cm_256_hmac_sha1_80(&p)
+ * sets the crypto_policy_t at location p to use policy
+ * AES_CM_256_HMAC_SHA1_80 as defined in
+ * draft-ietf-avt-srtp-big-aes-03.txt. 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 crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+
+void crypto_policy_set_aes_cm_256_hmac_sha1_80(crypto_policy_t *p);
+
+
+/**
+ * @brief 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 crypto_policy_set_aes_cm_256_hmac_sha1_32(&p)
+ * sets the crypto_policy_t at location p to use policy
+ * AES_CM_256_HMAC_SHA1_32 as defined in
+ * draft-ietf-avt-srtp-big-aes-03.txt. 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 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
+crypto_policy_set_aes_cm_256_hmac_sha1_32(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
+ * - err_status_ok if there no problems.
+ * - err_status_dealloc_fail a memory deallocation failure occured.
+ */
+
+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 in the DTLS-SRTP draft.
+ */
+
+typedef enum {
+ srtp_profile_reserved = 0,
+ srtp_profile_aes128_cm_sha1_80 = 1,
+ srtp_profile_aes128_cm_sha1_32 = 2,
+ srtp_profile_aes256_cm_sha1_80 = 3,
+ srtp_profile_aes256_cm_sha1_32 = 4,
+ srtp_profile_null_sha1_80 = 5,
+ srtp_profile_null_sha1_32 = 6,
+} srtp_profile_t;
+
+
+/**
+ * @brief 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 p is a pointer to the policy structure to be set
+ *
+ * The function call crypto_policy_set_rtp_default(&policy, profile)
+ * sets the 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 crypto_policy_t datatype.
+ *
+ * @return values
+ * - err_status_ok no problems were encountered
+ * - err_status_bad_param the profile is not supported
+ *
+ */
+err_status_t
+crypto_policy_set_from_profile_for_rtp(crypto_policy_t *policy,
+ srtp_profile_t profile);
+
+
+
+
+/**
+ * @brief 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 p is a pointer to the policy structure to be set
+ *
+ * The function call crypto_policy_set_rtcp_default(&policy, profile)
+ * sets the 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 crypto_policy_t datatype.
+ *
+ * @return values
+ * - err_status_ok no problems were encountered
+ * - err_status_bad_param the profile is not supported
+ *
+ */
+err_status_t
+crypto_policy_set_from_profile_for_rtcp(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 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
+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 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.
+ *
+ * @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 err_status_ok
+ * was returned. Otherwise, the value of the data to which it points
+ * is undefined.
+ *
+ * @return
+ * - err_status_ok if there were no problems.
+ * - [other] if there was a failure in
+ * the cryptographic mechanisms.
+ */
+
+
+err_status_t
+srtp_protect_rtcp(srtp_t ctx, void *rtcp_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 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 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 err_status_ok was
+ * returned. Otherwise, the value of the data to which it points is
+ * undefined.
+ *
+ * @return
+ * - err_status_ok if the RTCP packet is valid.
+ * - err_status_auth_fail if the SRTCP packet failed the message
+ * authentication check.
+ * - 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.
+ *
+ */
+
+err_status_t
+srtp_unprotect_rtcp(srtp_t ctx, void *srtcp_hdr, int *pkt_octet_len);
+
+/**
+ * @}
+ */
+
+/**
+ * @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_stream_dealloc()), 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. */
+ srtp_stream_t stream; /**< 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.
+ */
+
+err_status_t
+srtp_install_event_handler(srtp_event_handler_func_t func);
+
+/**
+ * @}
+ */
+/* 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_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..cf2274eb65
--- /dev/null
+++ b/netwerk/srtp/src/include/srtp_priv.h
@@ -0,0 +1,256 @@
+/*
+ * srtp_priv.h
+ *
+ * private internal data structures and functions for libSRTP
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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
+
+#include "srtp.h"
+#include "rdbx.h"
+#include "rdb.h"
+#include "integers.h"
+
+/*
+ * an srtp_hdr_t represents the srtp header
+ *
+ * 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
+
+/*
+ * 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.
+ */
+
+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 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 version:2; /* protocol version */
+ unsigned int p:1; /* padding flag */
+ unsigned int count:5; /* varies by packet type */
+ unsigned int pt:8; /* payload type */
+ uint16_t length; /* len of uint32s of packet less header */
+} rtcp_common_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
+
+
+/*
+ * 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.
+ */
+
+
+err_status_t
+srtp_stream_init_keys(srtp_stream_t srtp, const void *key);
+
+/*
+ * srtp_stream_init(s, p) initializes the srtp_stream_t s to
+ * use the policy at the location p
+ */
+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;
+
+/*
+ * 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
+ * cipher_t and auth_t pointers will point to the same structures
+ */
+
+typedef struct srtp_stream_ctx_t {
+ uint32_t ssrc;
+ cipher_t *rtp_cipher;
+ auth_t *rtp_auth;
+ rdbx_t rtp_rdbx;
+ sec_serv_t rtp_services;
+ cipher_t *rtcp_cipher;
+ auth_t *rtcp_auth;
+ rdb_t rtcp_rdb;
+ sec_serv_t rtcp_services;
+ key_limit_ctx_t *limit;
+ direction_t direction;
+ int allow_repeat_tx;
+ ekt_stream_t ekt;
+ struct srtp_stream_ctx_t *next; /* linked list of streams */
+} srtp_stream_ctx_t;
+
+
+/*
+ * an srtp_ctx_t holds a stream list and a service description
+ */
+
+typedef struct srtp_ctx_t {
+ srtp_stream_ctx_t *stream_list; /* linked list of streams */
+ srtp_stream_ctx_t *stream_template; /* act as template for other streams */
+} srtp_ctx_t;
+
+
+
+/*
+ * 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.stream = strm; \
+ data.event = evnt; \
+ srtp_event_handler(&data); \
+}
+
+
+#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..c25feeb654
--- /dev/null
+++ b/netwerk/srtp/src/include/ut_sim.h
@@ -0,0 +1,80 @@
+/*
+ * ut-sim.h
+ *
+ * an unreliable transport simulator
+ * (for testing replay databases and suchlike)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2006, 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 */
+
+#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);
+
+
+#endif /* UT_SIM_H */
diff --git a/netwerk/srtp/src/moz.build b/netwerk/srtp/src/moz.build
new file mode 100644
index 0000000000..3e871702f1
--- /dev/null
+++ b/netwerk/srtp/src/moz.build
@@ -0,0 +1,69 @@
+# -*- 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.c',
+ 'crypto/cipher/aes_cbc.c',
+ 'crypto/cipher/aes_icm.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',
+ 'crypto/rng/ctr_prng.c',
+ 'crypto/rng/prng.c',
+ 'crypto/rng/rand_source.c',
+ 'srtp/ekt.c',
+ 'srtp/srtp.c',
+]
+
+Library('nksrtp_s')
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ 'crypto/include',
+ 'include',
+]
+
+# 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_UINT32_T', 'HAVE_UINT64_T'):
+ DEFINES[var] = 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'):
+ 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'
+
+if CONFIG['GNU_CC']:
+ CFLAGS += ['-std=gnu99']
diff --git a/netwerk/srtp/src/srtp/ekt.c b/netwerk/srtp/src/srtp/ekt.c
new file mode 100644
index 0000000000..67f6a35c64
--- /dev/null
+++ b/netwerk/srtp/src/srtp/ekt.c
@@ -0,0 +1,276 @@
+/*
+ * ekt.c
+ *
+ * Encrypted Key Transport for SRTP
+ *
+ * David McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006 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 "err.h"
+#include "srtp_priv.h"
+#include "ekt.h"
+
+extern 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
+ekt_octets_after_base_tag(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 EKT_CIPHER_AES_128_ECB:
+ return 16 + EKT_OCTETS_AFTER_EMK;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static inline 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 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;
+}
+
+
+err_status_t
+ekt_alloc(ekt_stream_t *stream_data, 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 err_status_ok;
+ }
+
+ /* TODO */
+ *stream_data = NULL;
+
+ return err_status_ok;
+}
+
+err_status_t
+ekt_stream_init_from_policy(ekt_stream_t stream_data, ekt_policy_t policy) {
+ if (!stream_data)
+ return err_status_ok;
+
+ return err_status_ok;
+}
+
+
+void
+aes_decrypt_with_raw_key(void *ciphertext, const void *key, int key_len) {
+ aes_expanded_key_t expanded_key;
+
+ aes_expand_decryption_key(key, key_len, &expanded_key);
+ aes_decrypt(ciphertext, &expanded_key);
+}
+
+/*
+ * The function srtp_stream_init_from_ekt() initializes a stream using
+ * the EKT data from an SRTCP trailer.
+ */
+
+err_status_t
+srtp_stream_init_from_ekt(srtp_stream_t stream,
+ const void *srtcp_hdr,
+ unsigned pkt_octet_len) {
+ 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 err_status_no_ctx;
+
+ if (stream->ekt->data->ekt_cipher_type != EKT_CIPHER_AES_128_ECB)
+ return 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 = rdbx_set_roc(&stream->rtp_rdbx, roc);
+ if (err) return err;
+
+ err = srtp_stream_init(stream, &srtp_policy);
+ if (err) return err;
+
+ return err_status_ok;
+}
+
+void
+ekt_write_data(ekt_stream_t ekt,
+ uint8_t *base_tag,
+ unsigned base_tag_len,
+ int *packet_len,
+ 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 = ekt_octets_after_base_tag(ekt);
+ memcpy(packet, ekt->encrypted_master_key, emk_len);
+ debug_print(mod_srtp, "writing EKT EMK: %s,",
+ 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,",
+ 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,",
+ 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,",
+ 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(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..4d83707f98
--- /dev/null
+++ b/netwerk/srtp/src/srtp/srtp.c
@@ -0,0 +1,2167 @@
+/*
+ * srtp.c
+ *
+ * the secure real-time transport protocol
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2006, 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.h"
+#include "ekt.h" /* for SRTP Encrypted Key Transport */
+#include "alloc.h" /* for crypto_alloc() */
+
+#ifndef SRTP_KERNEL
+# include <limits.h>
+# ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+# elif defined(HAVE_WINSOCK2_H)
+# include <winsock2.h>
+# endif
+#endif /* ! SRTP_KERNEL */
+
+
+/* the debug module for 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
+
+
+err_status_t
+srtp_stream_alloc(srtp_stream_ctx_t **str_ptr,
+ const srtp_policy_t *p) {
+ srtp_stream_ctx_t *str;
+ err_status_t stat;
+
+ /*
+ * 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 *) crypto_alloc(sizeof(srtp_stream_ctx_t));
+ if (str == NULL)
+ return err_status_alloc_fail;
+ *str_ptr = str;
+
+ /* allocate cipher */
+ stat = crypto_kernel_alloc_cipher(p->rtp.cipher_type,
+ &str->rtp_cipher,
+ p->rtp.cipher_key_len);
+ if (stat) {
+ crypto_free(str);
+ return stat;
+ }
+
+ /* allocate auth function */
+ stat = crypto_kernel_alloc_auth(p->rtp.auth_type,
+ &str->rtp_auth,
+ p->rtp.auth_key_len,
+ p->rtp.auth_tag_len);
+ if (stat) {
+ cipher_dealloc(str->rtp_cipher);
+ crypto_free(str);
+ return stat;
+ }
+
+ /* allocate key limit structure */
+ str->limit = (key_limit_ctx_t*) crypto_alloc(sizeof(key_limit_ctx_t));
+ if (str->limit == NULL) {
+ auth_dealloc(str->rtp_auth);
+ cipher_dealloc(str->rtp_cipher);
+ crypto_free(str);
+ return err_status_alloc_fail;
+ }
+
+ /*
+ * ...and now the RTCP-specific initialization - first, allocate
+ * the cipher
+ */
+ stat = crypto_kernel_alloc_cipher(p->rtcp.cipher_type,
+ &str->rtcp_cipher,
+ p->rtcp.cipher_key_len);
+ if (stat) {
+ auth_dealloc(str->rtp_auth);
+ cipher_dealloc(str->rtp_cipher);
+ crypto_free(str->limit);
+ crypto_free(str);
+ return stat;
+ }
+
+ /* allocate auth function */
+ stat = crypto_kernel_alloc_auth(p->rtcp.auth_type,
+ &str->rtcp_auth,
+ p->rtcp.auth_key_len,
+ p->rtcp.auth_tag_len);
+ if (stat) {
+ cipher_dealloc(str->rtcp_cipher);
+ auth_dealloc(str->rtp_auth);
+ cipher_dealloc(str->rtp_cipher);
+ crypto_free(str->limit);
+ crypto_free(str);
+ return stat;
+ }
+
+ /* allocate ekt data associated with stream */
+ stat = ekt_alloc(&str->ekt, p->ekt);
+ if (stat) {
+ auth_dealloc(str->rtcp_auth);
+ cipher_dealloc(str->rtcp_cipher);
+ auth_dealloc(str->rtp_auth);
+ cipher_dealloc(str->rtp_cipher);
+ crypto_free(str->limit);
+ crypto_free(str);
+ return stat;
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+srtp_stream_dealloc(srtp_t session, srtp_stream_ctx_t *stream) {
+ err_status_t status;
+
+ /*
+ * we use a conservative deallocation strategy - if any deallocation
+ * fails, then we report that fact without trying to deallocate
+ * anything else
+ */
+
+ /* deallocate cipher, if it is not the same as that in template */
+ if (session->stream_template
+ && stream->rtp_cipher == session->stream_template->rtp_cipher) {
+ /* do nothing */
+ } else {
+ status = cipher_dealloc(stream->rtp_cipher);
+ if (status)
+ return status;
+ }
+
+ /* deallocate auth function, if it is not the same as that in template */
+ if (session->stream_template
+ && stream->rtp_auth == session->stream_template->rtp_auth) {
+ /* do nothing */
+ } else {
+ status = auth_dealloc(stream->rtp_auth);
+ if (status)
+ return status;
+ }
+
+ /* deallocate key usage limit, if it is not the same as that in template */
+ if (session->stream_template
+ && stream->limit == session->stream_template->limit) {
+ /* do nothing */
+ } else {
+ crypto_free(stream->limit);
+ }
+
+ /*
+ * deallocate rtcp cipher, if it is not the same as that in
+ * template
+ */
+ if (session->stream_template
+ && stream->rtcp_cipher == session->stream_template->rtcp_cipher) {
+ /* do nothing */
+ } else {
+ status = cipher_dealloc(stream->rtcp_cipher);
+ if (status)
+ return status;
+ }
+
+ /*
+ * deallocate rtcp auth function, if it is not the same as that in
+ * template
+ */
+ if (session->stream_template
+ && stream->rtcp_auth == session->stream_template->rtcp_auth) {
+ /* do nothing */
+ } else {
+ status = auth_dealloc(stream->rtcp_auth);
+ if (status)
+ return status;
+ }
+
+ status = rdbx_dealloc(&stream->rtp_rdbx);
+ if (status)
+ return status;
+
+ /* DAM - need to deallocate EKT here */
+
+ /* deallocate srtp stream context */
+ crypto_free(stream);
+
+ return 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
+ */
+
+err_status_t
+srtp_stream_clone(const srtp_stream_ctx_t *stream_template,
+ uint32_t ssrc,
+ srtp_stream_ctx_t **str_ptr) {
+ err_status_t status;
+ srtp_stream_ctx_t *str;
+
+ debug_print(mod_srtp, "cloning stream (SSRC: 0x%08x)", ssrc);
+
+ /* allocate srtp stream and set str_ptr */
+ str = (srtp_stream_ctx_t *) crypto_alloc(sizeof(srtp_stream_ctx_t));
+ if (str == NULL)
+ return err_status_alloc_fail;
+ *str_ptr = str;
+
+ /* set cipher and auth pointers to those of the template */
+ str->rtp_cipher = stream_template->rtp_cipher;
+ str->rtp_auth = stream_template->rtp_auth;
+ str->rtcp_cipher = stream_template->rtcp_cipher;
+ str->rtcp_auth = stream_template->rtcp_auth;
+
+ /* set key limit to point to that of the template */
+ status = key_limit_clone(stream_template->limit, &str->limit);
+ if (status)
+ return status;
+
+ /* initialize replay databases */
+ status = rdbx_init(&str->rtp_rdbx,
+ rdbx_get_window_size(&stream_template->rtp_rdbx));
+ if (status)
+ return status;
+ rdb_init(&str->rtcp_rdb);
+ str->allow_repeat_tx = stream_template->allow_repeat_tx;
+
+ /* set ssrc to that provided */
+ str->ssrc = ssrc;
+
+ /* 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;
+
+ /* defensive coding */
+ str->next = NULL;
+
+ return 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
+} srtp_prf_label;
+
+
+/*
+ * srtp_kdf_t represents a key derivation function. The SRTP
+ * default KDF is the only one implemented at present.
+ */
+
+typedef struct {
+ cipher_t *cipher; /* cipher used for key derivation */
+} srtp_kdf_t;
+
+err_status_t
+srtp_kdf_init(srtp_kdf_t *kdf, cipher_type_id_t cipher_id, const uint8_t *key, int length) {
+
+ err_status_t stat;
+ stat = crypto_kernel_alloc_cipher(cipher_id, &kdf->cipher, length);
+ if (stat)
+ return stat;
+
+ stat = cipher_init(kdf->cipher, key, direction_encrypt);
+ if (stat) {
+ cipher_dealloc(kdf->cipher);
+ return stat;
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+srtp_kdf_generate(srtp_kdf_t *kdf, srtp_prf_label label,
+ uint8_t *key, unsigned length) {
+
+ v128_t nonce;
+ err_status_t status;
+
+ /* set eigth octet of nonce to <label>, set the rest of it to zero */
+ v128_set_to_zero(&nonce);
+ nonce.v8[7] = label;
+
+ status = cipher_set_iv(kdf->cipher, &nonce);
+ if (status)
+ return status;
+
+ /* generate keystream output */
+ octet_string_set_to_zero(key, length);
+ status = cipher_encrypt(kdf->cipher, key, &length);
+ if (status)
+ return status;
+
+ return err_status_ok;
+}
+
+err_status_t
+srtp_kdf_clear(srtp_kdf_t *kdf) {
+ err_status_t status;
+ status = cipher_dealloc(kdf->cipher);
+ if (status)
+ return status;
+ kdf->cipher = NULL;
+
+ return err_status_ok;
+}
+
+/*
+ * end of key derivation functions
+ */
+
+#define MAX_SRTP_KEY_LEN 256
+
+
+/* Get the base key length corresponding to a given combined key+salt
+ * length for the given cipher.
+ * Assumption is that for AES-ICM a key length < 30 is Ismacryp using
+ * AES-128 and short salts; everything else uses a salt length of 14.
+ * TODO: key and salt lengths should be separate fields in the policy. */
+static inline int base_key_length(const cipher_type_t *cipher, int key_length)
+{
+ if (cipher->id != AES_ICM)
+ return key_length;
+ else if (key_length > 16 && key_length < 30)
+ return 16;
+ return key_length - 14;
+}
+
+err_status_t
+srtp_stream_init_keys(srtp_stream_ctx_t *srtp, const void *key) {
+ 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;
+
+ /* 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. */
+ rtp_keylen = cipher_get_key_length(srtp->rtp_cipher);
+ if (rtp_keylen > kdf_keylen)
+ kdf_keylen = rtp_keylen;
+
+ rtcp_keylen = cipher_get_key_length(srtp->rtcp_cipher);
+ if (rtcp_keylen > kdf_keylen)
+ kdf_keylen = rtcp_keylen;
+
+ /* initialize KDF state */
+ stat = srtp_kdf_init(&kdf, AES_ICM, (const uint8_t *)key, kdf_keylen);
+ if (stat) {
+ return err_status_init_fail;
+ }
+
+ rtp_base_key_len = base_key_length(srtp->rtp_cipher->type, rtp_keylen);
+ rtp_salt_len = rtp_keylen - rtp_base_key_len;
+
+ /* 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 err_status_init_fail;
+ }
+
+ /*
+ * 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 err_status_init_fail;
+ }
+ }
+ debug_print(mod_srtp, "cipher key: %s",
+ octet_string_hex_string(tmp_key, rtp_keylen));
+
+ /* initialize cipher */
+ stat = cipher_init(srtp->rtp_cipher, tmp_key, direction_any);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return err_status_init_fail;
+ }
+
+ /* generate authentication key */
+ stat = srtp_kdf_generate(&kdf, label_rtp_msg_auth,
+ tmp_key, auth_get_key_length(srtp->rtp_auth));
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return err_status_init_fail;
+ }
+ debug_print(mod_srtp, "auth key: %s",
+ octet_string_hex_string(tmp_key,
+ auth_get_key_length(srtp->rtp_auth)));
+
+ /* initialize auth function */
+ stat = auth_init(srtp->rtp_auth, tmp_key);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return err_status_init_fail;
+ }
+
+ /*
+ * ...now initialize SRTCP keys
+ */
+
+ rtcp_base_key_len = base_key_length(srtp->rtcp_cipher->type, rtcp_keylen);
+ rtcp_salt_len = rtcp_keylen - rtcp_base_key_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 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 err_status_init_fail;
+ }
+ }
+ debug_print(mod_srtp, "rtcp cipher key: %s",
+ octet_string_hex_string(tmp_key, rtcp_keylen));
+
+ /* initialize cipher */
+ stat = cipher_init(srtp->rtcp_cipher, tmp_key, direction_any);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return err_status_init_fail;
+ }
+
+ /* generate authentication key */
+ stat = srtp_kdf_generate(&kdf, label_rtcp_msg_auth,
+ tmp_key, auth_get_key_length(srtp->rtcp_auth));
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return err_status_init_fail;
+ }
+
+ debug_print(mod_srtp, "rtcp auth key: %s",
+ octet_string_hex_string(tmp_key,
+ auth_get_key_length(srtp->rtcp_auth)));
+
+ /* initialize auth function */
+ stat = auth_init(srtp->rtcp_auth, tmp_key);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return 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 err_status_init_fail;
+
+ return err_status_ok;
+}
+
+err_status_t
+srtp_stream_init(srtp_stream_ctx_t *srtp,
+ const srtp_policy_t *p) {
+ 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 err_status_bad_param;
+
+ if (p->window_size != 0)
+ err = rdbx_init(&srtp->rtp_rdbx, p->window_size);
+ else
+ err = rdbx_init(&srtp->rtp_rdbx, 128);
+ if (err) return err;
+
+ /* initialize key limit to maximum value */
+#ifdef NO_64BIT_MATH
+{
+ uint64_t temp;
+ temp = make64(UINT_MAX,UINT_MAX);
+ key_limit_set(srtp->limit, temp);
+}
+#else
+ key_limit_set(srtp->limit, 0xffffffffffffLL);
+#endif
+
+ /* set the SSRC value */
+ srtp->ssrc = htonl(p->ssrc.value);
+
+ /* 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 */
+ 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) {
+ rdbx_dealloc(&srtp->rtp_rdbx);
+ return 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_keys(srtp, p->key);
+ if (err) {
+ rdbx_dealloc(&srtp->rtp_rdbx);
+ return err;
+ }
+
+ /*
+ * if EKT is in use, then initialize the EKT data associated with
+ * the stream
+ */
+ err = ekt_stream_init_from_policy(srtp->ekt, p->ekt);
+ if (err) {
+ rdbx_dealloc(&srtp->rtp_rdbx);
+ return err;
+ }
+
+ return 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) {
+
+ err_report(err_level_warning, "srtp: in stream 0x%x: ",
+ data->stream->ssrc);
+
+ switch(data->event) {
+ case event_ssrc_collision:
+ err_report(err_level_warning, "\tSSRC collision\n");
+ break;
+ case event_key_soft_limit:
+ err_report(err_level_warning, "\tkey usage soft limit reached\n");
+ break;
+ case event_key_hard_limit:
+ err_report(err_level_warning, "\tkey usage hard limit reached\n");
+ break;
+ case event_packet_index_limit:
+ err_report(err_level_warning, "\tpacket index limit reached\n");
+ break;
+ default:
+ err_report(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;
+
+ 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 err_status_ok;
+ }
+
+ err_status_t
+ srtp_protect(srtp_ctx_t *ctx, void *rtp_hdr, int *pkt_octet_len) {
+ 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 */
+ unsigned enc_octet_len = 0; /* number of octets in encrypted portion */
+ 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 */
+ err_status_t status;
+ int tag_len;
+ srtp_stream_ctx_t *stream;
+ int prefix_len;
+
+ debug_print(mod_srtp, "function srtp_protect", 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_rtp_header)
+ return 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 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);
+ }
+ }
+
+ /*
+ * 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(key_limit_update(stream->limit)) {
+ case key_event_normal:
+ break;
+ case key_event_soft_limit:
+ srtp_handle_event(ctx, stream, event_key_soft_limit);
+ break;
+ case key_event_hard_limit:
+ srtp_handle_event(ctx, stream, event_key_hard_limit);
+ return err_status_key_expired;
+ default:
+ break;
+ }
+
+ /* get tag length from stream */
+ tag_len = auth_get_tag_length(stream->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) {
+ srtp_hdr_xtnd_t *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))
+ return err_status_parse_err;
+ enc_octet_len = (unsigned int)(*pkt_octet_len
+ - ((enc_start - (uint32_t *)hdr) << 2));
+ } 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;
+ } 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
+ */
+ delta = rdbx_estimate_index(&stream->rtp_rdbx, &est, ntohs(hdr->seq));
+ status = rdbx_check(&stream->rtp_rdbx, delta);
+ if (status) {
+ if (status != err_status_replay_fail || !stream->allow_repeat_tx)
+ return status; /* we've been asked to reuse an index */
+ }
+ else
+ 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 (stream->rtp_cipher->type->id == AES_ICM) {
+ 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 = cipher_set_iv(stream->rtp_cipher, &iv);
+
+ } 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 = cipher_set_iv(stream->rtp_cipher, &iv);
+ }
+ if (status)
+ return 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 = auth_get_prefix_length(stream->rtp_auth);
+ if (prefix_len) {
+ status = cipher_output(stream->rtp_cipher, auth_tag, prefix_len);
+ if (status)
+ return err_status_cipher_fail;
+ debug_print(mod_srtp, "keystream prefix: %s",
+ octet_string_hex_string(auth_tag, prefix_len));
+ }
+ }
+
+ /* if we're encrypting, exor keystream into the message */
+ if (enc_start) {
+ status = cipher_encrypt(stream->rtp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status)
+ return 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 = auth_start(stream->rtp_auth);
+ if (status) return status;
+
+ /* run auth func over packet */
+ status = auth_update(stream->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 = auth_compute(stream->rtp_auth, (uint8_t *)&est, 4, auth_tag);
+ debug_print(mod_srtp, "srtp auth tag: %s",
+ octet_string_hex_string(auth_tag, tag_len));
+ if (status)
+ return err_status_auth_fail;
+
+ }
+
+ if (auth_tag) {
+
+ /* increase the packet length by the length of the auth tag */
+ *pkt_octet_len += tag_len;
+ }
+
+ return err_status_ok;
+}
+
+
+err_status_t
+srtp_unprotect(srtp_ctx_t *ctx, void *srtp_hdr, int *pkt_octet_len) {
+ 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 enc_octet_len = 0;/* number of octets in encrypted portion */
+ uint8_t *auth_tag = NULL; /* location of auth_tag within packet */
+ 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;
+ err_status_t status;
+ srtp_stream_ctx_t *stream;
+ uint8_t tmp_tag[SRTP_MAX_TAG_LEN];
+ int tag_len, prefix_len;
+
+ debug_print(mod_srtp, "function srtp_unprotect", 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_rtp_header)
+ return 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)",
+ hdr->ssrc);
+
+ /*
+ * set estimated packet index to sequence number from header,
+ * and set delta equal to the same value
+ */
+#ifdef NO_64BIT_MATH
+ est = (xtd_seq_num_t) make64(0,ntohs(hdr->seq));
+ delta = low32(est);
+#else
+ est = (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 err_status_no_ctx;
+ }
+ } else {
+
+ /* estimate packet index from seq. num. in header */
+ delta = rdbx_estimate_index(&stream->rtp_rdbx, &est, ntohs(hdr->seq));
+
+ /* check replay database */
+ status = 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
+
+ /* get tag length from stream */
+ tag_len = auth_get_tag_length(stream->rtp_auth);
+
+ /*
+ * set the cipher's IV properly, depending on whatever cipher we
+ * happen to be using
+ */
+ if (stream->rtp_cipher->type->id == AES_ICM) {
+
+ /* 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 = cipher_set_iv(stream->rtp_cipher, &iv);
+ } 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 = cipher_set_iv(stream->rtp_cipher, &iv);
+ }
+ if (status)
+ return 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) {
+ srtp_hdr_xtnd_t *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)))
+ return err_status_parse_err;
+ enc_octet_len = (uint32_t)(*pkt_octet_len - tag_len
+ - ((enc_start - (uint32_t *)hdr) << 2));
+ } 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 (stream->rtp_auth->prefix_len != 0) {
+
+ prefix_len = auth_get_prefix_length(stream->rtp_auth);
+ status = cipher_output(stream->rtp_cipher, tmp_tag, prefix_len);
+ debug_print(mod_srtp, "keystream prefix: %s",
+ octet_string_hex_string(tmp_tag, prefix_len));
+ if (status)
+ return err_status_cipher_fail;
+ }
+
+ /* initialize auth func context */
+ status = auth_start(stream->rtp_auth);
+ if (status) return status;
+
+ /* now compute auth function over packet */
+ status = auth_update(stream->rtp_auth, (uint8_t *)auth_start,
+ *pkt_octet_len - tag_len);
+
+ /* run auth func over ROC, then write tmp tag */
+ status = auth_compute(stream->rtp_auth, (uint8_t *)&est, 4, tmp_tag);
+
+ debug_print(mod_srtp, "computed auth tag: %s",
+ octet_string_hex_string(tmp_tag, tag_len));
+ debug_print(mod_srtp, "packet auth tag: %s",
+ octet_string_hex_string(auth_tag, tag_len));
+ if (status)
+ return err_status_auth_fail;
+
+ if (octet_string_is_eq(tmp_tag, auth_tag, tag_len))
+ return 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(key_limit_update(stream->limit)) {
+ case key_event_normal:
+ break;
+ case key_event_soft_limit:
+ srtp_handle_event(ctx, stream, event_key_soft_limit);
+ break;
+ case key_event_hard_limit:
+ srtp_handle_event(ctx, stream, event_key_hard_limit);
+ return err_status_key_expired;
+ default:
+ break;
+ }
+
+ /* if we're decrypting, add keystream into ciphertext */
+ if (enc_start) {
+ status = cipher_decrypt(stream->rtp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status)
+ return 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
+ */
+ rdbx_add_index(&stream->rtp_rdbx, delta);
+
+ /* decrease the packet length by the length of the auth tag */
+ *pkt_octet_len -= tag_len;
+
+ return err_status_ok;
+}
+
+err_status_t
+srtp_init() {
+ err_status_t status;
+
+ /* initialize crypto kernel */
+ status = crypto_kernel_init();
+ if (status)
+ return status;
+
+ /* load srtp debug module into the kernel */
+ status = crypto_kernel_load_debug_module(&mod_srtp);
+ if (status)
+ return status;
+
+ return err_status_ok;
+}
+
+err_status_t
+srtp_shutdown() {
+ err_status_t status;
+
+ /* shut down crypto kernel */
+ status = crypto_kernel_shutdown();
+ if (status)
+ return status;
+
+ /* shutting down crypto kernel frees the srtp debug module as well */
+
+ return 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 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;
+}
+
+err_status_t
+srtp_dealloc(srtp_t session) {
+ srtp_stream_ctx_t *stream;
+ 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(session, stream);
+ if (status)
+ return status;
+ stream = next;
+ }
+
+ /* deallocate stream template, if there is one */
+ if (session->stream_template != NULL) {
+ status = auth_dealloc(session->stream_template->rtcp_auth);
+ if (status)
+ return status;
+ status = cipher_dealloc(session->stream_template->rtcp_cipher);
+ if (status)
+ return status;
+ crypto_free(session->stream_template->limit);
+ status = cipher_dealloc(session->stream_template->rtp_cipher);
+ if (status)
+ return status;
+ status = auth_dealloc(session->stream_template->rtp_auth);
+ if (status)
+ return status;
+ status = rdbx_dealloc(&session->stream_template->rtp_rdbx);
+ if (status)
+ return status;
+ crypto_free(session->stream_template);
+ }
+
+ /* deallocate session context */
+ crypto_free(session);
+
+ return err_status_ok;
+}
+
+
+err_status_t
+srtp_add_stream(srtp_t session,
+ const srtp_policy_t *policy) {
+ err_status_t status;
+ srtp_stream_t tmp;
+
+ /* sanity check arguments */
+ if ((session == NULL) || (policy == NULL) || (policy->key == NULL))
+ return 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) {
+ crypto_free(tmp);
+ 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) {
+ return err_status_bad_param;
+ }
+ session->stream_template = tmp;
+ session->stream_template->direction = dir_srtp_sender;
+ break;
+ case (ssrc_any_inbound):
+ if (session->stream_template) {
+ return 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:
+ crypto_free(tmp);
+ return err_status_bad_param;
+ }
+
+ return err_status_ok;
+}
+
+
+err_status_t
+srtp_create(srtp_t *session, /* handle for session */
+ const srtp_policy_t *policy) { /* SRTP policy (list) */
+ err_status_t stat;
+ srtp_ctx_t *ctx;
+
+ /* sanity check arguments */
+ if (session == NULL)
+ return err_status_bad_param;
+
+ /* allocate srtp context and set ctx_ptr */
+ ctx = (srtp_ctx_t *) crypto_alloc(sizeof(srtp_ctx_t));
+ if (ctx == NULL)
+ return 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;
+ while (policy != NULL) {
+
+ stat = srtp_add_stream(ctx, policy);
+ if (stat) {
+ /* clean up everything */
+ srtp_dealloc(*session);
+ return stat;
+ }
+
+ /* set policy to next item in list */
+ policy = policy->next;
+ }
+
+ return err_status_ok;
+}
+
+
+err_status_t
+srtp_remove_stream(srtp_t session, uint32_t ssrc) {
+ srtp_stream_ctx_t *stream, *last_stream;
+ err_status_t status;
+
+ /* sanity check arguments */
+ if (session == NULL)
+ return 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 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(session, stream);
+ if (status)
+ return status;
+
+ return err_status_ok;
+}
+
+
+/*
+ * the default policy - provides a convenient way for callers to use
+ * the default security policy
+ *
+ * this policy is that defined in the current SRTP internet draft.
+ *
+ */
+
+/*
+ * 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
+crypto_policy_set_rtp_default(crypto_policy_t *p) {
+
+ p->cipher_type = AES_ICM;
+ p->cipher_key_len = 30; /* default 128 bits per RFC 3711 */
+ p->auth_type = 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
+crypto_policy_set_rtcp_default(crypto_policy_t *p) {
+
+ p->cipher_type = AES_ICM;
+ p->cipher_key_len = 30; /* default 128 bits per RFC 3711 */
+ p->auth_type = 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
+crypto_policy_set_aes_cm_128_hmac_sha1_32(crypto_policy_t *p) {
+
+ /*
+ * corresponds to RFC 4568
+ *
+ * note that this crypto policy is intended for SRTP, but not SRTCP
+ */
+
+ p->cipher_type = AES_ICM;
+ p->cipher_key_len = 30; /* 128 bit key, 112 bit salt */
+ p->auth_type = 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
+crypto_policy_set_aes_cm_128_null_auth(crypto_policy_t *p) {
+
+ /*
+ * corresponds to RFC 4568
+ *
+ * note that this crypto policy is intended for SRTP, but not SRTCP
+ */
+
+ p->cipher_type = AES_ICM;
+ p->cipher_key_len = 30; /* 128 bit key, 112 bit salt */
+ p->auth_type = NULL_AUTH;
+ p->auth_key_len = 0;
+ p->auth_tag_len = 0;
+ p->sec_serv = sec_serv_conf;
+
+}
+
+
+void
+crypto_policy_set_null_cipher_hmac_sha1_80(crypto_policy_t *p) {
+
+ /*
+ * corresponds to RFC 4568
+ */
+
+ p->cipher_type = NULL_CIPHER;
+ p->cipher_key_len = 0;
+ p->auth_type = HMAC_SHA1;
+ p->auth_key_len = 20;
+ p->auth_tag_len = 10;
+ p->sec_serv = sec_serv_auth;
+
+}
+
+
+void
+crypto_policy_set_aes_cm_256_hmac_sha1_80(crypto_policy_t *p) {
+
+ /*
+ * corresponds to draft-ietf-avt-big-aes-03.txt
+ */
+
+ p->cipher_type = AES_ICM;
+ p->cipher_key_len = 46;
+ p->auth_type = 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
+crypto_policy_set_aes_cm_256_hmac_sha1_32(crypto_policy_t *p) {
+
+ /*
+ * corresponds to draft-ietf-avt-big-aes-03.txt
+ *
+ * note that this crypto policy is intended for SRTP, but not SRTCP
+ */
+
+ p->cipher_type = AES_ICM;
+ p->cipher_key_len = 46;
+ p->auth_type = 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;
+}
+
+
+/*
+ * secure rtcp functions
+ */
+
+err_status_t
+srtp_protect_rtcp(srtp_t ctx, void *rtcp_hdr, int *pkt_octet_len) {
+ 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; /* pointer to start of trailer */
+ unsigned enc_octet_len = 0;/* number of octets in encrypted portion */
+ uint8_t *auth_tag = NULL; /* location of auth_tag within packet */
+ err_status_t status;
+ int tag_len;
+ srtp_stream_ctx_t *stream;
+ int prefix_len;
+ uint32_t seq_num;
+
+ /* we assume the hdr is 32-bit aligned to start */
+ /*
+ * 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 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);
+ }
+ }
+
+ /* get tag length from stream context */
+ tag_len = auth_get_tag_length(stream->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! */
+ /* NOTE: trailer is 32-bit aligned because RTCP 'packets' are always
+ multiples of 32-bits (RFC 3550 6.1) */
+ trailer = (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 */
+ }
+
+ /*
+ * 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);
+
+ /* perform EKT processing if needed */
+ ekt_write_data(stream->ekt, auth_tag, tag_len, pkt_octet_len,
+ 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 = rdb_increment(&stream->rtcp_rdb);
+ if (status)
+ return status;
+ seq_num = rdb_get_value(&stream->rtcp_rdb);
+ *trailer |= htonl(seq_num);
+ debug_print(mod_srtp, "srtcp index: %x", seq_num);
+
+ /*
+ * if we're using rindael counter mode, set nonce and seq
+ */
+ if (stream->rtcp_cipher->type->id == AES_ICM) {
+ 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 = cipher_set_iv(stream->rtcp_cipher, &iv);
+
+ } 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 = cipher_set_iv(stream->rtcp_cipher, &iv);
+ }
+ if (status)
+ return 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 = auth_get_prefix_length(stream->rtcp_auth);
+ status = cipher_output(stream->rtcp_cipher, auth_tag, prefix_len);
+
+ debug_print(mod_srtp, "keystream prefix: %s",
+ octet_string_hex_string(auth_tag, prefix_len));
+
+ if (status)
+ return err_status_cipher_fail;
+ }
+
+ /* if we're encrypting, exor keystream into the message */
+ if (enc_start) {
+ status = cipher_encrypt(stream->rtcp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status)
+ return err_status_cipher_fail;
+ }
+
+ /* initialize auth func context */
+ auth_start(stream->rtcp_auth);
+
+ /*
+ * run auth func over packet (including trailer), and write the
+ * result at auth_tag
+ */
+ status = auth_compute(stream->rtcp_auth,
+ (uint8_t *)auth_start,
+ (*pkt_octet_len) + sizeof(srtcp_trailer_t),
+ auth_tag);
+ debug_print(mod_srtp, "srtcp auth tag: %s",
+ octet_string_hex_string(auth_tag, tag_len));
+ if (status)
+ return 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));
+
+ return err_status_ok;
+}
+
+
+err_status_t
+srtp_unprotect_rtcp(srtp_t ctx, void *srtcp_hdr, int *pkt_octet_len) {
+ 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; /* pointer to start of trailer */
+ unsigned 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];
+ err_status_t status;
+ unsigned auth_len;
+ int tag_len;
+ srtp_stream_ctx_t *stream;
+ int prefix_len;
+ uint32_t seq_num;
+
+ /* we assume the hdr is 32-bit aligned to start */
+ /*
+ * 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)",
+ hdr->ssrc);
+ } else {
+ /* no template stream, so we return an error */
+ return err_status_no_ctx;
+ }
+ }
+
+ /* get tag length from stream context */
+ tag_len = auth_get_tag_length(stream->rtcp_auth);
+
+ /*
+ * set encryption start, encryption length, and trailer
+ */
+ enc_octet_len = *pkt_octet_len -
+ (octets_in_rtcp_header + tag_len + 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 */
+ /*
+ * NOTE: trailer is 32-bit aligned because RTCP 'packets' are always
+ * multiples of 32-bits (RFC 3550 6.1)
+ */
+ trailer = (uint32_t *) ((char *) hdr +
+ *pkt_octet_len -(tag_len + sizeof(srtcp_trailer_t)));
+ if (*((unsigned char *) trailer) & 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 */
+ }
+
+ /*
+ * 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;
+ auth_len = *pkt_octet_len - tag_len;
+ auth_tag = (uint8_t *)hdr + auth_len;
+
+ /*
+ * 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 -= 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 = rdb_check(&stream->rtcp_rdb, seq_num);
+ if (status)
+ return status;
+
+ /*
+ * if we're using aes counter mode, set nonce and seq
+ */
+ if (stream->rtcp_cipher->type->id == AES_ICM) {
+ 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 = cipher_set_iv(stream->rtcp_cipher, &iv);
+
+ } 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 = cipher_set_iv(stream->rtcp_cipher, &iv);
+
+ }
+ if (status)
+ return err_status_cipher_fail;
+
+ /* initialize auth func context */
+ auth_start(stream->rtcp_auth);
+
+ /* run auth func over packet, put result into tmp_tag */
+ status = auth_compute(stream->rtcp_auth, (uint8_t *)auth_start,
+ auth_len, tmp_tag);
+ debug_print(mod_srtp, "srtcp computed tag: %s",
+ octet_string_hex_string(tmp_tag, tag_len));
+ if (status)
+ return err_status_auth_fail;
+
+ /* compare the tag just computed with the one in the packet */
+ debug_print(mod_srtp, "srtcp tag from packet: %s",
+ octet_string_hex_string(auth_tag, tag_len));
+ if (octet_string_is_eq(tmp_tag, auth_tag, tag_len))
+ return err_status_auth_fail;
+
+ /*
+ * if we're authenticating using a universal hash, put the keystream
+ * prefix into the authentication tag
+ */
+ prefix_len = auth_get_prefix_length(stream->rtcp_auth);
+ if (prefix_len) {
+ status = cipher_output(stream->rtcp_cipher, auth_tag, prefix_len);
+ debug_print(mod_srtp, "keystream prefix: %s",
+ octet_string_hex_string(auth_tag, prefix_len));
+ if (status)
+ return err_status_cipher_fail;
+ }
+
+ /* if we're decrypting, exor keystream into the message */
+ if (enc_start) {
+ status = cipher_decrypt(stream->rtcp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status)
+ return 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));
+
+ /*
+ * if EKT is in effect, subtract the EKT data out of the packet
+ * length
+ */
+ *pkt_octet_len -= 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 */
+ rdb_add_index(&stream->rtcp_rdb, seq_num);
+
+
+ return err_status_ok;
+}
+
+
+
+/*
+ * dtls keying for srtp
+ */
+
+err_status_t
+crypto_policy_set_from_profile_for_rtp(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:
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(policy);
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_aes128_cm_sha1_32:
+ crypto_policy_set_aes_cm_128_hmac_sha1_32(policy);
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_null_sha1_80:
+ crypto_policy_set_null_cipher_hmac_sha1_80(policy);
+ crypto_policy_set_null_cipher_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_aes256_cm_sha1_80:
+ crypto_policy_set_aes_cm_256_hmac_sha1_80(policy);
+ crypto_policy_set_aes_cm_256_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_aes256_cm_sha1_32:
+ crypto_policy_set_aes_cm_256_hmac_sha1_32(policy);
+ crypto_policy_set_aes_cm_256_hmac_sha1_80(policy);
+ break;
+ /* the following profiles are not (yet) supported */
+ case srtp_profile_null_sha1_32:
+ default:
+ return err_status_bad_param;
+ }
+
+ return err_status_ok;
+}
+
+err_status_t
+crypto_policy_set_from_profile_for_rtcp(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:
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_aes128_cm_sha1_32:
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_null_sha1_80:
+ crypto_policy_set_null_cipher_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_aes256_cm_sha1_80:
+ crypto_policy_set_aes_cm_256_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_aes256_cm_sha1_32:
+ crypto_policy_set_aes_cm_256_hmac_sha1_80(policy);
+ break;
+ /* the following profiles are not (yet) supported */
+ case srtp_profile_null_sha1_32:
+ default:
+ return err_status_bad_param;
+ }
+
+ return err_status_ok;
+}
+
+void
+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 16;
+ break;
+ case srtp_profile_aes128_cm_sha1_32:
+ return 16;
+ break;
+ case srtp_profile_null_sha1_80:
+ return 16;
+ break;
+ case srtp_profile_aes256_cm_sha1_80:
+ return 32;
+ break;
+ case srtp_profile_aes256_cm_sha1_32:
+ return 32;
+ 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 14;
+ break;
+ case srtp_profile_aes128_cm_sha1_32:
+ return 14;
+ break;
+ case srtp_profile_null_sha1_80:
+ return 14;
+ break;
+ case srtp_profile_aes256_cm_sha1_80:
+ return 14;
+ break;
+ case srtp_profile_aes256_cm_sha1_32:
+ return 14;
+ break;
+ /* the following profiles are not (yet) supported */
+ case srtp_profile_null_sha1_32:
+ default:
+ return 0; /* indicate error by returning a zero */
+ }
+}
diff --git a/netwerk/srtp/srtp_update.log b/netwerk/srtp/srtp_update.log
new file mode 100644
index 0000000000..c4592fd917
--- /dev/null
+++ b/netwerk/srtp/srtp_update.log
@@ -0,0 +1 @@
+srtp updated from CVS on Fri Sep 21 14:51:37 EDT 2012
diff --git a/netwerk/srtp/update_srtp.sh b/netwerk/srtp/update_srtp.sh
new file mode 100644
index 0000000000..74d6b59558
--- /dev/null
+++ b/netwerk/srtp/update_srtp.sh
@@ -0,0 +1,32 @@
+#!/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 SVN checkout of the libsrtp source
+#
+# srtp source is available (via cvs) at:
+# cvs -d:pserver:anonymous@srtp.cvs.sourceforge.net:/cvsroot/srtp login
+# cvs -z3 -d:pserver:anonymous@srtp.cvs.sourceforge.net:/cvsroot/srtp co -P srtp
+#
+# 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
+ export date=`date`
+# export revision=`(cd $1; svnversion -n)`
+ export CVSREAD=0
+ cp -rf $1/srtp $1/crypto $1/include $1/VERSION $1/LICENSE $1/README $1/configure.in netwerk/srtp/src
+
+ hg addremove netwerk/srtp/src --include "netwerk/srtp/src/VERSION" --include "netwerk/srtp/src/LICENSE" --include "netwerk/srtp/src/configure.in" --include "netwerk/srtp/src/README" --include "**.c" --include "**.h" --similarity 90
+
+ echo "srtp updated from CVS on $date" >> netwerk/srtp/srtp_update.log
+ echo "srtp updated from CVS 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/standalone/moz.build b/netwerk/standalone/moz.build
new file mode 100644
index 0000000000..4c7366e3b0
--- /dev/null
+++ b/netwerk/standalone/moz.build
@@ -0,0 +1,54 @@
+# -*- 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_TARGET'] != 'WINNT' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
+ Library('necko_standalone')
+
+src_list = [
+ 'nsNetModuleStandalone.cpp',
+]
+
+netwerk_base_src = [
+ 'PollableEvent.cpp',
+ 'nsDNSPrefetch.cpp',
+ 'nsNetAddr.cpp',
+ 'nsSocketTransportService2.cpp',
+ 'nsURLHelper.cpp',
+]
+src_list += [
+ '/netwerk/base/%s' % s for s in netwerk_base_src
+]
+
+netwerk_dns_src = [
+ 'nsHostResolver.cpp',
+ 'DNS.cpp',
+ 'DNSListenerProxy.cpp',
+ 'GetAddrInfo.cpp',
+ 'nameprep.c',
+ 'nsDNSService2.cpp',
+ 'nsIDNService.cpp',
+ 'punycode.c',
+]
+src_list += [
+ '/netwerk/dns/%s' % s for s in netwerk_dns_src
+]
+
+SOURCES += sorted(src_list)
+
+LOCAL_INCLUDES = [
+ '../base',
+ '../build',
+ '../dns',
+]
+
+DEFINES['IDNA2008'] = False
+DEFINES['MOZILLA_INTERNAL_API'] = True
+DEFINES['MOZILLA_EXTERNAL_LINKAGE'] = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/standalone/nsNetModuleStandalone.cpp b/netwerk/standalone/nsNetModuleStandalone.cpp
new file mode 100644
index 0000000000..04e43d0cdc
--- /dev/null
+++ b/netwerk/standalone/nsNetModuleStandalone.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 "necko-config.h"
+
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "nsCOMPtr.h"
+#include "nsICategoryManager.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsNetCID.h"
+#include "nsPIDNSService.h"
+#include "nsPISocketTransportService.h"
+#include "nscore.h"
+
+extern const mozilla::Module kNeckoStandaloneModule;
+
+namespace mozilla {
+
+nsresult
+InitNetModuleStandalone()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsPIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mozilla::DebugOnly<nsresult> rv = dns->Init();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service init failed");
+ } else {
+ NS_WARNING("failed to get dns service");
+ }
+
+ nsCOMPtr<nsPISocketTransportService> sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mozilla::DebugOnly<nsresult> rv = sts->Init();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Socket transport service init failed");
+ } else {
+ NS_WARNING("failed to get socket transport service");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ShutdownNetModuleStandalone()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsPIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mozilla::DebugOnly<nsresult> rv = dns->Shutdown();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service shutdown failed");
+ } else {
+ NS_WARNING("failed to get dns service");
+ }
+
+ nsCOMPtr<nsPISocketTransportService> sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mozilla::DebugOnly<nsresult> rv = sts->Shutdown(true);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Socket transport service shutdown failed");
+ } else {
+ NS_WARNING("failed to get socket transport service");
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
+
+#include "nsDNSService2.h"
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIDNSService,
+ nsDNSService::GetXPCOMSingleton)
+
+#include "nsSocketTransportService2.h"
+typedef mozilla::net::nsSocketTransportService nsSocketTransportService;
+#undef LOG
+#undef LOG_ENABLED
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSocketTransportService, Init)
+
+// Net module startup hook
+static nsresult nsNetStartup()
+{
+ return NS_OK;
+}
+
+// Net module shutdown hook
+static void nsNetShutdown()
+{
+}
+
+NS_DEFINE_NAMED_CID(NS_SOCKETTRANSPORTSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_DNSSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
+ { &kNS_SOCKETTRANSPORTSERVICE_CID, false, nullptr, nsSocketTransportServiceConstructor },
+ { &kNS_DNSSERVICE_CID, false, nullptr, nsIDNSServiceConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
+ { NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &kNS_SOCKETTRANSPORTSERVICE_CID },
+ { NS_DNSSERVICE_CONTRACTID, &kNS_DNSSERVICE_CID },
+ { nullptr }
+};
+
+const mozilla::Module kNeckoStandaloneModule = {
+ mozilla::Module::kVersion,
+ kNeckoCIDs,
+ kNeckoContracts,
+ nullptr,
+ nullptr,
+ nsNetStartup,
+ nsNetShutdown
+};
diff --git a/netwerk/standalone/nsNetModuleStandalone.h b/netwerk/standalone/nsNetModuleStandalone.h
new file mode 100644
index 0000000000..a9fe922770
--- /dev/null
+++ b/netwerk/standalone/nsNetModuleStandalone.h
@@ -0,0 +1,13 @@
+#ifndef ns_net_module_standalone_h_
+#define ns_net_module_standalone_h_
+
+#include <nsError.h>
+
+namespace mozilla {
+
+nsresult InitNetModuleStandalone();
+nsresult ShutdownNetModuleStandalone();
+
+} // namespace mozilla
+
+#endif // ns_net_module_standalone_h_
diff --git a/netwerk/streamconv/converters/ParseFTPList.cpp b/netwerk/streamconv/converters/ParseFTPList.cpp
new file mode 100644
index 0000000000..2ca16166c5
--- /dev/null
+++ b/netwerk/streamconv/converters/ParseFTPList.cpp
@@ -0,0 +1,1888 @@
+/* -*- 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 <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "plstr.h"
+#include "nsDebug.h"
+#include "prprf.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Sprintf.h"
+
+/* ==================================================================== */
+
+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 */
+}
+
+int ParseFTPList(const char *line, struct list_state *state,
+ struct list_result *result )
+{
+ 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 > 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 (isdigit(line[pos]))
+ {
+ while (pos < linelen && isdigit(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, PR_LocalTimeParameters, &(result->fe_time) );
+ }
+ }
+ }
+ else if (*p == 's')
+ {
+ if (isdigit(line[pos]))
+ {
+ while (pos < linelen && isdigit(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 (isalpha(*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] != '~' &&
+ !isdigit(p[pos]) && !isalpha(p[pos]))
+ break;
+ else if (isalpha(p[pos]) && p[pos] != toupper(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] != '~' && !isdigit(p[pos]) && !isalpha(p[pos]))
+ break;
+ else if (isalpha(p[pos]) && p[pos] != toupper(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 */
+ isdigit(*tokens[1]) && /* size */
+ isdigit(*tokens[2]) && /* date */
+ isdigit(*tokens[3]) /* time */
+ )
+ {
+ lstyle = 'V';
+ }
+ if (lstyle == 'V')
+ {
+ /*
+ * 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,,)
+ */
+ 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 != '~' && !isdigit(*p) && !isalpha(*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 != '~' && !isdigit(*p) && !isalpha(*p))
+ lstyle = 0;
+ else if (isalpha(*p) && *p != toupper(*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 (!isdigit(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 (isdigit(*tokens[1])) /* not no-privs message */
+ {
+ for (pos = 0; lstyle && pos < (toklen[1]); pos++)
+ {
+ if (!isdigit((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 (isdigit(*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++;
+
+/*
+ * I've never seen size come back in bytes, its always in blocks, and
+ * the following test fails. So, always perform the "size in blocks".
+ * I'm leaving the "size in bytes" code if'd out in case we ever need
+ * to re-instate it.
+*/
+#if 0
+ if (pos < toklen[1] && ( (pos<<1) > (toklen[1]-1) ||
+ (strtoul(tokens[1], (char **)0, 10) >
+ strtoul(tokens[1]+pos+1, (char **)0, 10)) ))
+ { /* size is in bytes */
+ if (pos > (sizeof(result->fe_size)-1))
+ pos = sizeof(result->fe_size)-1;
+ memcpy( result->fe_size, tokens[1], pos );
+ result->fe_size[pos] = '\0';
+ }
+ else /* size is in blocks */
+#endif
+ {
+ /* size requires multiplication by blocksize.
+ *
+ * We could assume blocksize is 512 (like Lynx does) and
+ * shift by 9, but that might not be right. Even if it
+ * were, doing that wouldn't reflect what the file's
+ * real size was. The sanest thing to do is not use the
+ * LISTing's filesize, so we won't (like ftpmirror).
+ *
+ * ulltoa(((unsigned long long)fsz)<<9, result->fe_size, 10);
+ *
+ * A block is always 512 bytes on OpenVMS, compute size.
+ * So its rounded up to the next block, so what, its better
+ * than not showing the size at all.
+ * A block is always 512 bytes on OpenVMS, compute size.
+ * So its rounded up to the next block, so what, its better
+ * than not showing the size at all.
+ */
+ uint64_t fsz = uint64_t(strtoul(tokens[1], (char **)0, 10) * 512);
+ SprintfLiteral(result->fe_size, "%" PRId64, fsz);
+ }
+
+ } /* if (result->fe_type != 'd') */
+
+ p = tokens[2] + 2;
+ if (*p == '-')
+ p++;
+ tbuf[0] = p[0];
+ tbuf[1] = tolower(p[1]);
+ tbuf[2] = tolower(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 (isdigit(*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] == '-') ||
+ (isdigit(*tokens[pos+1]) &&
+ isdigit(*tokens[pos+2]) &&
+ isdigit(*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 (isalpha(*p) && toupper(*p) != *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 (!isdigit(*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 != '-' && !isdigit(*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 != ':' && !isdigit(*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) - 1900;
+ 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);
+ if (result->fe_time.tm_year < 70)
+ result->fe_time.tm_year += 100;
+ }
+
+ 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'))
+ {
+ /*
+ * "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"
+ if ((numtoks >= 4) && (toklen[0] == 8 || toklen[0] == 10) &&
+ (toklen[1] == 5 || toklen[1] == 7) &&
+ (*tokens[2] == '<' || isdigit(*tokens[2])) )
+ {
+ p = tokens[0];
+ if ( isdigit(p[0]) && isdigit(p[1]) && p[2]=='-' &&
+ isdigit(p[3]) && isdigit(p[4]) && p[5]=='-' &&
+ isdigit(p[6]) && isdigit(p[7]) )
+ {
+ p = tokens[1];
+ if ( isdigit(p[0]) && isdigit(p[1]) && p[2]==':' &&
+ isdigit(p[3]) && isdigit(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 (!isdigit(*++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];
+ while (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);
+ /* if year has only two digits then assume that
+ 00-79 is 2000-2079
+ 80-99 is 1980-1999 */
+ if (result->fe_time.tm_year < 80)
+ result->fe_time.tm_year += 2000;
+ else if (result->fe_time.tm_year < 100)
+ result->fe_time.tm_year += 1900;
+ }
+
+ 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 && isdigit(*tokens[0]) &&
+ (linelen - toklen[0]) >= (53-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] == ' ' &&
+ isdigit(p[35-18]) && isdigit(p[36-18]) &&
+ isdigit(p[38-18]) && isdigit(p[39-18]) &&
+ isdigit(p[41-18]) && isdigit(p[42-18]) &&
+ isdigit(p[46-18]) && isdigit(p[47-18]) &&
+ isdigit(p[49-18]) && isdigit(p[50-18])
+ )
+ {
+ lstyle = 'O'; /* OS/2 */
+ if (!state->lstyle)
+ {
+ for (pos = 1; lstyle && pos < toklen[0]; pos++)
+ {
+ if (!isdigit(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]);
+ if (result->fe_time.tm_year < 80)
+ result->fe_time.tm_year += 100;
+ 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 (isdigit(*tokens[pos]) /* size */
+ /* (\w\w\w) */
+ && toklen[pos+1] == 3 && isalpha(*tokens[pos+1]) &&
+ isalpha(tokens[pos+1][1]) && isalpha(tokens[pos+1][2])
+ /* (\d|\d\d) */
+ && isdigit(*tokens[pos+2]) &&
+ (toklen[pos+2] == 1 ||
+ (toklen[pos+2] == 2 && isdigit(tokens[pos+2][1])))
+ && toklen[pos+3] >= 4 && isdigit(*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]) == ':'))
+ && isdigit(tokens[pos+3][toklen[pos+3]-2])
+ && isdigit(tokens[pos+3][toklen[pos+3]-1])
+ && (
+ /* (\d\d\d\d) */
+ ((toklen[pos+3] == 4 || toklen[pos+3] == 5) &&
+ isdigit(tokens[pos+3][1]) &&
+ isdigit(tokens[pos+3][2]) )
+ /* (\d\:\d\d|\d\:\d\d\:\d\d) */
+ || ((toklen[pos+3] == 4 || toklen[pos+3] == 7) &&
+ (tokens[pos+3][1]) == ':' &&
+ isdigit(tokens[pos+3][2]) && isdigit(tokens[pos+3][3]))
+ /* (\d\d\:\d\d|\d\d\:\d\d\:\d\d) */
+ || ((toklen[pos+3] == 5 || toklen[pos+3] == 8) &&
+ isdigit(tokens[pos+3][1]) && (tokens[pos+3][2]) == ':' &&
+ isdigit(tokens[pos+3][3]) && isdigit(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 (!isdigit(*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 = PR_Now();
+ PR_ExplodeTime((state->now_time), PR_LocalTimeParameters, &(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". */
+ uint32_t fe_size = atoi(result->fe_size);
+
+ if (result->fe_fnlen > (fe_size + 4) &&
+ PL_strncmp(result->fe_fname + result->fe_fnlen - fe_size - 4 , " -> ", 4) == 0)
+ {
+ result->fe_lname = result->fe_fname + (result->fe_fnlen - fe_size);
+ result->fe_lnlen = (&(line[linelen])) - (result->fe_lname);
+ result->fe_fnlen -= fe_size + 4;
+ }
+ 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] == '<') || isdigit(*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]) == ':'
+ && isdigit(tokens[2][0]) && isdigit(tokens[2][1])
+ && isdigit(tokens[2][3]) && isdigit(tokens[2][4])
+ && isdigit(tokens[2][6]) && isdigit(tokens[2][7])
+ && (toklen[2] < 9 || isdigit(tokens[2][8]))
+ && isdigit(tokens[3][toklen[3]-1]) && isdigit(tokens[3][toklen[3]-2])
+ && isdigit(tokens[3][toklen[3]-4]) && isdigit(*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]) == ':'
+ && isalpha(tokens[2][0]) && isalpha(tokens[2][1])
+ && isalpha(tokens[2][2])
+ && isdigit(tokens[3][0]) && isdigit(tokens[3][1])
+ && isdigit(tokens[4][0]) && isdigit(tokens[4][1])
+ && isdigit(tokens[4][2]) && isdigit(tokens[4][3])
+ && isdigit(tokens[5][0]) && isdigit(tokens[5][1])
+ && isdigit(tokens[5][3]) && isdigit(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 (!isdigit(*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 (isdigit(*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] = toupper(p[0]);
+ tbuf[1] = tolower(p[1]);
+ tbuf[2] = tolower(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]) - 1900;
+ 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);
+ if (result->fe_time.tm_year < 80) /* SuperTCP */
+ result->fe_time.tm_year += 100;
+
+ 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 == '=' || isdigit(*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 (!isdigit(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 (isdigit(*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)
+ && isdigit(*p) && isdigit(p[pos-1]) && isdigit(p[pos-2])
+ && ((pos == 5 && p[2] == ':') ||
+ (pos == 4 && (isdigit(p[1]) || p[1] == ':')))
+ )
+ {
+ month_num = tokmarker+1; /* assumed position of month field */
+ pos = tokmarker+2; /* assumed position of mday field */
+ if (isdigit(*tokens[month_num])) /* positions are reversed */
+ {
+ month_num++;
+ pos--;
+ }
+ p = tokens[month_num];
+ if (isdigit(*tokens[pos])
+ && (toklen[pos] == 1 ||
+ (toklen[pos] == 2 && isdigit(tokens[pos][1])))
+ && toklen[month_num] == 3
+ && isalpha(*p) && isalpha(p[1]) && isalpha(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-1900;
+ 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 = PR_Now();
+ PR_ExplodeTime((state->now_time), PR_LocalTimeParameters, &(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);
+}
+
+/* ==================================================================== */
+/* standalone testing */
+/* ==================================================================== */
+#if 0
+
+#include <stdio.h>
+
+static int do_it(FILE *outfile,
+ char *line, size_t linelen, struct list_state *state,
+ char **cmnt_buf, unsigned int *cmnt_buf_sz,
+ char **list_buf, unsigned int *list_buf_sz )
+{
+ struct list_result result;
+ char *p;
+ int rc;
+
+ rc = ParseFTPList( line, state, &result );
+
+ if (!outfile)
+ {
+ outfile = stdout;
+ if (rc == '?')
+ fprintf(outfile, "junk: %.*s\n", (int)linelen, line );
+ else if (rc == '"')
+ fprintf(outfile, "cmnt: %.*s\n", (int)linelen, line );
+ else
+ fprintf(outfile,
+ "list: %02u-%02u-%02u %02u:%02u%cM %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 % 100) : 0),
+ result.fe_time.tm_hour -
+ ((result.fe_time.tm_hour > 12)?(12):(0)),
+ result.fe_time.tm_min,
+ ((result.fe_time.tm_hour >= 12) ? 'P' : 'A'),
+ (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 : "") );
+ }
+ else if (rc != '?') /* NOT junk */
+ {
+ char **bufp = list_buf;
+ unsigned int *bufz = list_buf_sz;
+
+ if (rc == '"') /* comment - make it a 'result' */
+ {
+ memset( &result, 0, sizeof(result));
+ result.fe_fname = line;
+ result.fe_fnlen = linelen;
+ result.fe_type = 'f';
+ if (line[linelen-1] == '/')
+ {
+ result.fe_type = 'd';
+ result.fe_fnlen--;
+ }
+ bufp = cmnt_buf;
+ bufz = cmnt_buf_sz;
+ rc = result.fe_type;
+ }
+
+ linelen = 80 + result.fe_fnlen + result.fe_lnlen;
+ p = (char *)realloc( *bufp, *bufz + linelen );
+ if (!p)
+ return -1;
+ sprintf( &p[*bufz],
+ "%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 + 1900) : 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 : "") );
+ linelen = strlen(&p[*bufz]);
+ *bufp = p;
+ *bufz = *bufz + linelen;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ FILE *infile = (FILE *)0;
+ FILE *outfile = (FILE *)0;
+ int need_close_in = 0;
+ int need_close_out = 0;
+
+ if (argc > 1)
+ {
+ infile = stdin;
+ if (strcmp(argv[1], "-") == 0)
+ need_close_in = 0;
+ else if ((infile = fopen(argv[1], "r")) != ((FILE *)0))
+ need_close_in = 1;
+ else
+ fprintf(stderr, "Unable to open input file '%s'\n", argv[1]);
+ }
+ if (infile && argc > 2)
+ {
+ outfile = stdout;
+ if (strcmp(argv[2], "-") == 0)
+ need_close_out = 0;
+ else if ((outfile = fopen(argv[2], "w")) != ((FILE *)0))
+ need_close_out = 1;
+ else
+ {
+ fprintf(stderr, "Unable to open output file '%s'\n", argv[2]);
+ fclose(infile);
+ infile = (FILE *)0;
+ }
+ }
+
+ if (!infile)
+ {
+ char *appname = &(argv[0][strlen(argv[0])]);
+ while (appname > argv[0])
+ {
+ appname--;
+ if (*appname == '/' || *appname == '\\' || *appname == ':')
+ {
+ appname++;
+ break;
+ }
+ }
+ fprintf(stderr,
+ "Usage: %s <inputfilename> [<outputfilename>]\n"
+ "\nIf an outout file is specified the results will be"
+ "\nbe post-processed, and only the file entries will appear"
+ "\n(or all comments if there are no file entries)."
+ "\nNot specifying an output file causes %s to run in \"debug\""
+ "\nmode, ie results are printed as lines are parsed."
+ "\nIf a filename is a single dash ('-'), stdin/stdout is used."
+ "\n", appname, appname );
+ }
+ else
+ {
+ char *cmnt_buf = (char *)0;
+ unsigned int cmnt_buf_sz = 0;
+ char *list_buf = (char *)0;
+ unsigned int list_buf_sz = 0;
+
+ struct list_state state;
+ char line[512];
+
+ memset( &state, 0, sizeof(state) );
+ while (fgets(line, sizeof(line), infile))
+ {
+ size_t linelen = strlen(line);
+ if (linelen < (sizeof(line)-1))
+ {
+ if (linelen > 0 && line[linelen-1] == '\n')
+ linelen--;
+ if (do_it( outfile, line, linelen, &state,
+ &cmnt_buf, &cmnt_buf_sz, &list_buf, &list_buf_sz) != 0)
+ {
+ fprintf(stderr, "Insufficient memory. Listing may be incomplete.\n");
+ break;
+ }
+ }
+ else
+ {
+ /* no '\n' found. drop this and everything up to the next '\n' */
+ fprintf(stderr, "drop: %.*s", (int)linelen, line );
+ while (linelen == sizeof(line))
+ {
+ if (!fgets(line, sizeof(line), infile))
+ break;
+ linelen = 0;
+ while (linelen < sizeof(line) && line[linelen] != '\n')
+ linelen++;
+ fprintf(stderr, "%.*s", (int)linelen, line );
+ }
+ fprintf(stderr, "\n");
+ }
+ }
+ if (outfile)
+ {
+ if (list_buf)
+ fwrite( list_buf, 1, list_buf_sz, outfile );
+ else if (cmnt_buf)
+ fwrite( cmnt_buf, 1, cmnt_buf_sz, outfile );
+ }
+ if (list_buf)
+ free(list_buf);
+ if (cmnt_buf)
+ free(cmnt_buf);
+
+ if (need_close_in)
+ fclose(infile);
+ if (outfile && need_close_out)
+ fclose(outfile);
+ }
+
+ return 0;
+}
+#endif
diff --git a/netwerk/streamconv/converters/ParseFTPList.h b/netwerk/streamconv/converters/ParseFTPList.h
new file mode 100644
index 0000000000..f4a632a87b
--- /dev/null
+++ b/netwerk/streamconv/converters/ParseFTPList.h
@@ -0,0 +1,104 @@
+/* -*- 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 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) */
+};
+
+int ParseFTPList(const char *line,
+ struct list_state *state,
+ struct list_result *result );
+
+#endif /* !ParseRTPList_h___ */
diff --git a/netwerk/streamconv/converters/moz.build b/netwerk/streamconv/converters/moz.build
new file mode 100644
index 0000000000..d190843c6d
--- /dev/null
+++ b/netwerk/streamconv/converters/moz.build
@@ -0,0 +1,39 @@
+# -*- 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'
+]
+
+XPIDL_MODULE = 'necko_http'
+
+UNIFIED_SOURCES += [
+ 'mozTXTToHTMLConv.cpp',
+ 'nsDirIndex.cpp',
+ 'nsDirIndexParser.cpp',
+ 'nsHTTPCompressConv.cpp',
+ 'nsIndexedToHTML.cpp',
+ 'nsMultiMixedConv.cpp',
+ 'nsTXTToHTMLConv.cpp',
+ 'nsUnknownDecoder.cpp',
+]
+
+if 'ftp' in CONFIG['NECKO_PROTOCOLS']:
+ UNIFIED_SOURCES += [
+ 'nsFTPDirListingConv.cpp',
+ 'ParseFTPList.cpp',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'cocoa':
+ UNIFIED_SOURCES += [
+ 'nsBinHexDecoder.cpp',
+ ]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
diff --git a/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp b/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp
new file mode 100644
index 0000000000..db23cca35f
--- /dev/null
+++ b/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp
@@ -0,0 +1,1427 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozTXTToHTMLConv.h"
+#include "nsNetUtil.h"
+#include "nsUnicharUtils.h"
+#include "nsCRT.h"
+#include "nsIExternalProtocolHandler.h"
+#include "nsIIOService.h"
+#include "nsIURI.h"
+
+#include <algorithm>
+
+#ifdef DEBUG_BenB_Perf
+#include "prtime.h"
+#include "prinrval.h"
+#endif
+
+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, nsString& 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
+ MOZ_FALLTHROUGH;
+ default:
+ aStringToAppendTo += ch;
+ }
+
+ return;
+}
+
+// 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.Insert(NS_LITERAL_STRING("&lt;"), i);
+ i += 4; // skip past the integers we just added
+ break;
+ case '>':
+ aInString.Cut(i, 1);
+ aInString.Insert(NS_LITERAL_STRING("&gt;"), i);
+ i += 4; // skip past the integers we just added
+ break;
+ case '&':
+ aInString.Cut(i, 1);
+ aInString.Insert(NS_LITERAL_STRING("&amp;"), i);
+ i += 5; // skip past the integers we just added
+ break;
+ case '"':
+ if (inAttribute)
+ {
+ aInString.Cut(i, 1);
+ aInString.Insert(NS_LITERAL_STRING("&quot;"), i);
+ i += 6;
+ break;
+ }
+ // else fall through
+ MOZ_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 (!nsCRT::strncmp(subString, u"&lt;", std::min(4, aLength - remainingChars)))
+ {
+ aOutString.Append(char16_t('<'));
+ i += 4;
+ }
+ else if (!nsCRT::strncmp(subString, u"&gt;", std::min(4, aLength - remainingChars)))
+ {
+ aOutString.Append(char16_t('>'));
+ i += 4;
+ }
+ else if (!nsCRT::strncmp(subString, u"&amp;", std::min(5, aLength - remainingChars)))
+ {
+ aOutString.Append(char16_t('&'));
+ i += 5;
+ }
+ else if (!nsCRT::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 (!nsCRT::strncmp(&aInString[std::max(int32_t(pos - 4), 0)], u"<URL:", 5))
+ {
+ start = pos + 1;
+ return true;
+ }
+ else
+ 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;
+ }
+ else
+ return false;
+ }
+ case freetext:
+ {
+ int32_t i = pos - 1;
+ for (; i >= 0 && (
+ nsCRT::IsAsciiAlpha(aInString[uint32_t(i)]) ||
+ nsCRT::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 && nsCRT::IsAsciiAlpha(aInString[uint32_t(i)]))
+ {
+ start = uint32_t(i);
+ return true;
+ }
+ else
+ 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 || nsCRT::IsAscii(aInString[uint32_t(i)]))
+ ; i--)
+ ;
+ if
+ (
+ ++i >= 0 && uint32_t(i) < pos
+ &&
+ (
+ nsCRT::IsAsciiAlpha(aInString[uint32_t(i)]) ||
+ nsCRT::IsAsciiDigit(aInString[uint32_t(i)])
+ )
+ )
+ {
+ start = uint32_t(i);
+ return true;
+ }
+ else
+ 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] == '\'' ||
+ !nsCRT::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(&aInString[descstart], pos - descstart, ~kURLs /*prevents loop*/ & whathasbeendone, temp2);
+ replaceBefore = temp2.Length();
+ return;
+}
+
+bool mozTXTToHTMLConv::ShouldLinkify(const nsCString& aURL)
+{
+ if (!mIOService)
+ return false;
+
+ nsAutoCString scheme;
+ nsresult rv = mIOService->ExtractScheme(aURL, scheme);
+ if(NS_FAILED(rv))
+ return false;
+
+ // 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;
+ }
+ else
+ 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;
+ MOZ_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;
+}
+
+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;
+
+ char16_t text0 = aInString[0];
+ char16_t textAfterPos = aInString[aRepLen + (before == LT_IGNORE ? 0 : 1)];
+
+ if
+ (
+ (before == LT_ALPHA
+ && !nsCRT::IsAsciiAlpha(text0)) ||
+ (before == LT_DIGIT
+ && !nsCRT::IsAsciiDigit(text0)) ||
+ (before == LT_DELIMITER
+ &&
+ (
+ nsCRT::IsAsciiAlpha(text0) ||
+ nsCRT::IsAsciiDigit(text0) ||
+ text0 == *rep
+ )) ||
+ (after == LT_ALPHA
+ && !nsCRT::IsAsciiAlpha(textAfterPos)) ||
+ (after == LT_DIGIT
+ && !nsCRT::IsAsciiDigit(textAfterPos)) ||
+ (after == LT_DELIMITER
+ &&
+ (
+ nsCRT::IsAsciiAlpha(textAfterPos) ||
+ nsCRT::IsAsciiDigit(textAfterPos) ||
+ textAfterPos == *rep
+ )) ||
+ !Substring(Substring(aInString, aInString+aInLength),
+ (before == LT_IGNORE ? 0 : 1),
+ 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;
+
+ for (int32_t i = 0; i < aInStringLength; i++)
+ {
+ const char16_t * indexIntoString = &aInString[i];
+ if (ItMatchesDelimited(indexIntoString, aInStringLength - i, 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,
+ nsString& 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="
+ AppendASCIItoUTF16(imageName, outputHTML); // e.g. smiley-frown
+ outputHTML.AppendLiteral("\" title=\""); // " title="
+ AppendASCIItoUTF16(tagTXT, outputHTML); // smiley tooltip
+ outputHTML.AppendLiteral("\"><span>"); // "><span>
+ AppendASCIItoUTF16(tagTXT, outputHTML); // 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,
+ nsString& 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 == '^'
+ &&
+ (
+ nsCRT::IsAsciiDigit(text0) || nsCRT::IsAsciiAlpha(text0) ||
+ text0 == ')' || text0 == ']' || text0 == '}'
+ )
+ &&
+ (
+ (2 < aInLength && nsCRT::IsAsciiDigit(aInString[2])) ||
+ (3 < aInLength && aInString[2] == '-' && nsCRT::IsAsciiDigit(aInString[3]))
+ )
+ )
+ {
+ // Find first non-digit
+ int32_t delimPos = 3; // skip "^" and first digit (or '-')
+ for (; delimPos < aInLength
+ &&
+ (
+ nsCRT::IsAsciiDigit(aInString[delimPos]) ||
+ (aInString[delimPos] == '.' && delimPos + 1 < aInLength &&
+ nsCRT::IsAsciiDigit(aInString[delimPos + 1]))
+ );
+ delimPos++)
+ ;
+
+ if (delimPos < aInLength && nsCRT::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
+****************************************************************************/
+
+mozTXTToHTMLConv::mozTXTToHTMLConv()
+{
+}
+
+mozTXTToHTMLConv::~mozTXTToHTMLConv()
+{
+}
+
+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 && nsCRT::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(NS_LITERAL_STRING(">From "), 0, minlength),
+ nsCaseInsensitiveStringComparator()))
+ //XXX RFC2646
+ moreCites = false;
+ else
+ {
+ result++;
+ logLineStart = i;
+ }
+ }
+ else
+ moreCites = false;
+ }
+
+ return result;
+}
+
+void
+mozTXTToHTMLConv::ScanTXT(const char16_t * aInString, int32_t aInStringLength, uint32_t whattodo, nsString& aOutString)
+{
+ 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;
+
+ nsAutoString outputHTML; // moved here for performance increase
+
+ for(uint32_t i = 0; int32_t(i) < aInStringLength;)
+ {
+ if (doGlyphSubstitution)
+ {
+ int32_t glyphTextLen;
+ if (GlyphHit(&aInString[i], aInStringLength - i, i == 0, aOutString, glyphTextLen))
+ {
+ i += glyphTextLen;
+ continue;
+ }
+ }
+
+ if (doStructPhrase)
+ {
+ const char16_t * newOffset = aInString;
+ int32_t newLength = aInStringLength;
+ if (i > 0 ) // skip the first element?
+ {
+ newOffset = &aInString[i-1];
+ newLength = aInStringLength - i + 1;
+ }
+
+ switch (aInString[i]) // Performance increase
+ {
+ case '*':
+ if (StructPhraseHit(newOffset, newLength, i == 0,
+ u"*", 1,
+ "b", "class=\"moz-txt-star\"",
+ aOutString, structPhrase_strong))
+ {
+ i++;
+ continue;
+ }
+ break;
+ case '/':
+ if (StructPhraseHit(newOffset, newLength, i == 0,
+ u"/", 1,
+ "i", "class=\"moz-txt-slash\"",
+ aOutString, structPhrase_italic))
+ {
+ i++;
+ continue;
+ }
+ break;
+ case '_':
+ if (StructPhraseHit(newOffset, newLength, i == 0,
+ u"_", 1,
+ "span" /* <u> is deprecated */,
+ "class=\"moz-txt-underscore\"",
+ aOutString, structPhrase_underline))
+ {
+ i++;
+ continue;
+ }
+ break;
+ case '|':
+ if (StructPhraseHit(newOffset, newLength, i == 0,
+ u"|", 1,
+ "code", "class=\"moz-txt-verticalline\"",
+ aOutString, structPhrase_code))
+ {
+ i++;
+ continue;
+ }
+ break;
+ }
+ }
+
+ if (doURLs)
+ {
+ switch (aInString[i])
+ {
+ case ':':
+ case '@':
+ case '.':
+ if ( (i == 0 || ((i > 0) && aInString[i - 1] != ' ')) && aInString[i +1] != ' ') // Performance increase
+ {
+ int32_t replaceBefore;
+ int32_t replaceAfter;
+ if (FindURL(aInString, aInStringLength, i, whattodo,
+ outputHTML, replaceBefore, replaceAfter)
+ && structPhrase_strong + structPhrase_italic +
+ structPhrase_underline + structPhrase_code == 0
+ /* workaround for bug #19445 */ )
+ {
+ aOutString.Cut(aOutString.Length() - replaceBefore, replaceBefore);
+ aOutString += outputHTML;
+ i += replaceAfter + 1;
+ continue;
+ }
+ }
+ break;
+ } //switch
+ }
+
+ switch (aInString[i])
+ {
+ // Special symbols
+ case '<':
+ case '>':
+ case '&':
+ EscapeChar(aInString[i], aOutString, false);
+ i++;
+ break;
+ // Normal characters
+ default:
+ aOutString += aInString[i];
+ i++;
+ break;
+ }
+ }
+}
+
+void
+mozTXTToHTMLConv::ScanHTML(nsString& aInString, uint32_t whattodo, nsString &aOutString)
+{
+ // 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.get(), tempString.Length(), whattodo, aOutString);
+ }
+ }
+
+#ifdef DEBUG_BenB_Perf
+ printf("ScanHTML time: %d ms\n", PR_IntervalToMilliseconds(PR_IntervalNow() - parsing_start));
+#endif
+}
+
+/****************************************************************************
+ 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::OnDataAvailable(nsIRequest* request, nsISupports *ctxt,
+ nsIInputStream *inStr, uint64_t sourceOffset,
+ uint32_t count)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::OnStopRequest(nsIRequest* request, nsISupports *ctxt,
+ 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;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::ScanTXT(const char16_t *text, uint32_t whattodo,
+ char16_t **_retval)
+{
+ NS_ENSURE_ARG(text);
+
+ // FIX ME!!!
+ nsString outString;
+ int32_t inLength = NS_strlen(text);
+ // by setting a large capacity up front, we save time
+ // when appending characters to the output string because we don't
+ // need to reallocate and re-copy the characters already in the out String.
+ NS_ASSERTION(inLength, "ScanTXT passed 0 length string");
+ if (inLength == 0) {
+ *_retval = NS_strdup(text);
+ return NS_OK;
+ }
+
+ outString.SetCapacity(uint32_t(inLength * growthRate));
+ ScanTXT(text, inLength, whattodo, outString);
+
+ *_retval = ToNewUnicode(outString);
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+mozTXTToHTMLConv::ScanHTML(const char16_t *text, uint32_t whattodo,
+ char16_t **_retval)
+{
+ NS_ENSURE_ARG(text);
+
+ // FIX ME!!!
+ nsString outString;
+ nsString inString (text); // look at this nasty extra copy of the entire input buffer!
+ outString.SetCapacity(uint32_t(inString.Length() * growthRate));
+
+ ScanHTML(inString, whattodo, outString);
+ *_retval = ToNewUnicode(outString);
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult
+MOZ_NewTXTToHTMLConv(mozTXTToHTMLConv** aConv)
+{
+ NS_PRECONDITION(aConv != nullptr, "null ptr");
+ if (!aConv)
+ return NS_ERROR_NULL_POINTER;
+
+ *aConv = new mozTXTToHTMLConv();
+ if (!*aConv)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*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..731cc07375
--- /dev/null
+++ b/netwerk/streamconv/converters/mozTXTToHTMLConv.h
@@ -0,0 +1,294 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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();
+
+//////////////////////////////////////////////////////////
+public:
+//////////////////////////////////////////////////////////
+
+ mozTXTToHTMLConv();
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_MOZITXTTOHTMLCONV
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISTREAMCONVERTER
+
+/**
+ see mozITXTToHTMLConv::ScanTXT
+ */
+ void ScanTXT(const char16_t * aInString, int32_t aInStringLength, uint32_t whattodo, nsString& aOutString);
+
+/**
+ see mozITXTToHTMLConv::ScanHTML. We will modify aInString potentially...
+ */
+ void ScanHTML(nsString& aInString, uint32_t whattodo, nsString &aOutString);
+
+/**
+ 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, nsString& 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,
+ nsString& 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,
+ nsString& 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/nsBinHexDecoder.cpp b/netwerk/streamconv/converters/nsBinHexDecoder.cpp
new file mode 100644
index 0000000000..416fd743bb
--- /dev/null
+++ b/netwerk/streamconv/converters/nsBinHexDecoder.cpp
@@ -0,0 +1,520 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsBinHexDecoder.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamConverterService.h"
+#include "nsCRT.h"
+#include "nsIPipe.h"
+#include "nsMimeTypes.h"
+#include "netCore.h"
+#include "nsXPIDLString.h"
+#include "prnetdb.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+
+#include "nsIMIMEService.h"
+#include "nsMimeTypes.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+nsBinHexDecoder::nsBinHexDecoder() :
+ mState(0), mCRC(0), mFileCRC(0), mOctetin(26),
+ mDonePos(3), mInCRC(0), mCount(0), mMarker(0), mPosInbuff(0),
+ mPosOutputBuff(0)
+{
+ mDataBuffer = nullptr;
+ mOutgoingBuffer = nullptr;
+ mPosInDataBuffer = 0;
+ mRlebuf = 0;
+
+ mOctetBuf.val = 0;
+ mHeader.type = 0;
+ mHeader.creator = 0;
+ mHeader.flags = 0;
+ mHeader.dlen = 0;
+ mHeader.rlen = 0;
+}
+
+nsBinHexDecoder::~nsBinHexDecoder()
+{
+ if (mDataBuffer)
+ free(mDataBuffer);
+ if (mOutgoingBuffer)
+ free(mOutgoingBuffer);
+}
+
+NS_IMPL_ADDREF(nsBinHexDecoder)
+NS_IMPL_RELEASE(nsBinHexDecoder)
+
+NS_INTERFACE_MAP_BEGIN(nsBinHexDecoder)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamConverter)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+
+// The binhex 4.0 decoder table....
+
+static const signed char binhex_decode[256] =
+{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -1, -1,
+ 13, 14, 15, 16, 17, 18, 19, -1, 20, 21, -1, -1, -1, -1, -1, -1,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1,
+ 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47, -1, -1, -1, -1,
+ 48, 49, 50, 51, 52, 53, 54, -1, 55, 56, 57, 58, 59, 60, -1, -1,
+ 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
+
+#define BHEXVAL(c) (binhex_decode[(unsigned char) c])
+
+//////////////////////////////////////////////////////
+// nsIStreamConverter methods...
+//////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsBinHexDecoder::Convert(nsIInputStream *aFromStream,
+ const char *aFromType,
+ const char *aToType,
+ nsISupports *aCtxt,
+ nsIInputStream **aResultStream)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBinHexDecoder::AsyncConvertData(const char *aFromType,
+ const char *aToType,
+ nsIStreamListener *aListener,
+ nsISupports *aCtxt)
+{
+ NS_ASSERTION(aListener && aFromType && aToType,
+ "null pointer passed into bin hex converter");
+
+ // hook up our final listener. this guy gets the various On*() calls we want to throw
+ // at him.
+ //
+ mNextListener = aListener;
+ return (aListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//////////////////////////////////////////////////////
+// nsIStreamListener methods...
+//////////////////////////////////////////////////////
+NS_IMETHODIMP
+nsBinHexDecoder::OnDataAvailable(nsIRequest* request,
+ nsISupports *aCtxt,
+ nsIInputStream *aStream,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ nsresult rv = NS_OK;
+
+ if (mOutputStream && mDataBuffer && aCount > 0)
+ {
+ uint32_t numBytesRead = 0;
+ while (aCount > 0) // while we still have bytes to copy...
+ {
+ aStream->Read(mDataBuffer, std::min(aCount, nsIOService::gDefaultSegmentSize - 1), &numBytesRead);
+ if (aCount >= numBytesRead)
+ aCount -= numBytesRead; // subtract off the number of bytes we just read
+ else
+ aCount = 0;
+
+ // Process this new chunk of bin hex data...
+ ProcessNextChunk(request, aCtxt, numBytesRead);
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsBinHexDecoder::ProcessNextState(nsIRequest * aRequest, nsISupports * aContext)
+{
+ nsresult status = NS_OK;
+ uint16_t tmpcrc, cval;
+ unsigned char ctmp, c = mRlebuf;
+
+ /* do CRC */
+ ctmp = mInCRC ? c : 0;
+ cval = mCRC & 0xf000;
+ tmpcrc = ((uint16_t) (mCRC << 4) | (ctmp >> 4)) ^ (cval | (cval >> 7) | (cval >> 12));
+ cval = tmpcrc & 0xf000;
+ mCRC = ((uint16_t) (tmpcrc << 4) | (ctmp & 0x0f)) ^ (cval | (cval >> 7) | (cval >> 12));
+
+ /* handle state */
+ switch (mState)
+ {
+ case BINHEX_STATE_START:
+ mState = BINHEX_STATE_FNAME;
+ mCount = 0;
+
+ // c & 63 returns the length of mName. So if we need the length, that's how
+ // you can figure it out....
+ mName.SetLength(c & 63);
+ if (mName.Length() != (c & 63)) {
+ /* XXX ProcessNextState/ProcessNextChunk aren't rv checked */
+ mState = BINHEX_STATE_DONE;
+ }
+ break;
+
+ case BINHEX_STATE_FNAME:
+ if (mCount < mName.Length()) {
+ mName.BeginWriting()[mCount] = c;
+ }
+
+ if (++mCount > mName.Length())
+ {
+ // okay we've figured out the file name....set the content type on the channel
+ // based on the file name AND issue our delayed on start request....
+
+ DetectContentType(aRequest, mName);
+ // now propagate the on start request
+ mNextListener->OnStartRequest(aRequest, aContext);
+
+ mState = BINHEX_STATE_HEADER;
+ mCount = 0;
+ }
+ break;
+
+ case BINHEX_STATE_HEADER:
+ ((char *) &mHeader)[mCount] = c;
+ if (++mCount == 18)
+ {
+ if (sizeof(binhex_header) != 18) /* fix an alignment problem in some OSes */
+ {
+ char *p = (char *)&mHeader;
+ p += 19;
+ for (c = 0; c < 8; c++)
+ {
+ *p = *(p-2);
+ --p;
+ }
+ }
+
+ mState = BINHEX_STATE_HCRC;
+ mInCRC = 1;
+ mCount = 0;
+ }
+ break;
+
+ case BINHEX_STATE_DFORK:
+ case BINHEX_STATE_RFORK:
+ mOutgoingBuffer[mPosOutputBuff++] = c;
+ if (--mCount == 0)
+ {
+ /* only output data fork in the non-mac system. */
+ if (mState == BINHEX_STATE_DFORK)
+ {
+ uint32_t numBytesWritten = 0;
+ mOutputStream->Write(mOutgoingBuffer, mPosOutputBuff, &numBytesWritten);
+ if (int32_t(numBytesWritten) != mPosOutputBuff)
+ status = NS_ERROR_FAILURE;
+
+ // now propagate the data we just wrote
+ mNextListener->OnDataAvailable(aRequest, aContext, mInputStream, 0, numBytesWritten);
+ }
+ else
+ status = NS_OK; /* do nothing for resource fork. */
+
+ mPosOutputBuff = 0;
+
+ if (status != NS_OK)
+ mState = BINHEX_STATE_DONE;
+ else
+ ++mState;
+
+ mInCRC = 1;
+ }
+ else if (mPosOutputBuff >= (int32_t) nsIOService::gDefaultSegmentSize)
+ {
+ if (mState == BINHEX_STATE_DFORK)
+ {
+ uint32_t numBytesWritten = 0;
+ mOutputStream->Write(mOutgoingBuffer, mPosOutputBuff, &numBytesWritten);
+ if (int32_t(numBytesWritten) != mPosOutputBuff)
+ status = NS_ERROR_FAILURE;
+
+ mNextListener->OnDataAvailable(aRequest, aContext, mInputStream, 0, numBytesWritten);
+ mPosOutputBuff = 0;
+ }
+ }
+ break;
+
+ case BINHEX_STATE_HCRC:
+ case BINHEX_STATE_DCRC:
+ case BINHEX_STATE_RCRC:
+ if (!mCount++)
+ mFileCRC = (unsigned short) c << 8;
+ else
+ {
+ if ((mFileCRC | c) != mCRC)
+ {
+ mState = BINHEX_STATE_DONE;
+ break;
+ }
+
+ /* passed the CRC check!!!*/
+ mCRC = 0;
+ if (++mState == BINHEX_STATE_FINISH)
+ {
+ // when we reach the finished state...fire an on stop request on the event listener...
+ mNextListener->OnStopRequest(aRequest, aContext, NS_OK);
+ mNextListener = nullptr;
+
+ /* now We are done with everything. */
+ ++mState;
+ break;
+ }
+
+ if (mState == BINHEX_STATE_DFORK)
+ mCount = PR_ntohl(mHeader.dlen);
+ else
+ {
+ // we aren't processing the resurce Fork. uncomment this line if we make this converter
+ // smart enough to do this in the future.
+ // mCount = PR_ntohl(mHeader.rlen); /* it should in host byte order */
+ mCount = 0;
+ }
+
+ if (mCount) {
+ mInCRC = 0;
+ } else {
+ /* nothing inside, so skip to the next state. */
+ ++mState;
+ }
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsBinHexDecoder::ProcessNextChunk(nsIRequest * aRequest, nsISupports * aContext, uint32_t numBytesInBuffer)
+{
+ bool foundStart;
+ int16_t octetpos, c = 0;
+ uint32_t val;
+ mPosInDataBuffer = 0; // use member variable.
+
+ NS_ENSURE_TRUE(numBytesInBuffer > 0, NS_ERROR_FAILURE);
+
+ // if it is the first time, seek to the right start place.
+ if (mState == BINHEX_STATE_START)
+ {
+ foundStart = false;
+ // go through the line, until we get a ':'
+ while (mPosInDataBuffer < numBytesInBuffer)
+ {
+ c = mDataBuffer[mPosInDataBuffer++];
+ while (c == nsCRT::CR || c == nsCRT::LF)
+ {
+ if (mPosInDataBuffer >= numBytesInBuffer)
+ break;
+
+ c = mDataBuffer[mPosInDataBuffer++];
+ if (c == ':')
+ {
+ foundStart = true;
+ break;
+ }
+ }
+ if (foundStart) break; /* we got the start point. */
+ }
+
+ if (mPosInDataBuffer >= numBytesInBuffer)
+ return NS_OK; /* we meet buff end before we get the start point, wait till next fills. */
+
+ if (c != ':')
+ return NS_ERROR_FAILURE; /* can't find the start character. */
+ }
+
+ while (mState != BINHEX_STATE_DONE)
+ {
+ /* fill in octetbuf */
+ do
+ {
+ if (mPosInDataBuffer >= numBytesInBuffer)
+ return NS_OK; /* end of buff, go on for the nxet calls. */
+
+ c = GetNextChar(numBytesInBuffer);
+ if (c == 0) return NS_OK;
+
+ if ((val = BHEXVAL(c)) == uint32_t(-1))
+ {
+ /* we incount an invalid character. */
+ if (c)
+ {
+ /* rolling back. */
+ --mDonePos;
+ if (mOctetin >= 14)
+ --mDonePos;
+ if (mOctetin >= 20)
+ --mDonePos;
+ }
+ break;
+ }
+ mOctetBuf.val |= val << mOctetin;
+ }
+ while ((mOctetin -= 6) > 2);
+
+ /* handle decoded characters -- run length encoding (rle) detection */
+
+ // We put decoded chars into mOctetBuf.val in order from high to low (via
+ // bitshifting, above). But we want to byte-address them, so we want the
+ // first byte to correspond to the high byte. In other words, we want
+ // these bytes to be in network order.
+ mOctetBuf.val = PR_htonl(mOctetBuf.val);
+
+ for (octetpos = 0; octetpos < mDonePos; ++octetpos)
+ {
+ c = mOctetBuf.c[octetpos];
+
+ if (c == 0x90 && !mMarker++)
+ continue;
+
+ if (mMarker)
+ {
+ if (c == 0)
+ {
+ mRlebuf = 0x90;
+ ProcessNextState(aRequest, aContext);
+ }
+ else
+ {
+ /* we are in the run length mode */
+ while (--c > 0)
+ ProcessNextState(aRequest, aContext);
+ }
+ mMarker = 0;
+ }
+ else
+ {
+ mRlebuf = (unsigned char) c;
+ ProcessNextState(aRequest, aContext);
+ }
+
+ if (mState >= BINHEX_STATE_DONE)
+ break;
+ }
+
+ /* prepare for next 3 characters. */
+ if (mDonePos < 3 && mState < BINHEX_STATE_DONE)
+ mState = BINHEX_STATE_DONE;
+
+ mOctetin = 26;
+ mOctetBuf.val = 0;
+ }
+
+ return NS_OK;
+}
+
+int16_t nsBinHexDecoder::GetNextChar(uint32_t numBytesInBuffer)
+{
+ char c = 0;
+
+ while (mPosInDataBuffer < numBytesInBuffer)
+ {
+ c = mDataBuffer[mPosInDataBuffer++];
+ if (c != nsCRT::LF && c != nsCRT::CR)
+ break;
+ }
+ return (c == nsCRT::LF || c == nsCRT::CR) ? 0 : (int) c;
+}
+
+//////////////////////////////////////////////////////
+// nsIRequestObserver methods...
+//////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsBinHexDecoder::OnStartRequest(nsIRequest* request, nsISupports *aCtxt)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_TRUE(mNextListener, NS_ERROR_FAILURE);
+
+ mDataBuffer = (char *) malloc((sizeof(char) * nsIOService::gDefaultSegmentSize));
+ mOutgoingBuffer = (char *) malloc((sizeof(char) * nsIOService::gDefaultSegmentSize));
+ if (!mDataBuffer || !mOutgoingBuffer) return NS_ERROR_FAILURE; // out of memory;
+
+ // now we want to create a pipe which we'll use to write our converted data...
+ rv = NS_NewPipe(getter_AddRefs(mInputStream), getter_AddRefs(mOutputStream),
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentSize,
+ true, true);
+
+ // don't propagate the on start request to mNextListener until we have determined the content type.
+ return rv;
+}
+
+// Given the fileName we discovered inside the bin hex decoding, figure out the
+// content type and set it on the channel associated with the request. If the
+// filename tells us nothing useful, just report an unknown type and let the
+// unknown decoder handle things.
+nsresult nsBinHexDecoder::DetectContentType(nsIRequest* aRequest,
+ const nsAFlatCString &aFilename)
+{
+ if (aFilename.IsEmpty()) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contentType;
+
+ // extract the extension from aFilename and look it up.
+ const char * fileExt = strrchr(aFilename.get(), '.');
+ if (!fileExt) {
+ return NS_OK;
+ }
+
+ mimeService->GetTypeFromExtension(nsDependentCString(fileExt), contentType);
+
+ // Only set the type if it's not empty and, to prevent recursive loops, not the binhex type
+ if (!contentType.IsEmpty() && !contentType.Equals(APPLICATION_BINHEX)) {
+ channel->SetContentType(contentType);
+ } else {
+ channel->SetContentType(NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE));
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsBinHexDecoder::OnStopRequest(nsIRequest* request, nsISupports *aCtxt,
+ nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+
+ if (!mNextListener) return NS_ERROR_FAILURE;
+ // don't do anything here...we'll fire our own on stop request when we are done
+ // processing the data....
+
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/streamconv/converters/nsBinHexDecoder.h b/netwerk/streamconv/converters/nsBinHexDecoder.h
new file mode 100644
index 0000000000..01d9b5c155
--- /dev/null
+++ b/netwerk/streamconv/converters/nsBinHexDecoder.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/. */
+
+// A decoder for Mac Bin Hex 4.0.
+
+// This decoder is currently only intended to be used on NON-Mac platforms. It isn't hooked up to
+// code which would actually save the file to disk. As a result, we can't leverage the resource fork.
+// This makes this decoder most unhelpful for the Mac. Our assumption is that if you save a bin hex file
+// on the mac and try to open it, stuffit or some other tool is already going to be on the Mac which knows how
+// to handle bin hex. On windows and unix, that's not the case. We need client code to strip out the data fork.
+// So this decoder currently just strips out the data fork.
+
+// Note: it's possible that we can eventually turn this decoder into both a decoder and into a file stream (much
+// like the apple double decoder) so on the Mac, if we are saving to disk, we can invoke the decoder as a file stream
+// and it will process the resource fork and do the right magic.
+
+#ifndef nsBinHexDecoder_h__
+#define nsBinHexDecoder_h__
+
+#include "nsIStreamConverter.h"
+#include "nsIChannel.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#define NS_BINHEXDECODER_CID \
+{ /* 301DEA42-6850-4cda-8945-81F7DBC2186B */ \
+ 0x301dea42, 0x6850, 0x4cda, \
+ { 0x89, 0x45, 0x81, 0xf7, 0xdb, 0xc2, 0x18, 0x6b } \
+}
+
+namespace mozilla {
+namespace net {
+
+typedef struct _binhex_header
+{
+ uint32_t type, creator;
+ uint16_t flags;
+ int32_t dlen, rlen;
+} binhex_header;
+
+typedef union
+{
+ unsigned char c[4];
+ uint32_t val;
+} longbuf;
+
+#define BINHEX_STATE_START 0
+#define BINHEX_STATE_FNAME 1
+#define BINHEX_STATE_HEADER 2
+#define BINHEX_STATE_HCRC 3
+#define BINHEX_STATE_DFORK 4
+#define BINHEX_STATE_DCRC 5
+#define BINHEX_STATE_RFORK 6
+#define BINHEX_STATE_RCRC 7
+#define BINHEX_STATE_FINISH 8
+#define BINHEX_STATE_DONE 9
+/* #define BINHEX_STATE_ERROR 10 */
+
+class nsBinHexDecoder : public nsIStreamConverter
+{
+public:
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ // nsIStreamConverter methods
+ NS_DECL_NSISTREAMCONVERTER
+
+ // nsIStreamListener methods
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIRequestObserver methods
+ NS_DECL_NSIREQUESTOBSERVER
+
+ nsBinHexDecoder();
+
+protected:
+ virtual ~nsBinHexDecoder();
+
+ int16_t GetNextChar(uint32_t numBytesInBuffer);
+ nsresult ProcessNextChunk(nsIRequest * aRequest, nsISupports * aContext, uint32_t numBytesInBuffer);
+ nsresult ProcessNextState(nsIRequest * aRequest, nsISupports * aContext);
+ nsresult DetectContentType(nsIRequest * aRequest, const nsAFlatCString &aFilename);
+
+protected:
+ nsCOMPtr<nsIStreamListener> mNextListener;
+
+ // the input and output streams form a pipe...they need to be passed around together..
+ nsCOMPtr<nsIOutputStream> mOutputStream; // output stream
+ nsCOMPtr<nsIInputStream> mInputStream;
+
+ int16_t mState; /* current state */
+ uint16_t mCRC; /* cumulative CRC */
+ uint16_t mFileCRC; /* CRC value from file */
+ longbuf mOctetBuf; /* buffer for decoded 6-bit values */
+ int16_t mOctetin; /* current input position in octetbuf */
+ int16_t mDonePos; /* ending position in octetbuf */
+ int16_t mInCRC; /* flag set when reading a CRC */
+
+ // Bin Hex Header Information
+ binhex_header mHeader;
+ nsCString mName; /* fsspec for the output file */
+
+ // unfortunately we are going to need 2 8K buffers here. One for the data we are currently digesting. Another
+ // for the outgoing decoded data. I tried getting them to share a buffer but things didn't work out so nicely.
+ char * mDataBuffer; // temporary holding pen for the incoming data.
+ char * mOutgoingBuffer; // temporary holding pen for the incoming data.
+ uint32_t mPosInDataBuffer;
+
+ unsigned char mRlebuf; /* buffer for last run length encoding value */
+
+ uint32_t mCount; /* generic counter */
+ int16_t mMarker; /* flag indicating maker */
+
+ int32_t mPosInbuff; /* the index of the inbuff. */
+ int32_t mPosOutputBuff; /* the position of the out buff. */
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* nsBinHexDecoder_h__ */
diff --git a/netwerk/streamconv/converters/nsDirIndex.cpp b/netwerk/streamconv/converters/nsDirIndex.cpp
new file mode 100644
index 0000000000..0ad77805ac
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndex.cpp
@@ -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 "nsDirIndex.h"
+
+NS_IMPL_ISUPPORTS(nsDirIndex,
+ nsIDirIndex)
+
+nsDirIndex::nsDirIndex() : mType(TYPE_UNKNOWN),
+ mSize(UINT64_MAX),
+ mLastModified(-1LL)
+{
+}
+
+nsDirIndex::~nsDirIndex() {}
+
+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(char* *aContentType)
+{
+ NS_ENSURE_ARG_POINTER(aContentType);
+
+ *aContentType = ToNewCString(mContentType);
+ if (!*aContentType)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetContentType(const char* aContentType)
+{
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::GetLocation(char* *aLocation)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ *aLocation = ToNewCString(mLocation);
+ if (!*aLocation)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetLocation(const char* aLocation)
+{
+ mLocation = aLocation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::GetDescription(char16_t* *aDescription)
+{
+ NS_ENSURE_ARG_POINTER(aDescription);
+
+ *aDescription = ToNewUnicode(mDescription);
+ if (!*aDescription)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetDescription(const char16_t* aDescription)
+{
+ mDescription.Assign(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..b4b3b3c568
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndex.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDirIndex.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+/* CID: {f6913e2e-1dd1-11b2-84be-f455dee342af} */
+
+class nsDirIndex final : public nsIDirIndex {
+
+private:
+ ~nsDirIndex();
+
+public:
+ nsDirIndex();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDIRINDEX
+
+protected:
+ uint32_t mType;
+ nsXPIDLCString mContentType;
+ nsXPIDLCString mLocation;
+ nsString mDescription;
+ int64_t mSize;
+ PRTime mLastModified;
+};
diff --git a/netwerk/streamconv/converters/nsDirIndexParser.cpp b/netwerk/streamconv/converters/nsDirIndexParser.cpp
new file mode 100644
index 0000000000..1f4bc78cb0
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndexParser.cpp
@@ -0,0 +1,427 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/ArrayUtils.h"
+
+#include "prprf.h"
+
+#include "nsDirIndexParser.h"
+#include "nsEscape.h"
+#include "nsIInputStream.h"
+#include "nsCRT.h"
+#include "mozilla/dom/FallbackEncoding.h"
+#include "nsITextToSubURI.h"
+#include "nsIDirIndex.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsDirIndexParser,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIDirIndexParser)
+
+nsDirIndexParser::nsDirIndexParser() {
+}
+
+nsresult
+nsDirIndexParser::Init() {
+ mLineStart = 0;
+ mHasDescription = false;
+ mFormat[0] = -1;
+ mozilla::dom::FallbackEncoding::FromLocale(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);
+
+ 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);
+
+ if (!*aEncoding)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::OnStopRequest(nsIRequest *aRequest, nsISupports *aCtxt,
+ nsresult aStatusCode) {
+ // Finish up
+ if (mBuf.Length() > (uint32_t) mLineStart) {
+ ProcessData(aRequest, aCtxt);
+ }
+
+ 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;
+
+nsresult
+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.SetCapacity(len + 1);
+ 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)));
+
+ return NS_OK;
+}
+
+nsresult
+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 NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ 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 NS_OK;
+ }
+
+ while ((lineLen > 0) && nsCRT::IsAsciiSpace(*aDataStr)) {
+ ++aDataStr;
+ --lineLen;
+ }
+
+ if (lineLen < 1) {
+ // invalid format, bail
+ return NS_OK;
+ }
+
+ 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 NS_OK;
+ }
+ } 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) {
+ char16_t *result = nullptr;
+ if (NS_SUCCEEDED(rv = gTextToSubURI->UnEscapeAndConvert(mEncoding.get(), filename.get(),
+ &result)) && (result)) {
+ if (*result) {
+ aIdx->SetLocation(filename.get());
+ if (!mHasDescription)
+ aIdx->SetDescription(result);
+ success = true;
+ }
+ free(result);
+ } 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.get());
+ if (!mHasDescription) {
+ aIdx->SetDescription(NS_ConvertUTF8toUTF16(value).get());
+ }
+ }
+ }
+ break;
+ case FIELD_DESCRIPTION:
+ nsUnescape(value);
+ aIdx->SetDescription(NS_ConvertUTF8toUTF16(value).get());
+ 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(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;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::OnDataAvailable(nsIRequest *aRequest, nsISupports *aCtxt,
+ 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, aCtxt);
+}
+
+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) {
+ nsresult rv;
+ 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
+ rv = ParseFormat(buf + 4);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else if (buf[2] == '1' && buf[3] == ':') {
+ // 201. Field data
+ nsCOMPtr<nsIDirIndex> idx = do_CreateInstance("@mozilla.org/dirIndex;1",&rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = ParseData(idx, ((char *)buf) + 4, lineLen - 4);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ 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..bc3b67ea4d
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndexParser.h
@@ -0,0 +1,67 @@
+/* -*- 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 __NSDIRINDEX_H_
+#define __NSDIRINDEX_H_
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIDirIndexListener.h"
+
+class nsIDirIndex;
+class nsITextToSubURI;
+
+/* CID: {a0d6ad32-1dd1-11b2-aa55-a40187b54036} */
+
+class nsDirIndexParser : public nsIDirIndexParser {
+
+private:
+ virtual ~nsDirIndexParser();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIDIRINDEXPARSER
+
+ nsDirIndexParser();
+ nsresult Init();
+
+ 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);
+ nsresult ParseFormat(const char* buf);
+ nsresult 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..6e1bb8f34c
--- /dev/null
+++ b/netwerk/streamconv/converters/nsFTPDirListingConv.cpp
@@ -0,0 +1,345 @@
+/* -*- 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 "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 "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;
+}
+
+
+// nsIStreamListener implementation
+NS_IMETHODIMP
+nsFTPDirListingConv::OnDataAvailable(nsIRequest* request, nsISupports *ctxt,
+ 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 = %x, ctxt = %x, inStr = %x, sourceOffset = %llu, count = %u)\n", request, ctxt, 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 %d 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, ctxt, inputData, 0, indexFormat.Length());
+
+ return rv;
+}
+
+
+// nsIRequestObserver implementation
+NS_IMETHODIMP
+nsFTPDirListingConv::OnStartRequest(nsIRequest* request, nsISupports *ctxt) {
+ // we don't care about start. move along... but start masqeurading
+ // as the http-index channel now.
+ return mFinalListener->OnStartRequest(request, ctxt);
+}
+
+NS_IMETHODIMP
+nsFTPDirListingConv::OnStopRequest(nsIRequest* request, nsISupports *ctxt,
+ nsresult aStatus) {
+ // we don't care about stop. move along...
+
+ return mFinalListener->OnStopRequest(request, ctxt, 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()) {
+ rv = uri->SetPassword(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ rv = uri->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+ headers.Append(spec);
+ rv = uri->SetPassword(pw);
+ if (NS_FAILED(rv)) return rv;
+ } 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 (int i = 0; i < int(sizeof(result.fe_size)); ++i)
+ {
+ if (result.fe_size[i] != '\0')
+ aString.Append((const char*)&result.fe_size[i], 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", &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)
+{
+ NS_PRECONDITION(aFTPDirListingConv != nullptr, "null ptr");
+ if (! aFTPDirListingConv)
+ return NS_ERROR_NULL_POINTER;
+
+ *aFTPDirListingConv = new nsFTPDirListingConv();
+ if (! *aFTPDirListingConv)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aFTPDirListingConv);
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/nsFTPDirListingConv.h b/netwerk/streamconv/converters/nsFTPDirListingConv.h
new file mode 100644
index 0000000000..b85877a056
--- /dev/null
+++ b/netwerk/streamconv/converters/nsFTPDirListingConv.h
@@ -0,0 +1,52 @@
+/* -*- 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 __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..37b4b28b00
--- /dev/null
+++ b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp
@@ -0,0 +1,659 @@
+/* -*- 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 "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)
+
+// nsFTPDirListingConv methods
+nsHTTPCompressConv::nsHTTPCompressConv()
+ : mMode(HTTP_COMPRESS_IDENTITY)
+ , mOutBuffer(nullptr)
+ , mInpBuffer(nullptr)
+ , mOutBufferLen(0)
+ , mInpBufferLen(0)
+ , mCheckHeaderDone(false)
+ , mStreamEnded(false)
+ , mStreamInitialized(false)
+ , mLen(0)
+ , hMode(0)
+ , mSkipCount(0)
+ , mFlags(0)
+ , mDecodedDataLength(0)
+{
+ 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, mMode));
+
+ // hook ourself up with the receiving listener.
+ mListener = aListener;
+
+ mAsyncConvContext = aCtxt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::OnStartRequest(nsIRequest* request, nsISupports *aContext)
+{
+ LOG(("nsHttpCompresssConv %p onstart\n", this));
+ return mListener->OnStartRequest(request, aContext);
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsISupports *aContext,
+ nsresult aStatus)
+{
+ nsresult status = aStatus;
+ LOG(("nsHttpCompresssConv %p onstop %x\n", this, 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) && !BrotliStateIsStreamEnd(&mBrotli->mState)) {
+ status = NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+ LOG(("nsHttpCompresssConv %p onstop brotlihandler rv %x\n", this, status));
+ if (fpChannel && !isPending) {
+ fpChannel->ForcePending(false);
+ }
+ }
+ return mListener->OnStopRequest(request, aContext, 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;
+ BrotliResult 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 %d\n", self, avail));
+ res = ::BrotliDecompressStream(
+ &avail, reinterpret_cast<const unsigned char **>(&dataIn),
+ &outSize, &outPtr, &self->mBrotli->mTotalOut, &self->mBrotli->mState);
+ outSize = kOutSize - outSize;
+ LOG(("nsHttpCompresssConv %p brotlihandler decompress rv=%x out=%d\n",
+ self, res, outSize));
+
+ if (res == BROTLI_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_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=%x", self, rv));
+ if (NS_FAILED(rv)) {
+ self->mBrotli->mStatus = rv;
+ return self->mBrotli->mStatus;
+ }
+ }
+
+ if (res == BROTLI_RESULT_SUCCESS ||
+ res == BROTLI_RESULT_NEEDS_MORE_INPUT) {
+ *countRead = aAvail;
+ return NS_OK;
+ }
+ MOZ_ASSERT (res == BROTLI_RESULT_NEEDS_MORE_OUTPUT);
+ } while (res == BROTLI_RESULT_NEEDS_MORE_OUTPUT);
+
+ self->mBrotli->mStatus = NS_ERROR_UNEXPECTED;
+ return self->mBrotli->mStatus;
+}
+
+NS_IMETHODIMP
+nsHTTPCompressConv::OnDataAvailable(nsIRequest* request,
+ nsISupports *aContext,
+ 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;
+ }
+
+ MOZ_FALLTHROUGH;
+
+ case HTTP_COMPRESS_DEFLATE:
+
+ if (mInpBuffer != nullptr && streamLen > mInpBufferLen) {
+ mInpBuffer = (unsigned char *) realloc(mInpBuffer, mInpBufferLen = streamLen);
+
+ if (mOutBufferLen < streamLen * 2) {
+ mOutBuffer = (unsigned char *) realloc(mOutBuffer, mOutBufferLen = streamLen * 3);
+ }
+
+ 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, aContext, 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, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
+ if (NS_FAILED (rv)) {
+ return rv;
+ }
+ }
+ } else if (code == Z_BUF_ERROR) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, aContext, 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, aContext, 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, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
+ if (NS_FAILED (rv)) {
+ return rv;
+ }
+ }
+ } else if (code == Z_BUF_ERROR) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, aContext, 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 = new BrotliWrapper();
+ }
+
+ mBrotli->mRequest = request;
+ mBrotli->mContext = aContext;
+ 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:
+ rv = mListener->OnDataAvailable(request, aContext, 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);
+
+ nsresult rv = mListener->OnDataAvailable(request, context, 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;
+}
+
+} // namespace net
+} // namespace mozilla
+
+nsresult
+NS_NewHTTPCompressConv(mozilla::net::nsHTTPCompressConv **aHTTPCompressConv)
+{
+ NS_PRECONDITION(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..38889a8f71
--- /dev/null
+++ b/netwerk/streamconv/converters/nsHTTPCompressConv.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined (__nsHTTPCompressConv__h__)
+#define __nsHTTPCompressConv__h__ 1
+
+#include "nsIStreamConverter.h"
+#include "nsICompressConvStats.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.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)
+ {
+ BrotliStateInit(&mState);
+ }
+ ~BrotliWrapper()
+ {
+ BrotliStateCleanup(&mState);
+ }
+
+ BrotliState mState;
+ size_t mTotalOut;
+ nsresult mStatus;
+
+ nsIRequest *mRequest;
+ nsISupports *mContext;
+ uint64_t mSourceOffset;
+};
+
+class nsHTTPCompressConv
+ : public nsIStreamConverter
+ , public nsICompressConvStats
+{
+ public:
+ // nsISupports methods
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICOMPRESSCONVSTATS
+
+ // nsIStreamConverter methods
+ NS_DECL_NSISTREAMCONVERTER
+
+ nsHTTPCompressConv ();
+
+private:
+ virtual ~nsHTTPCompressConv ();
+
+ nsCOMPtr<nsIStreamListener> mListener; // this guy gets the converted data via his OnDataAvailable ()
+ CompressMode mMode;
+
+ unsigned char *mOutBuffer;
+ unsigned char *mInpBuffer;
+
+ uint32_t mOutBufferLen;
+ uint32_t mInpBufferLen;
+
+ nsAutoPtr<BrotliWrapper> mBrotli;
+
+ nsCOMPtr<nsISupports> mAsyncConvContext;
+ 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;
+ 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);
+
+ uint32_t mDecodedDataLength;
+};
+
+} // 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..0414c48417
--- /dev/null
+++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp
@@ -0,0 +1,895 @@
+/* -*- 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 "nsIndexedToHTML.h"
+#include "mozilla/dom/EncodingUtils.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 "nsIPlatformCharset.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIChromeRegistry.h"
+#include "nsIDateTimeFormat.h"
+#include "nsIStringBundle.h"
+#include "nsITextToSubURI.h"
+#include "nsXPIDLString.h"
+#include <algorithm>
+#include "nsIChannel.h"
+
+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(';');
+ }
+ }
+}
+
+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;
+
+ mDateTime = nsIDateTimeFormat::Create();
+ if (!mDateTime)
+ return NS_ERROR_FAILURE;
+
+ 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::OnStartRequest(nsIRequest* request, nsISupports *aContext) {
+ nsCString buffer;
+ nsresult rv = DoOnStartRequest(request, aContext, buffer);
+ if (NS_FAILED(rv)) {
+ request->Cancel(rv);
+ }
+
+ rv = mListener->OnStartRequest(request, aContext);
+ 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, aContext, 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->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ channel->SetContentType(NS_LITERAL_CSTRING("text/html"));
+
+ mParser = do_CreateInstance("@mozilla.org/dirIndexParser;1",&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->SetListener(this);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->OnStartRequest(request, aContext);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString baseUri, titleUri;
+ rv = uri->GetAsciiSpec(baseUri);
+ if (NS_FAILED(rv)) return rv;
+ titleUri = baseUri;
+
+ 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
+
+ bool isScheme = false;
+ bool isSchemeFile = false;
+ if (NS_SUCCEEDED(uri->SchemeIs("ftp", &isScheme)) && isScheme) {
+
+ // 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 = uri->GetPassword(pw);
+ if (NS_FAILED(rv)) return rv;
+ if (!pw.IsEmpty()) {
+ nsCOMPtr<nsIURI> newUri;
+ rv = uri->Clone(getter_AddRefs(newUri));
+ if (NS_FAILED(rv)) return rv;
+ rv = newUri->SetPassword(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ rv = newUri->GetAsciiSpec(titleUri);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!path.EqualsLiteral("//") && !path.LowerCaseEqualsLiteral("/%2f")) {
+ rv = uri->Resolve(NS_LITERAL_CSTRING(".."),parentStr);
+ if (NS_FAILED(rv)) return rv;
+ }
+ } else if (NS_SUCCEEDED(uri->SchemeIs("file", &isSchemeFile)) && isSchemeFile) {
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+ file->SetFollowLinks(true);
+
+ 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 (NS_SUCCEEDED(uri->SchemeIs("jar", &isScheme)) && isScheme) {
+ nsAutoCString path;
+ rv = uri->GetPath(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_LITERAL_CSTRING("!/"))) {
+ rv = uri->Resolve(NS_LITERAL_CSTRING(".."), 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->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+ if (baseUri.Last() != '/') {
+ baseUri.Append('/');
+ path.Append('/');
+ uri->SetPath(path);
+ }
+ if (!path.EqualsLiteral("/")) {
+ rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr);
+ 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://gre/res/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.slice(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.slice(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("<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("chrome://global/skin/dirListing/local.png");
+ 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("chrome://global/skin/dirListing/remote.png");
+ 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;
+ }
+
+ nsXPIDLCString encoding;
+ rv = uri->GetOriginCharset(encoding);
+ if (NS_FAILED(rv)) return rv;
+ if (encoding.IsEmpty()) {
+ encoding.AssignLiteral("UTF-8");
+ }
+
+ nsXPIDLString unEscapeSpec;
+ rv = mTextToSubURI->UnEscapeAndConvert(encoding, titleUri.get(),
+ getter_Copies(unEscapeSpec));
+ // unescape may fail because
+ // 1. file URL may be encoded in platform charset for backward compatibility
+ // 2. query part may not be encoded in UTF-8 (see bug 261929)
+ // so try the platform's default if this is file url
+ if (NS_FAILED(rv) && isSchemeFile) {
+ nsCOMPtr<nsIPlatformCharset> platformCharset(do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString charset;
+ rv = platformCharset->GetCharset(kPlatformCharsetSel_FileName, charset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mTextToSubURI->UnEscapeAndConvert(charset.get(), titleUri.get(),
+ getter_Copies(unEscapeSpec));
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsXPIDLString htmlEscSpec;
+ htmlEscSpec.Adopt(nsEscapeHTML2(unEscapeSpec.get(),
+ unEscapeSpec.Length()));
+
+ nsXPIDLString title;
+ const char16_t* formatTitle[] = {
+ htmlEscSpec.get()
+ };
+
+ rv = mBundle->FormatStringFromName(u"DirTitle",
+ formatTitle,
+ sizeof(formatTitle)/sizeof(char16_t*),
+ getter_Copies(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.
+ nsCOMPtr<nsIURI> originalUri;
+ rv = channel->GetOriginalURI(getter_AddRefs(originalUri));
+ bool wasResource = false;
+ if (NS_FAILED(rv) ||
+ NS_FAILED(originalUri->SchemeIs("resource", &wasResource)) ||
+ !wasResource) {
+ buffer.AppendLiteral("<base href=\"");
+ nsAdoptingCString htmlEscapedUri(nsEscapeHTML(baseUri.get()));
+ buffer.Append(htmlEscapedUri);
+ buffer.AppendLiteral("\" />\n");
+ }
+ }
+ else
+ {
+ NS_ERROR("broken protocol handler didn't escape double-quote.");
+ }
+
+ nsCString direction(NS_LITERAL_CSTRING("ltr"));
+ nsCOMPtr<nsIXULChromeRegistry> reg =
+ mozilla::services::GetXULChromeRegistryService();
+ if (reg) {
+ bool isRTL = false;
+ reg->IsLocaleRTL(NS_LITERAL_CSTRING("global"), &isRTL);
+ if (isRTL) {
+ direction.AssignLiteral("rtl");
+ }
+ }
+
+ buffer.AppendLiteral("</head>\n<body dir=\"");
+ buffer.Append(direction);
+ buffer.AppendLiteral("\">\n<h1>");
+
+ const char16_t* formatHeading[] = {
+ htmlEscSpec.get()
+ };
+
+ rv = mBundle->FormatStringFromName(u"DirTitle",
+ formatHeading,
+ sizeof(formatHeading)/sizeof(char16_t*),
+ getter_Copies(title));
+ if (NS_FAILED(rv)) return rv;
+
+ AppendNonAsciiToNCR(title, buffer);
+ buffer.AppendLiteral("</h1>\n");
+
+ if (!parentStr.IsEmpty()) {
+ nsXPIDLString parentText;
+ rv = mBundle->GetStringFromName(u"DirGoUp",
+ getter_Copies(parentText));
+ if (NS_FAILED(rv)) return rv;
+
+ buffer.AppendLiteral("<p id=\"UI_goUp\"><a class=\"up\" href=\"");
+
+ nsAdoptingCString htmlParentStr(nsEscapeHTML(parentStr.get()));
+ buffer.Append(htmlParentStr);
+ buffer.AppendLiteral("\">");
+ AppendNonAsciiToNCR(parentText, buffer);
+ buffer.AppendLiteral("</a></p>\n");
+ }
+
+ if (isSchemeFile) {
+ nsXPIDLString showHiddenText;
+ rv = mBundle->GetStringFromName(u"ShowHidden",
+ getter_Copies(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");
+
+ nsXPIDLString columnText;
+
+ buffer.AppendLiteral(" <thead>\n"
+ " <tr>\n"
+ " <th>");
+
+ rv = mBundle->GetStringFromName(u"DirColName",
+ getter_Copies(columnText));
+ if (NS_FAILED(rv)) return rv;
+ AppendNonAsciiToNCR(columnText, buffer);
+ buffer.AppendLiteral("</th>\n"
+ " <th>");
+
+ rv = mBundle->GetStringFromName(u"DirColSize",
+ getter_Copies(columnText));
+ if (NS_FAILED(rv)) return rv;
+ AppendNonAsciiToNCR(columnText, buffer);
+ buffer.AppendLiteral("</th>\n"
+ " <th colspan=\"2\">");
+
+ rv = mBundle->GetStringFromName(u"DirColMTime",
+ getter_Copies(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, nsISupports *aContext,
+ nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ nsCString buffer;
+ buffer.AssignLiteral("</tbody></table></body></html>\n");
+
+ aStatus = SendToListener(request, aContext, buffer);
+ }
+
+ mParser->OnStopRequest(request, aContext, aStatus);
+ mParser = nullptr;
+
+ return mListener->OnStopRequest(request, aContext, 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, aContext,
+ inputData, 0, aBuffer.Length());
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aCtxt,
+ nsIInputStream* aInput,
+ uint64_t aOffset,
+ uint32_t aCount) {
+ return mParser->OnDataAvailable(aRequest, aCtxt, aInput, aOffset, aCount);
+}
+
+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.
+ nsXPIDLCString loc;
+ aIndex->GetLocation(getter_Copies(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;
+ }
+ nsAdoptingCString escaped(nsEscapeHTML(loc));
+ pushBuffer.Append(escaped);
+
+ pushBuffer.AppendLiteral("\"><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");
+ nsAdoptingCString htmlEscapedURL(nsEscapeHTML(locEscaped.get()));
+ pushBuffer.Append(htmlEscapedURL);
+
+ 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);
+ nsAdoptingCString htmlFileExt(nsEscapeHTML(locEscaped.get()));
+ pushBuffer.Append(htmlFileExt);
+ } else {
+ pushBuffer.AppendLiteral("unknown");
+ }
+ pushBuffer.AppendLiteral("?size=16\" alt=\"");
+
+ nsXPIDLString altText;
+ rv = mBundle->GetStringFromName(u"DirFileLabel",
+ getter_Copies(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;
+ mDateTime->FormatPRTime(nullptr,
+ kDateFormatShort,
+ kTimeFormatNone,
+ t,
+ formatted);
+ AppendNonAsciiToNCR(formatted, pushBuffer);
+ pushBuffer.AppendLiteral("</td>\n <td>");
+ mDateTime->FormatPRTime(nullptr,
+ kDateFormatNone,
+ kTimeFormatSeconds,
+ 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;
+ nsAdoptingString escaped(nsEscapeHTML2(PromiseFlatString(aInfo).get()));
+ if (!escaped)
+ return NS_ERROR_OUT_OF_MEMORY;
+ 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(escaped, 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");
+ }
+}
+
+nsIndexedToHTML::nsIndexedToHTML() {
+}
+
+nsIndexedToHTML::~nsIndexedToHTML() {
+}
diff --git a/netwerk/streamconv/converters/nsIndexedToHTML.h b/netwerk/streamconv/converters/nsIndexedToHTML.h
new file mode 100644
index 0000000000..6f0fe9479c
--- /dev/null
+++ b/netwerk/streamconv/converters/nsIndexedToHTML.h
@@ -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/. */
+
+#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 nsIDateTimeFormat;
+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<nsIDateTimeFormat> mDateTime;
+ nsCOMPtr<nsIStringBundle> mBundle;
+
+ nsCOMPtr<nsITextToSubURI> mTextToSubURI;
+
+private:
+ // Expecting absolute locations, given by 201 lines.
+ bool mExpectAbsLoc;
+
+ virtual ~nsIndexedToHTML();
+};
+
+#endif
+
diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.cpp b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
new file mode 100644
index 0000000000..4ebd61d9c2
--- /dev/null
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -0,0 +1,1121 @@
+/* -*- 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 "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 "nsICacheInfoChannel.h"
+#include <algorithm>
+#include "nsContentSecurityManager.h"
+#include "nsHttp.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "nsHttpHeaderArray.h"
+
+//
+// Helper function for determining the length of data bytes up to
+// the next multipart token. A token is usually preceded by a LF
+// or CRLF delimiter.
+//
+static uint32_t
+LengthToToken(const char *cursor, const char *token)
+{
+ uint32_t len = token - cursor;
+ // Trim off any LF or CRLF preceding the token
+ if (len && *(token-1) == '\n') {
+ --len;
+ if (len && *(token-2) == '\r')
+ --len;
+ }
+ return len;
+}
+
+nsPartChannel::nsPartChannel(nsIChannel *aMultipartChannel, uint32_t aPartID,
+ nsIStreamListener* aListener) :
+ mMultipartChannel(aMultipartChannel),
+ mListener(aListener),
+ mStatus(NS_OK),
+ 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));
+}
+
+nsPartChannel::~nsPartChannel()
+{
+}
+
+void nsPartChannel::InitializeByteRange(int64_t aStart, int64_t aEnd)
+{
+ mIsByteRangeRequest = true;
+
+ mByteRangeStart = aStart;
+ mByteRangeEnd = aEnd;
+}
+
+nsresult nsPartChannel::SendOnStartRequest(nsISupports* aContext)
+{
+ return mListener->OnStartRequest(this, aContext);
+}
+
+nsresult nsPartChannel::SendOnDataAvailable(nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aLen)
+{
+ return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aLen);
+}
+
+nsresult nsPartChannel::SendOnStopRequest(nsISupports* aContext,
+ nsresult aStatus)
+{
+ // Drop the listener
+ nsCOMPtr<nsIStreamListener> listener;
+ listener.swap(mListener);
+ return listener->OnStopRequest(this, aContext, aStatus);
+}
+
+void nsPartChannel::SetContentDisposition(const nsACString& aContentDispositionHeader)
+{
+ mContentDispositionHeader = aContentDispositionHeader;
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+ NS_GetFilenameFromDisposition(mContentDispositionFilename,
+ mContentDispositionHeader, uri);
+ 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::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 **result)
+{
+ // This channel cannot be opened!
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPartChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsPartChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
+{
+ // This channel cannot be opened!
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPartChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+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::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)
+{
+ 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;
+}
+
+// AutoFree implementation to prevent memory leaks
+class AutoFree
+{
+public:
+ AutoFree() : mBuffer(nullptr) {}
+
+ explicit AutoFree(char *buffer) : mBuffer(buffer) {}
+
+ ~AutoFree() {
+ free(mBuffer);
+ }
+
+ AutoFree& operator=(char *buffer) {
+ mBuffer = buffer;
+ return *this;
+ }
+
+ operator char*() const {
+ return mBuffer;
+ }
+private:
+ char *mBuffer;
+};
+
+// nsIStreamListener implementation
+NS_IMETHODIMP
+nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context,
+ nsIInputStream *inStr, uint64_t sourceOffset,
+ uint32_t count) {
+ nsresult rv = NS_OK;
+ AutoFree buffer(nullptr);
+ uint32_t bufLen = 0, read = 0;
+
+ NS_ASSERTION(request, "multimixed converter needs a request");
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // fill buffer
+ {
+ bufLen = count + mBufLen;
+ NS_ENSURE_TRUE((bufLen >= count) && (bufLen >= mBufLen),
+ NS_ERROR_FAILURE);
+ buffer = (char *) malloc(bufLen);
+ if (!buffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (mBufLen) {
+ // incorporate any buffered data into the parsing
+ memcpy(buffer, mBuffer, mBufLen);
+ free(mBuffer);
+ mBuffer = 0;
+ mBufLen = 0;
+ }
+
+ rv = inStr->Read(buffer + (bufLen - count), count, &read);
+
+ if (NS_FAILED(rv) || read == 0) return rv;
+ NS_ASSERTION(read == count, "poor data size assumption");
+ }
+
+ char *cursor = buffer;
+
+ if (mFirstOnData) {
+ // this is the first OnData() for this request. some servers
+ // don't bother sending a token in the first "part." This is
+ // illegal, but we'll handle the case anyway by shoving the
+ // boundary token in for the server.
+ mFirstOnData = false;
+ NS_ASSERTION(!mBufLen, "this is our first time through, we can't have buffered data");
+ const char * token = mToken.get();
+
+ PushOverLine(cursor, bufLen);
+
+ bool needMoreChars = bufLen < mTokenLen + 2;
+ nsAutoCString firstBuffer(buffer, bufLen);
+ int32_t posCR = firstBuffer.Find("\r");
+
+ if (needMoreChars || (posCR == kNotFound)) {
+ // we don't have enough data yet to make this comparison.
+ // skip this check, and try again the next time OnData()
+ // is called.
+ mFirstOnData = true;
+ } else if (!PL_strnstr(cursor, token, mTokenLen + 2)) {
+ char *newBuffer = (char *) realloc(buffer, bufLen + mTokenLen + 1);
+ if (!newBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+ buffer = newBuffer;
+
+ memmove(buffer + mTokenLen + 1, buffer, bufLen);
+ memcpy(buffer, token, mTokenLen);
+ buffer[mTokenLen] = '\n';
+
+ bufLen += (mTokenLen + 1);
+
+ // need to reset cursor to the buffer again (bug 100595)
+ cursor = buffer;
+ }
+ }
+
+ char *token = nullptr;
+
+ if (mProcessingHeaders) {
+ // we were not able to process all the headers
+ // for this "part" given the previous buffer given to
+ // us in the previous OnDataAvailable callback.
+ bool done = false;
+ rv = ParseHeaders(channel, cursor, bufLen, &done);
+ if (NS_FAILED(rv)) return rv;
+
+ if (done) {
+ mProcessingHeaders = false;
+ rv = SendStart(channel);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ int32_t tokenLinefeed = 1;
+ while ( (token = FindToken(cursor, bufLen)) ) {
+
+ if (((token + mTokenLen + 1) < (cursor + bufLen)) &&
+ (*(token + mTokenLen + 1) == '-')) {
+ // This was the last delimiter so we can stop processing
+ rv = SendData(cursor, LengthToToken(cursor, token));
+ if (NS_FAILED(rv)) return rv;
+ if (mPartChannel) {
+ mPartChannel->SetIsLastPart();
+ }
+ return SendStop(NS_OK);
+ }
+
+ if (!mNewPart && token > cursor) {
+ // headers are processed, we're pushing data now.
+ NS_ASSERTION(!mProcessingHeaders, "we should be pushing raw data");
+ rv = SendData(cursor, LengthToToken(cursor, token));
+ bufLen -= token - cursor;
+ if (NS_FAILED(rv)) return rv;
+ }
+ // XXX else NS_ASSERTION(token == cursor, "?");
+ token += mTokenLen;
+ bufLen -= mTokenLen;
+ tokenLinefeed = PushOverLine(token, bufLen);
+
+ if (mNewPart) {
+ // parse headers
+ mNewPart = false;
+ cursor = token;
+ bool done = false;
+ rv = ParseHeaders(channel, cursor, bufLen, &done);
+ if (NS_FAILED(rv)) return rv;
+
+ if (done) {
+ rv = SendStart(channel);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // we haven't finished processing header info.
+ // we'll break out and try to process later.
+ mProcessingHeaders = true;
+ break;
+ }
+ }
+ else {
+ mNewPart = true;
+ // Reset state so we don't carry it over from part to part
+ mContentType.Truncate();
+ mContentLength = UINT64_MAX;
+ mContentDisposition.Truncate();
+ mIsByteRangeRequest = false;
+ mByteRangeStart = 0;
+ mByteRangeEnd = 0;
+
+ rv = SendStop(NS_OK);
+ if (NS_FAILED(rv)) return rv;
+ // reset the token to front. this allows us to treat
+ // the token as a starting token.
+ token -= mTokenLen + tokenLinefeed;
+ bufLen += mTokenLen + tokenLinefeed;
+ cursor = token;
+ }
+ }
+
+ // at this point, we want to buffer up whatever amount (bufLen)
+ // we have leftover. However, we *always* want to ensure that
+ // we buffer enough data to handle a broken token.
+
+ // carry over
+ uint32_t bufAmt = 0;
+ if (mProcessingHeaders)
+ bufAmt = bufLen;
+ else if (bufLen) {
+ // if the data ends in a linefeed, and we're in the middle
+ // of a "part" (ie. mPartChannel exists) don't bother
+ // buffering, go ahead and send the data we have. Otherwise
+ // if we don't have a channel already, then we don't even
+ // have enough info to start a part, go ahead and buffer
+ // enough to collect a boundary token.
+ if (!mPartChannel || !(cursor[bufLen-1] == nsCRT::LF) )
+ bufAmt = std::min(mTokenLen - 1, bufLen);
+ }
+
+ if (bufAmt) {
+ rv = BufferData(cursor + (bufLen - bufAmt), bufAmt);
+ if (NS_FAILED(rv)) return rv;
+ bufLen -= bufAmt;
+ }
+
+ if (bufLen) {
+ rv = SendData(cursor, bufLen);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return rv;
+}
+
+
+// nsIRequestObserver implementation
+NS_IMETHODIMP
+nsMultiMixedConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
+ // we're assuming the content-type is available at this stage
+ NS_ASSERTION(mToken.IsEmpty(), "a second on start???");
+ const char *bndry = nullptr;
+ nsAutoCString delimiter;
+ nsresult rv = NS_OK;
+ mContext = ctxt;
+
+ mFirstOnData = true;
+ mTotalSent = 0;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // ask the HTTP channel for the content-type and extract the boundary from it.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-type"), delimiter);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ // try asking the channel directly
+ rv = channel->GetContentType(delimiter);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ bndry = strstr(delimiter.BeginWriting(), "boundary");
+
+ if (!bndry) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bndry = strchr(bndry, '=');
+ if (!bndry) return NS_ERROR_FAILURE;
+
+ bndry++; // move past the equals sign
+
+ char *attrib = (char *) strchr(bndry, ';');
+ if (attrib) *attrib = '\0';
+
+ nsAutoCString boundaryString(bndry);
+ if (attrib) *attrib = ';';
+
+ boundaryString.Trim(" \"");
+
+ mToken = boundaryString;
+ mTokenLen = boundaryString.Length();
+
+ if (mTokenLen == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiMixedConv::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
+ nsresult aStatus)
+{
+ if (mToken.IsEmpty()) { // no token, no love.
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mPartChannel) {
+ mPartChannel->SetIsLastPart();
+
+ // we've already called SendStart() (which sets up the mPartChannel,
+ // and fires an OnStart()) send any data left over, and then fire the stop.
+ if (mBufLen > 0 && mBuffer) {
+ (void) SendData(mBuffer, mBufLen);
+ // don't bother checking the return value here, if the send failed
+ // we're done anyway as we're in the OnStop() callback.
+ free(mBuffer);
+ mBuffer = nullptr;
+ mBufLen = 0;
+ }
+ (void) SendStop(aStatus);
+ } else if (NS_FAILED(aStatus)) {
+ // underlying data production problem. we should not be in
+ // the middle of sending data. if we were, mPartChannel,
+ // above, would have been true.
+
+ // if we send the start, the URI Loader's m_targetStreamListener, may
+ // be pointing at us causing a nice stack overflow. So, don't call
+ // OnStartRequest! - This breaks necko's semantecs.
+ //(void) mFinalListener->OnStartRequest(request, ctxt);
+
+ (void) mFinalListener->OnStopRequest(request, ctxt, aStatus);
+ }
+
+ return NS_OK;
+}
+
+
+// nsMultiMixedConv methods
+nsMultiMixedConv::nsMultiMixedConv() :
+ mCurrentPartID(0)
+{
+ mTokenLen = 0;
+ mNewPart = true;
+ mContentLength = UINT64_MAX;
+ mBuffer = nullptr;
+ mBufLen = 0;
+ mProcessingHeaders = false;
+ mByteRangeStart = 0;
+ mByteRangeEnd = 0;
+ mTotalSent = 0;
+ mIsByteRangeRequest = false;
+}
+
+nsMultiMixedConv::~nsMultiMixedConv() {
+ NS_ASSERTION(!mBuffer, "all buffered data should be gone");
+ if (mBuffer) {
+ free(mBuffer);
+ mBuffer = nullptr;
+ }
+}
+
+nsresult
+nsMultiMixedConv::BufferData(char *aData, uint32_t aLen) {
+ NS_ASSERTION(!mBuffer, "trying to over-write buffer");
+
+ char *buffer = (char *) malloc(aLen);
+ if (!buffer) return NS_ERROR_OUT_OF_MEMORY;
+
+ memcpy(buffer, aData, aLen);
+ mBuffer = buffer;
+ mBufLen = aLen;
+ return NS_OK;
+}
+
+
+nsresult
+nsMultiMixedConv::SendStart(nsIChannel *aChannel) {
+ 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.
+ NS_ASSERTION(!mPartChannel, "tisk tisk, shouldn't be overwriting a channel");
+
+ nsPartChannel *newChannel;
+ newChannel = new nsPartChannel(aChannel, 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);
+
+ 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;
+ }
+
+ // 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) {
+
+ nsresult rv = NS_OK;
+ 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;
+}
+
+nsresult
+nsMultiMixedConv::SendData(char *aBuffer, uint32_t aLen) {
+
+ nsresult rv = NS_OK;
+
+ 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(aLen) + mTotalSent) > mContentLength)
+ aLen = static_cast<uint32_t>(mContentLength - mTotalSent);
+
+ if (aLen == 0)
+ return NS_OK;
+ }
+
+ uint64_t offset = mTotalSent;
+ mTotalSent += aLen;
+
+ nsCOMPtr<nsIStringInputStream> ss(
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = ss->ShareData(aBuffer, aLen);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIInputStream> inStream(do_QueryInterface(ss, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ return mPartChannel->SendOnDataAvailable(mContext, inStream, offset, aLen);
+}
+
+int32_t
+nsMultiMixedConv::PushOverLine(char *&aPtr, uint32_t &aLen) {
+ int32_t chars = 0;
+ if ((aLen > 0) && (*aPtr == nsCRT::CR || *aPtr == nsCRT::LF)) {
+ if ((aLen > 1) && (aPtr[1] == nsCRT::LF))
+ chars++;
+ chars++;
+ aPtr += chars;
+ aLen -= chars;
+ }
+ return chars;
+}
+
+nsresult
+nsMultiMixedConv::ParseHeaders(nsIChannel *aChannel, char *&aPtr,
+ uint32_t &aLen, bool *_retval) {
+ // NOTE: this data must be ascii.
+ // NOTE: aPtr is NOT null terminated!
+ nsresult rv = NS_OK;
+ char *cursor = aPtr, *newLine = nullptr;
+ uint32_t cursorLen = aLen;
+ bool done = false;
+ uint32_t lineFeedIncrement = 1;
+
+ mContentLength = UINT64_MAX; // XXX what if we were already called?
+ while (cursorLen && (newLine = (char *) memchr(cursor, nsCRT::LF, cursorLen))) {
+ // adjust for linefeeds
+ if ((newLine > cursor) && (newLine[-1] == nsCRT::CR) ) { // CRLF
+ lineFeedIncrement = 2;
+ newLine--;
+ }
+ else
+ lineFeedIncrement = 1; // reset
+
+ if (newLine == cursor) {
+ // move the newLine beyond the linefeed marker
+ NS_ASSERTION(cursorLen >= lineFeedIncrement, "oops!");
+
+ cursor += lineFeedIncrement;
+ cursorLen -= lineFeedIncrement;
+
+ done = true;
+ break;
+ }
+
+ char tmpChar = *newLine;
+ *newLine = '\0'; // cursor is now null terminated
+
+ char *colon = (char *) strchr(cursor, ':');
+ if (colon) {
+ *colon = '\0';
+ nsAutoCString headerStr(cursor);
+ headerStr.CompressWhitespace();
+ *colon = ':';
+
+ nsAutoCString headerVal(colon + 1);
+ headerVal.CompressWhitespace();
+
+ // examine header
+ if (headerStr.LowerCaseEqualsLiteral("content-type")) {
+ mContentType = headerVal;
+ } else if (headerStr.LowerCaseEqualsLiteral("content-length")) {
+ mContentLength = nsCRT::atoll(headerVal.get());
+ } else if (headerStr.LowerCaseEqualsLiteral("content-disposition")) {
+ mContentDisposition = headerVal;
+ } else if (headerStr.LowerCaseEqualsLiteral("set-cookie")) {
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal =
+ do_QueryInterface(aChannel);
+ if (httpInternal) {
+ httpInternal->SetCookie(headerVal.get());
+ }
+ } else if (headerStr.LowerCaseEqualsLiteral("content-range") ||
+ headerStr.LowerCaseEqualsLiteral("range") ) {
+ // something like: Content-range: bytes 7000-7999/8000
+ char* tmpPtr;
+
+ tmpPtr = (char *) strchr(colon + 1, '/');
+ if (tmpPtr)
+ *tmpPtr = '\0';
+
+ // pass the bytes-unit and the SP
+ char *range = (char *) strchr(colon + 2, ' ');
+ if (!range)
+ return NS_ERROR_FAILURE;
+
+ do {
+ range++;
+ } while (*range == ' ');
+
+ if (range[0] == '*'){
+ mByteRangeStart = mByteRangeEnd = 0;
+ }
+ else {
+ tmpPtr = (char *) strchr(range, '-');
+ if (!tmpPtr)
+ return NS_ERROR_FAILURE;
+
+ tmpPtr[0] = '\0';
+
+ mByteRangeStart = nsCRT::atoll(range);
+ tmpPtr++;
+ mByteRangeEnd = nsCRT::atoll(tmpPtr);
+ }
+
+ mIsByteRangeRequest = true;
+ if (mContentLength == UINT64_MAX)
+ mContentLength = uint64_t(mByteRangeEnd - mByteRangeStart + 1);
+ }
+ }
+ *newLine = tmpChar;
+ newLine += lineFeedIncrement;
+ cursorLen -= (newLine - cursor);
+ cursor = newLine;
+ }
+
+ aPtr = cursor;
+ aLen = cursorLen;
+
+ *_retval = done;
+ return rv;
+}
+
+char *
+nsMultiMixedConv::FindToken(char *aCursor, uint32_t aLen) {
+ // strnstr without looking for null termination
+ const char *token = mToken.get();
+ char *cur = aCursor;
+
+ if (!(token && aCursor && *token)) {
+ NS_WARNING("bad data");
+ return nullptr;
+ }
+
+ for (; aLen >= mTokenLen; aCursor++, aLen--) {
+ if (!memcmp(aCursor, token, mTokenLen) ) {
+ if ((aCursor - cur) >= 2) {
+ // back the cursor up over a double dash for backwards compat.
+ if ((*(aCursor-1) == '-') && (*(aCursor-2) == '-')) {
+ aCursor -= 2;
+ aLen += 2;
+
+ // we're playing w/ double dash tokens, adjust.
+ mToken.Assign(aCursor, mTokenLen + 2);
+ mTokenLen = mToken.Length();
+ }
+ }
+ return aCursor;
+ }
+ }
+
+ return nullptr;
+}
+
+nsresult
+NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv)
+{
+ NS_PRECONDITION(aMultiMixedConv != nullptr, "null ptr");
+ if (! aMultiMixedConv)
+ return NS_ERROR_NULL_POINTER;
+
+ *aMultiMixedConv = new nsMultiMixedConv();
+ if (! *aMultiMixedConv)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aMultiMixedConv);
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.h b/netwerk/streamconv/converters/nsMultiMixedConv.h
new file mode 100644
index 0000000000..fa058b0720
--- /dev/null
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.h
@@ -0,0 +1,176 @@
+/* -*- 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 __nsmultimixedconv__h__
+#define __nsmultimixedconv__h__
+
+#include "nsIStreamConverter.h"
+#include "nsIChannel.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIByteRangeRequest.h"
+#include "nsILoadInfo.h"
+#include "nsIMultiPartChannel.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.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);
+ void SetResponseHead(mozilla::net::nsHttpResponseHead * head) { mResponseHead = head; }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIBYTERANGEREQUEST
+ NS_DECL_NSIMULTIPARTCHANNEL
+
+protected:
+ ~nsPartChannel();
+
+protected:
+ nsCOMPtr<nsIChannel> mMultipartChannel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsAutoPtr<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 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
+
+ nsMultiMixedConv();
+
+protected:
+ virtual ~nsMultiMixedConv();
+
+ nsresult SendStart(nsIChannel *aChannel);
+ nsresult SendStop(nsresult aStatus);
+ nsresult SendData(char *aBuffer, uint32_t aLen);
+ nsresult ParseHeaders(nsIChannel *aChannel, char *&aPtr,
+ uint32_t &aLen, bool *_retval);
+ int32_t PushOverLine(char *&aPtr, uint32_t &aLen);
+ char *FindToken(char *aCursor, uint32_t aLen);
+ nsresult BufferData(char *aData, uint32_t aLen);
+
+ // member data
+ bool mNewPart; // Are we processing the beginning of a part?
+ bool mProcessingHeaders;
+ nsCOMPtr<nsIStreamListener> mFinalListener; // this guy gets the converted data via his OnDataAvailable()
+
+ nsCString mToken;
+ uint32_t mTokenLen;
+
+ RefPtr<nsPartChannel> mPartChannel; // the channel for the given part we're processing.
+ // one channel per part.
+ nsCOMPtr<nsISupports> mContext;
+ nsCString mContentType;
+ nsCString mContentDisposition;
+ uint64_t mContentLength;
+
+ char *mBuffer;
+ uint32_t mBufLen;
+ uint64_t mTotalSent;
+ bool mFirstOnData; // used to determine if we're in our first OnData callback.
+
+ // 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;
+
+ uint32_t mCurrentPartID;
+};
+
+#endif /* __nsmultimixedconv__h__ */
diff --git a/netwerk/streamconv/converters/nsTXTToHTMLConv.cpp b/netwerk/streamconv/converters/nsTXTToHTMLConv.cpp
new file mode 100644
index 0000000000..ded3e9e6c5
--- /dev/null
+++ b/netwerk/streamconv/converters/nsTXTToHTMLConv.cpp
@@ -0,0 +1,314 @@
+/* -*- 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 "nsTXTToHTMLConv.h"
+#include "nsEscape.h"
+#include "nsStringStream.h"
+#include "nsAutoPtr.h"
+#include "nsIChannel.h"
+#include <algorithm>
+
+#include "mozilla/UniquePtrExtensions.h"
+
+#define TOKEN_DELIMITERS u"\t\r\n "
+
+using namespace mozilla;
+
+// nsISupports methods
+NS_IMPL_ISUPPORTS(nsTXTToHTMLConv,
+ nsIStreamConverter,
+ nsITXTToHTMLConv,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+
+// nsIStreamConverter methods
+NS_IMETHODIMP
+nsTXTToHTMLConv::Convert(nsIInputStream *aFromStream,
+ const char *aFromType, const char *aToType,
+ nsISupports *aCtxt, nsIInputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsTXTToHTMLConv::AsyncConvertData(const char *aFromType,
+ const char *aToType,
+ nsIStreamListener *aListener,
+ nsISupports *aCtxt)
+{
+ NS_ASSERTION(aListener, "null pointer");
+ mListener = aListener;
+ return NS_OK;
+}
+
+
+// nsIRequestObserver methods
+NS_IMETHODIMP
+nsTXTToHTMLConv::OnStartRequest(nsIRequest* request, nsISupports *aContext)
+{
+ mBuffer.AssignLiteral("<html>\n<head><title>");
+ mBuffer.Append(mPageTitle);
+ mBuffer.AppendLiteral("</title></head>\n<body>\n");
+ if (mPreFormatHTML) { // Use <pre> tags
+ mBuffer.AppendLiteral("<pre>\n");
+ }
+
+ // Push mBuffer to the listener now, so the initial HTML will not
+ // be parsed in OnDataAvailable().
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel)
+ channel->SetContentType(NS_LITERAL_CSTRING("text/html"));
+ // else, assume there is a channel somewhere that knows what it is doing!
+
+ nsresult rv = mListener->OnStartRequest(request, aContext);
+ 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;
+
+ nsCOMPtr<nsIInputStream> inputData;
+ NS_LossyConvertUTF16toASCII asciiData(mBuffer);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputData), asciiData);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mListener->OnDataAvailable(request, aContext,
+ inputData, 0, mBuffer.Length());
+ if (NS_FAILED(rv)) return rv;
+ mBuffer.Truncate();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsTXTToHTMLConv::OnStopRequest(nsIRequest* request, nsISupports *aContext,
+ nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+ if (mToken) {
+ // we still have an outstanding token
+ NS_ASSERTION(mToken->prepend,
+ "Non prepending tokens should be handled in "
+ "OnDataAvailable. There should only be a single "
+ "prepending token left to be processed.");
+ (void)CatHTML(0, mBuffer.Length());
+ }
+ if (mPreFormatHTML) {
+ mBuffer.AppendLiteral("</pre>\n");
+ }
+ mBuffer.AppendLiteral("\n</body></html>");
+
+ nsCOMPtr<nsIInputStream> inputData;
+ NS_LossyConvertUTF16toASCII asciiData(mBuffer);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputData), asciiData);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mListener->OnDataAvailable(request, aContext,
+ inputData, 0, mBuffer.Length());
+ if (NS_FAILED(rv)) return rv;
+
+ return mListener->OnStopRequest(request, aContext, aStatus);
+}
+
+// nsITXTToHTMLConv methods
+NS_IMETHODIMP
+nsTXTToHTMLConv::SetTitle(const char16_t *aTitle)
+{
+ mPageTitle.Assign(aTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTXTToHTMLConv::PreFormatHTML(bool value)
+{
+ mPreFormatHTML = value;
+ return NS_OK;
+}
+
+// nsIStreamListener method
+NS_IMETHODIMP
+nsTXTToHTMLConv::OnDataAvailable(nsIRequest* request, nsISupports *aContext,
+ nsIInputStream *aInStream,
+ uint64_t aOffset, uint32_t aCount)
+{
+ nsresult rv = NS_OK;
+ nsString pushBuffer;
+ uint32_t amtRead = 0;
+ auto buffer = MakeUniqueFallible<char[]>(aCount+1);
+ if (!buffer) return NS_ERROR_OUT_OF_MEMORY;
+
+ do {
+ uint32_t read = 0;
+ // XXX readSegments, to avoid the first copy?
+ rv = aInStream->Read(buffer.get(), aCount-amtRead, &read);
+ if (NS_FAILED(rv)) return rv;
+
+ buffer[read] = '\0';
+ // XXX charsets?? non-latin1 characters?? utf-16??
+ AppendASCIItoUTF16(buffer.get(), mBuffer);
+ amtRead += read;
+
+ int32_t front = -1, back = -1, tokenLoc = -1, cursor = 0;
+
+ while ( (tokenLoc = FindToken(cursor, &mToken)) > -1) {
+ if (mToken->prepend) {
+ front = mBuffer.RFindCharInSet(TOKEN_DELIMITERS, tokenLoc);
+ front++;
+ back = mBuffer.FindCharInSet(TOKEN_DELIMITERS, tokenLoc);
+ } else {
+ front = tokenLoc;
+ back = front + mToken->token.Length();
+ }
+ if (back == -1) {
+ // didn't find an ending, buffer up.
+ mBuffer.Left(pushBuffer, front);
+ cursor = front;
+ break;
+ }
+ // found the end of the token.
+ cursor = CatHTML(front, back);
+ }
+
+ int32_t end = mBuffer.RFind(TOKEN_DELIMITERS, mBuffer.Length());
+ mBuffer.Left(pushBuffer, std::max(cursor, end));
+ mBuffer.Cut(0, std::max(cursor, end));
+ cursor = 0;
+
+ if (!pushBuffer.IsEmpty()) {
+ nsCOMPtr<nsIInputStream> inputData;
+ NS_LossyConvertUTF16toASCII asciiData(pushBuffer);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputData), asciiData);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = mListener->OnDataAvailable(request, aContext,
+ inputData, 0, pushBuffer.Length());
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ } while (amtRead < aCount);
+
+ return rv;
+}
+
+// nsTXTToHTMLConv methods
+nsTXTToHTMLConv::nsTXTToHTMLConv()
+{
+ mToken = nullptr;
+ mPreFormatHTML = false;
+}
+
+nsTXTToHTMLConv::~nsTXTToHTMLConv()
+{
+ mTokens.Clear();
+}
+
+nsresult
+nsTXTToHTMLConv::Init()
+{
+ nsresult rv = NS_OK;
+
+ // build up the list of tokens to handle
+ convToken *token = new convToken;
+ if (!token) return NS_ERROR_OUT_OF_MEMORY;
+ token->prepend = false;
+ token->token.Assign(char16_t('<'));
+ token->modText.AssignLiteral("&lt;");
+ mTokens.AppendElement(token);
+
+ token = new convToken;
+ if (!token) return NS_ERROR_OUT_OF_MEMORY;
+ token->prepend = false;
+ token->token.Assign(char16_t('>'));
+ token->modText.AssignLiteral("&gt;");
+ mTokens.AppendElement(token);
+
+ token = new convToken;
+ if (!token) return NS_ERROR_OUT_OF_MEMORY;
+ token->prepend = false;
+ token->token.Assign(char16_t('&'));
+ token->modText.AssignLiteral("&amp;");
+ mTokens.AppendElement(token);
+
+ token = new convToken;
+ if (!token) return NS_ERROR_OUT_OF_MEMORY;
+ token->prepend = true;
+ token->token.AssignLiteral("http://"); // XXX need to iterate through all protos
+ mTokens.AppendElement(token);
+
+ token = new convToken;
+ if (!token) return NS_ERROR_OUT_OF_MEMORY;
+ token->prepend = true;
+ token->token.Assign(char16_t('@'));
+ token->modText.AssignLiteral("mailto:");
+ mTokens.AppendElement(token);
+
+ return rv;
+}
+
+int32_t
+nsTXTToHTMLConv::FindToken(int32_t cursor, convToken* *_retval)
+{
+ int32_t loc = -1, firstToken = mBuffer.Length();
+ int8_t token = -1;
+ for (uint8_t i=0; i < mTokens.Length(); i++) {
+ loc = mBuffer.Find(mTokens[i]->token, cursor);
+ if (loc != -1)
+ if (loc < firstToken) {
+ firstToken = loc;
+ token = i;
+ }
+ }
+ if (token == -1)
+ return -1;
+
+ *_retval = mTokens[token];
+ return firstToken;
+}
+
+int32_t
+nsTXTToHTMLConv::CatHTML(int32_t front, int32_t back)
+{
+ int32_t cursor = 0;
+ int32_t modLen = mToken->modText.Length();
+ if (!mToken->prepend) {
+ // replace the entire token (from delimiter to delimiter)
+ mBuffer.Cut(front, back - front);
+ mBuffer.Insert(mToken->modText, front);
+ cursor = front+modLen;
+ } else {
+ nsString linkText;
+ // href is implied
+ mBuffer.Mid(linkText, front, back-front);
+
+ mBuffer.Insert(NS_LITERAL_STRING("<a href=\""), front);
+ cursor += front+9;
+ if (modLen) {
+ mBuffer.Insert(mToken->modText, cursor);
+ cursor += modLen;
+ }
+
+ NS_ConvertUTF16toUTF8 linkTextUTF8(linkText);
+ nsCString escaped;
+ if (NS_EscapeURL(linkTextUTF8.Data(), linkTextUTF8.Length(), esc_Minimal, escaped)) {
+ mBuffer.Cut(cursor, back - front);
+ CopyUTF8toUTF16(escaped, linkText);
+ mBuffer.Insert(linkText, cursor);
+ back = front + linkText.Length();
+ }
+
+ cursor += back-front;
+ mBuffer.Insert(NS_LITERAL_STRING("\">"), cursor);
+ cursor += 2;
+ mBuffer.Insert(linkText, cursor);
+ cursor += linkText.Length();
+ mBuffer.Insert(NS_LITERAL_STRING("</a>"), cursor);
+ cursor += 4;
+ }
+ mToken = nullptr; // indicates completeness
+ return cursor;
+}
diff --git a/netwerk/streamconv/converters/nsTXTToHTMLConv.h b/netwerk/streamconv/converters/nsTXTToHTMLConv.h
new file mode 100644
index 0000000000..30ca811a16
--- /dev/null
+++ b/netwerk/streamconv/converters/nsTXTToHTMLConv.h
@@ -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/. */
+
+#ifndef ____nstxttohtmlconv___h___
+#define ____nstxttohtmlconv___h___
+
+#include "nsITXTToHTMLConv.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+#define NS_NSTXTTOHTMLCONVERTER_CID \
+{ /* 9ef9fa14-1dd1-11b2-9d65-d72d6d1f025e */ \
+ 0x9ef9fa14, \
+ 0x1dd1, \
+ 0x11b2, \
+ {0x9d, 0x65, 0xd7, 0x2d, 0x6d, 0x1f, 0x02, 0x5e} \
+}
+
+// Internal representation of a "token"
+typedef struct convToken {
+ nsString token; // the actual string (i.e. "http://")
+ nsString modText; // replacement text or href prepend text.
+ bool prepend; // flag indicating how the modText should be used.
+} convToken;
+
+template<class T> class nsAutoPtr;
+
+/**
+ * Convert plain text to HTML.
+ *
+ * OVERVIEW OF HOW THIS CLASS WORKS:
+ *
+ * This class stores an array of tokens that should be replaced by something,
+ * or something that should be prepended.
+ * The "token" member of convToken is the text to search for. This is a
+ * substring of the desired token. Tokens are delimited by TOKEN_DELIMITERS.
+ * That entire token will be replaced by modText (if prepend is false); or it
+ * will be linkified and modText will be prepended to the token if prepend is
+ * true.
+ *
+ * Note that all of the text will be in a preformatted block, so there is no
+ * need to emit line-end tags, or set the font face to monospace.
+ *
+ * This works as a stream converter, so data will arrive by
+ * OnStartRequest/OnDataAvailable/OnStopRequest calls.
+ *
+ * OStopR will possibly process a remaining token.
+ *
+ * If the data of one pass contains a part of a token, that part will be stored
+ * in mBuffer. The rest of the data will be sent to the next listener.
+ *
+ * XXX this seems suboptimal. this means that this design will only work for
+ * links. and it is impossible to append anything to the token. this means that,
+ * for example, making *foo* bold is not possible.
+ */
+class nsTXTToHTMLConv : public nsITXTToHTMLConv {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMCONVERTER
+ NS_DECL_NSITXTTOHTMLCONV
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsTXTToHTMLConv();
+ nsresult Init();
+
+protected:
+ virtual ~nsTXTToHTMLConv();
+
+ // return the token and it's location in the underlying buffer.
+ int32_t FindToken(int32_t cursor, convToken* *_retval);
+
+ // return the cursor location after munging HTML into the
+ // underlying buffer, according to mToken
+ int32_t CatHTML(int32_t front, int32_t back);
+
+ nsCOMPtr<nsIStreamListener> mListener; // final listener (consumer)
+ nsString mBuffer; // any carry over data
+ nsTArray<nsAutoPtr<convToken> > mTokens; // list of tokens to search for
+ convToken *mToken; // current token (if any)
+ nsString mPageTitle; // Page title
+ bool mPreFormatHTML; // Whether to use <pre> tags
+};
+
+#endif // ____nstxttohtmlconv___h___
+
diff --git a/netwerk/streamconv/converters/nsUnknownDecoder.cpp b/netwerk/streamconv/converters/nsUnknownDecoder.cpp
new file mode 100644
index 0000000000..6382a9cb6f
--- /dev/null
+++ b/netwerk/streamconv/converters/nsUnknownDecoder.cpp
@@ -0,0 +1,836 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+
+#include "nsCRT.h"
+
+#include "nsIMIMEService.h"
+
+#include "nsIDivertableChannel.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;
+}
+
+nsUnknownDecoder::ConvertedStreamListener::~ConvertedStreamListener()
+{
+}
+
+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,
+ nsISupports* context)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::ConvertedStreamListener::
+ OnDataAvailable(nsIRequest* request,
+ nsISupports* context,
+ nsIInputStream* stream,
+ uint64_t offset,
+ uint32_t count)
+{
+ uint32_t read;
+ return stream->ReadSegments(AppendDataToString, &mDecoder->mDecodedData, count,
+ &read);
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::ConvertedStreamListener::OnStopRequest(nsIRequest* request,
+ nsISupports* context,
+ nsresult status)
+{
+ return NS_OK;
+}
+
+nsUnknownDecoder::nsUnknownDecoder()
+ : mBuffer(nullptr)
+ , mBufferLen(0)
+ , mRequireHTMLsuffix(false)
+ , 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_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.
+ //
+ mNextListener = aListener;
+ return (aListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// ----
+//
+// nsIStreamListener methods...
+//
+// ----
+
+NS_IMETHODIMP
+nsUnknownDecoder::OnDataAvailable(nsIRequest* request,
+ nsISupports *aCtxt,
+ nsIInputStream *aStream,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ nsresult rv = NS_OK;
+
+ if (!mNextListener) return NS_ERROR_FAILURE;
+
+ if (mContentType.IsEmpty()) {
+ 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, aCtxt);
+ }
+ }
+
+ // Must not fire ODA again if it failed once
+ if (aCount && NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+
+ nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+ if (divertable) {
+ bool diverting;
+ divertable->GetDivertingToParent(&diverting);
+ if (diverting) {
+ // The channel is diverted to the parent do not send any more data here.
+ return rv;
+ }
+ }
+ rv = mNextListener->OnDataAvailable(request, aCtxt, aStream,
+ aSourceOffset, aCount);
+ }
+
+ return rv;
+}
+
+// ----
+//
+// nsIRequestObserver methods...
+//
+// ----
+
+NS_IMETHODIMP
+nsUnknownDecoder::OnStartRequest(nsIRequest* request, nsISupports *aCtxt)
+{
+ nsresult rv = NS_OK;
+
+ 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;
+ }
+ }
+
+ nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+ if (divertable) {
+ divertable->UnknownDecoderInvolvedKeepData();
+ }
+
+ // Do not pass the OnStartRequest on to the next listener (yet)...
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUnknownDecoder::OnStopRequest(nsIRequest* request, nsISupports *aCtxt,
+ nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+
+ if (!mNextListener) return NS_ERROR_FAILURE;
+
+ //
+ // The total amount of data is less than the size of the sniffer buffer.
+ // Analyze the buffer now...
+ //
+ if (mContentType.IsEmpty()) {
+ 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, aCtxt);
+
+ if (NS_FAILED(rv)) {
+ aStatus = rv;
+ }
+
+ // now we need to set pending state to false before calling OnStopRequest
+ if (forcePendingChannel) {
+ forcePendingChannel->ForcePending(false);
+ }
+ }
+
+ rv = mNextListener->OnStopRequest(request, aCtxt, aStatus);
+ mNextListener = nullptr;
+
+ return rv;
+}
+
+// ----
+//
+// nsIContentSniffer methods...
+//
+// ----
+NS_IMETHODIMP
+nsUnknownDecoder::GetMIMETypeFromContent(nsIRequest* aRequest,
+ const uint8_t* aData,
+ uint32_t aLength,
+ nsACString& type)
+{
+ 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;
+ }
+
+ bool isLocalFile = false;
+ if (NS_FAILED(uri->SchemeIs("file", &isLocalFile)) || isLocalFile) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * 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)
+{
+ NS_ASSERTION(mContentType.IsEmpty(), "Content type is already known.");
+ if (!mContentType.IsEmpty()) return;
+
+ const char* testData = mBuffer;
+ uint32_t testDataLen = mBufferLen;
+ // Check if data are compressed.
+ nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(aRequest));
+ if (channel) {
+ nsresult rv = ConvertEncodedData(aRequest, mBuffer, mBufferLen);
+ if (NS_SUCCEEDED(rv)) {
+ if (!mDecodedData.IsEmpty()) {
+ testData = mDecodedData.get();
+ testDataLen = std::min(mDecodedData.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) {
+ mContentType = sSnifferEntries[i].mMimeType;
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+ return;
+ }
+ if ((this->*(sSnifferEntries[i].mContentTypeSniffer))(aRequest)) {
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+ return;
+ }
+ }
+ }
+
+ NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest,
+ (const uint8_t*)testData, testDataLen, mContentType);
+ if (!mContentType.IsEmpty()) {
+ return;
+ }
+
+ if (SniffForHTML(aRequest)) {
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+ return;
+ }
+
+ // We don't know what this is yet. Before we just give up, try
+ // the URI from the request.
+ if (SniffURI(aRequest)) {
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+ return;
+ }
+
+ LastDitchSniff(aRequest);
+ NS_ASSERTION(!mContentType.IsEmpty(),
+ "Content type should be known by now.");
+}
+
+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;
+ }
+
+ // 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
+ mContentType = TEXT_XML;
+ }
+
+ return true;
+}
+
+bool nsUnknownDecoder::SniffURI(nsIRequest* aRequest)
+{
+ 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)) {
+ 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
+
+ const char* testData;
+ uint32_t testDataLen;
+ if (mDecodedData.IsEmpty()) {
+ testData = mBuffer;
+ testDataLen = mBufferLen;
+ } 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++) {
+ continue;
+ }
+
+ if (i == testDataLen) {
+ mContentType = TEXT_PLAIN;
+ }
+ else {
+ mContentType = APPLICATION_OCTET_STREAM;
+ }
+
+ return true;
+}
+
+
+nsresult nsUnknownDecoder::FireListenerNotifications(nsIRequest* request,
+ nsISupports *aCtxt)
+{
+ nsresult rv = NS_OK;
+
+ if (!mNextListener) return NS_ERROR_FAILURE;
+
+ if (!mContentType.IsEmpty()) {
+ nsCOMPtr<nsIViewSourceChannel> viewSourceChannel =
+ do_QueryInterface(request);
+ if (viewSourceChannel) {
+ rv = viewSourceChannel->SetOriginalContentType(mContentType);
+ } else {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ // Set the new content type on the channel...
+ rv = channel->SetContentType(mContentType);
+ }
+ }
+
+ 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);
+ mNextListener->OnStartRequest(request, aCtxt);
+
+ nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+ if (divertable) {
+ rv = divertable->UnknownDecoderInvolvedOnStartRequestCalled();
+ }
+
+ return rv;
+ }
+ }
+
+ // Fire the OnStartRequest(...)
+ rv = mNextListener->OnStartRequest(request, aCtxt);
+
+ nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+ if (divertable) {
+ rv = divertable->UnknownDecoderInvolvedOnStartRequestCalled();
+ bool diverting;
+ divertable->GetDivertingToParent(&diverting);
+ if (diverting) {
+ // The channel is diverted to the parent do not send any more data here.
+ return rv;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // install stream converter if required
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(request);
+ if (encodedChannel) {
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = encodedChannel->DoApplyContentConversions(mNextListener, getter_AddRefs(listener), aCtxt);
+ if (NS_SUCCEEDED(rv) && listener) {
+ mNextListener = listener;
+ }
+ }
+ }
+
+ 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 = mNextListener->OnDataAvailable(request, aCtxt, 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;
+
+ 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, nullptr);
+
+ 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, nullptr, rawStream, 0,
+ length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ listener->OnStopRequest(request, nullptr, NS_OK);
+ }
+ }
+ return rv;
+}
+
+void
+nsBinaryDetector::DetermineContentType(nsIRequest* aRequest)
+{
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ return;
+ }
+
+ // It's an HTTP channel. Check for the text/plain mess
+ nsAutoCString contentTypeHdr;
+ httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
+ 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;
+ httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
+ contentEncoding);
+ if (!contentEncoding.IsEmpty()) {
+ return;
+ }
+
+ LastDitchSniff(aRequest);
+ if (mContentType.Equals(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..a5087ed062
--- /dev/null
+++ b/netwerk/streamconv/converters/nsUnknownDecoder.h
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIContentSniffer.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#define NS_UNKNOWNDECODER_CID \
+{ /* 7d7008a0-c49a-11d3-9b22-0080c7cb1080 */ \
+ 0x7d7008a0, \
+ 0xc49a, \
+ 0x11d3, \
+ {0x9b, 0x22, 0x00, 0x80, 0xc7, 0xcb, 0x10, 0x80} \
+}
+
+
+class nsUnknownDecoder : public nsIStreamConverter, public nsIContentSniffer
+{
+public:
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ // nsIStreamConverter methods
+ NS_DECL_NSISTREAMCONVERTER
+
+ // nsIStreamListener methods
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIRequestObserver methods
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // nsIContentSniffer methods
+ NS_DECL_NSICONTENTSNIFFER
+
+ 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();
+ 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;
+
+ char *mBuffer;
+ uint32_t mBufferLen;
+ bool mRequireHTMLsuffix;
+
+ nsCString mContentType;
+
+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);
+};
+
+#define NS_BINARYDETECTOR_CATEGORYENTRY \
+ { NS_CONTENT_SNIFFER_CATEGORY, "Binary Detector", NS_BINARYDETECTOR_CONTRACTID }
+
+#endif /* nsUnknownDecoder_h__ */
+
diff --git a/netwerk/streamconv/converters/parse-ftp/3-guess.in b/netwerk/streamconv/converters/parse-ftp/3-guess.in
new file mode 100644
index 0000000000..b5e8596c1c
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/3-guess.out b/netwerk/streamconv/converters/parse-ftp/3-guess.out
new file mode 100644
index 0000000000..30a1d0fb98
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/C-VMold.in b/netwerk/streamconv/converters/parse-ftp/C-VMold.in
new file mode 100644
index 0000000000..5453b496e7
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/C-VMold.out b/netwerk/streamconv/converters/parse-ftp/C-VMold.out
new file mode 100644
index 0000000000..0c9c016f61
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/C-zVM.in b/netwerk/streamconv/converters/parse-ftp/C-zVM.in
new file mode 100644
index 0000000000..bd63f631ff
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/C-zVM.out b/netwerk/streamconv/converters/parse-ftp/C-zVM.out
new file mode 100644
index 0000000000..04511ebc7e
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/D-WinNT.in b/netwerk/streamconv/converters/parse-ftp/D-WinNT.in
new file mode 100644
index 0000000000..e5b5297f45
--- /dev/null
+++ b/netwerk/streamconv/converters/parse-ftp/D-WinNT.in
@@ -0,0 +1,60 @@
+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
+07-21-00 07:22AM <JUNCTION> a_junction_sample
+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
+226 Transfer complete.
+ftp> close
+221 Thank-You For Using Microsoft Products!
+ftp>
+
diff --git a/netwerk/streamconv/converters/parse-ftp/D-WinNT.out b/netwerk/streamconv/converters/parse-ftp/D-WinNT.out
new file mode 100644
index 0000000000..a6e8e74d15
--- /dev/null
+++ b/netwerk/streamconv/converters/parse-ftp/D-WinNT.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/. -->
+
+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
+07-21-2000 07:22:00 <JUNCTION> a_junction_sample
+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
diff --git a/netwerk/streamconv/converters/parse-ftp/E-EPLF.in b/netwerk/streamconv/converters/parse-ftp/E-EPLF.in
new file mode 100644
index 0000000000..3798ea0a9d
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/E-EPLF.out b/netwerk/streamconv/converters/parse-ftp/E-EPLF.out
new file mode 100644
index 0000000000..e72a793537
--- /dev/null
+++ b/netwerk/streamconv/converters/parse-ftp/E-EPLF.out
@@ -0,0 +1,174 @@
+01-11-2001 10:38:25 <DIR> tmp
+02-29-2000 03:01:45 <DIR> 1998-100
+12-24-2000 21:44:06 1172 mirrors.html
+02-29-2000 03:01:47 <DIR> 1999-275
+05-08-2000 07:25:16 933 1999-275.html
+05-08-2000 07:27:06 322 1998-401.html
+02-29-2000 03:01:47 <DIR> 1998-401
+02-29-2000 03:01:45 <DIR> 1997-275
+05-08-2000 07:28:25 277 1998-515.html
+05-08-2000 07:36:43 325 2000-436.html
+12-23-2000 07:47:11 <DIR> checkpwd
+07-04-2002 19:47:46 <DIR> bib
+02-29-2000 03:01:47 <DIR> 1999-541
+04-28-2001 05:02:35 <DIR> 2001-260
+04-17-2002 20:27:55 <DIR> 2002-501
+06-01-2001 22:56:17 <DIR> 1995-514
+02-29-2000 03:01:45 <DIR> 1999-180
+06-01-2001 04:44:23 475 2000-515.html
+02-29-2000 03:01:47 <DIR> 1998-515
+02-29-2000 03:01:47 <DIR> 2000-515
+05-12-2000 09:46:13 <DIR> zpfft
+10-27-2001 15:00:14 <DIR> psibound
+02-29-2000 03:01:51 398 anonftpd.html
+02-26-2002 03:34:22 <DIR> nistp224
+08-10-2001 09:27:20 <DIR> cdb
+05-01-2002 01:58:27 <DIR> talks
+12-23-2000 22:34:35 <DIR> ftpparse
+01-11-2000 06:50:10 <DIR> clockspeed
+02-29-2000 03:01:52 1350 clockspeed.html
+02-06-2002 21:52:34 <DIR> slashdoc
+06-07-2002 18:00:40 <DIR> export
+07-17-2001 03:48:36 <DIR> daemontools
+07-12-2001 23:17:08 2187 daemontools.html
+08-30-2001 22:51:16 175 data.html
+12-28-2001 02:32:37 1901 crypto.html
+11-11-2001 00:45:23 739 arith.html
+11-30-2001 04:48:09 2733 cdb.html
+11-11-2001 00:51:02 344 floatasm.html
+12-21-2000 22:25:56 <DIR> djbfft
+02-29-2000 03:01:52 1319 djbfft.html
+07-09-2000 19:16:47 <DIR> dnscache
+07-04-2000 00:57:44 4016 dnscache.html
+07-10-2001 05:16:22 <DIR> docs
+02-29-2000 03:01:52 293 docs.html
+02-29-2000 03:01:52 952 dot-forward.html
+01-11-2000 06:50:11 <DIR> etc-mta
+02-29-2000 03:01:52 3174 etc-mta.html
+02-29-2000 03:01:52 2990 ezmlm.html
+12-21-2000 22:25:56 <DIR> ftp
+02-29-2000 03:01:49 <DIR> im
+02-29-2000 03:01:52 1823 fastforward.html
+02-29-2000 03:01:52 1707 ftp.html
+02-12-2001 03:17:10 <DIR> freebugtraq
+06-02-2002 03:39:25 <DIR> hardware
+06-01-2001 04:57:35 610 1995-514.html
+12-21-2000 22:25:57 <DIR> hash127
+05-19-2000 04:29:02 4835 hash127.html
+02-29-2000 03:01:52 1515 im.html
+02-12-2002 21:40:06 <DIR> immhf
+04-25-2001 23:48:49 2902 immhf.html
+01-25-2001 06:58:36 <DIR> lib
+06-06-2001 01:45:50 <DIR> libtai
+07-10-2000 20:54:16 2120 libtai.html
+11-27-2001 18:52:40 14460 qmail.html
+08-30-2001 22:51:23 2153 mail.html
+02-29-2000 03:01:52 108 maildir.html
+02-29-2000 03:01:50 <DIR> maildisasters
+02-29-2000 03:01:52 873 maildisasters.html
+02-29-2000 03:01:52 1688 mess822.html
+02-25-2002 03:47:27 <DIR> mirror
+12-26-2001 22:02:32 581 ntheory.html
+06-03-2002 17:36:27 <DIR> papers
+05-21-2002 18:50:15 <DIR> postpropter
+01-11-2000 06:50:12 <DIR> postings
+01-11-2000 06:50:12 <DIR> primegen
+08-30-2001 22:51:33 415 precompiled.html
+02-29-2000 03:01:52 1121 primegen.html
+05-05-2002 03:37:55 <DIR> proto
+02-29-2000 03:01:52 452 proto.html
+07-10-2001 00:17:53 <DIR> publicfile
+11-30-2001 04:59:17 5651 publicfile.html
+08-30-2001 22:51:37 409 qlist.html
+04-02-2002 06:25:45 <DIR> qmail
+02-02-2002 06:52:28 7549 lists.html
+02-29-2000 03:01:52 1166 qmailanalog.html
+08-30-2001 22:51:39 1434 qmsmac.html
+03-13-2000 06:38:06 465 rblsmtpd.html
+02-29-2000 03:01:52 1225 rights.html
+01-11-2000 06:50:13 <DIR> sarcasm
+09-14-2001 04:34:14 1058 unix.html
+02-29-2000 03:01:53 1568 serialmail.html
+10-02-2000 21:13:44 840 sigs.html
+05-21-2002 18:50:08 <DIR> smtp
+04-26-2001 17:58:47 1723 smtp.html
+12-21-2000 22:26:03 <DIR> software
+02-29-2000 03:01:53 3671 softwarelaw.html
+01-11-2000 06:50:13 <DIR> sortedsums
+02-29-2000 03:01:53 2735 sortedsums.html
+02-26-2002 03:06:38 <DIR> speed
+07-04-2000 00:40:17 572 surveydns.html
+10-04-2001 02:59:31 <DIR> surveys
+10-04-2001 02:46:36 3503 surveys.html
+11-10-2000 06:58:08 <DIR> syncookies
+02-01-2002 03:03:37 7918 syncookies.html
+08-30-2001 22:51:43 1115 tcpcontrol.html
+03-18-2002 13:08:27 589 tcpip.html
+02-29-2000 03:01:53 906 thoughts.html
+07-30-2001 16:18:09 <DIR> threecubes
+09-14-2001 03:25:58 525 time.html
+05-22-2002 06:21:05 <DIR> ucspi-tcp
+01-25-2002 00:41:31 4086 ucspi-tcp.html
+08-30-2001 22:51:50 732 web.html
+07-04-2002 19:47:48 <DIR> www
+09-14-2001 03:26:08 2717 y2k.html
+02-08-2001 10:28:09 <DIR> dnsroot
+04-23-2001 08:01:37 2745 2001-260.html
+02-29-2000 03:01:53 291 zeroseek.html
+02-29-2000 03:01:53 <DIR> zmodexp
+05-19-2000 04:29:07 1513 zmodexp.html
+08-01-2000 03:08:54 <DIR> zmult
+08-01-2000 03:24:53 669 zmult.html
+12-23-2000 07:19:47 1097 checkpwd.html
+12-31-2001 19:47:16 2077 focus.html
+07-20-2000 01:11:31 3842 im2000.html
+07-04-2002 19:47:43 <DIR> slashcommand
+07-04-2002 19:47:44 <DIR> slashpackage
+07-16-2001 00:24:39 602 slashpackage.html
+05-03-2002 02:15:33 23874 talks.html
+05-08-2000 07:24:59 566 1997-275.html
+05-08-2000 07:31:04 313 1998-100.html
+02-29-2000 03:01:47 <DIR> 2000-436
+05-08-2000 07:37:26 660 1997-494.html
+05-08-2000 07:40:41 730 1999-541.html
+09-13-2000 09:08:58 2367 psibound.html
+06-22-2000 04:09:00 1866 smallfactors.html
+06-09-2000 03:35:19 <DIR> smallfactors
+07-02-2000 00:55:19 <DIR> conferences
+05-22-2002 07:52:29 <DIR> djbdns
+07-17-2001 06:00:58 2712 djbdns.html
+07-17-2000 03:15:39 216 zpfft.html
+08-20-2000 20:56:37 <DIR> fastnewton
+08-20-2000 20:57:31 333 fastnewton.html
+09-01-2000 20:33:51 <DIR> patents
+12-23-2000 22:40:17 1748 ftpparse.html
+11-11-2001 00:57:50 3261 slash.html
+01-17-2001 18:50:10 2663 slashdoc.html
+03-18-2002 10:04:35 2344 dnsroot.html
+12-26-2001 22:03:00 3238 software.html
+09-14-2001 04:34:33 3481 compatibility.html
+05-01-2002 05:30:34 5525 distributors.html
+02-25-2001 04:49:03 212 freebugtraq.html
+03-31-2002 04:59:50 806 hardware.html
+03-31-2001 08:15:18 121 securesoftware.html
+05-22-2002 04:39:56 10950 conferences.html
+11-30-2001 18:51:50 <DIR> 2001-275
+05-29-2001 02:15:07 459 rwb100.html
+10-03-2001 07:40:42 863 nistp224.html
+07-30-2001 16:15:46 778 threecubes.html
+06-03-2002 17:32:16 27125 papers.html
+11-21-2001 03:51:09 379 bib.html
+11-30-2001 18:41:33 3791 2001-275.html
+10-02-2001 20:08:59 169 dh224.html
+03-16-2002 07:05:36 1126 courses.html
+09-03-2001 00:22:28 355 slashcommand.html
+03-27-2002 22:20:22 1199 patents.html
+11-08-2001 20:27:15 640 mailcopyright.html
+06-02-2002 01:27:50 1257 djb.html
+01-07-2002 07:12:34 301 2002-501.html
+05-01-2002 04:41:45 1655 export.html
+03-24-2002 11:40:59 6446 index.html
+03-16-2002 07:09:08 570 2005-590.html
+03-16-2002 07:05:49 800 2004-494.html
+06-02-2002 01:28:59 4459 positions.html
+06-02-2002 00:55:31 466 contact.html
diff --git a/netwerk/streamconv/converters/parse-ftp/O-guess.in b/netwerk/streamconv/converters/parse-ftp/O-guess.in
new file mode 100644
index 0000000000..4477a9d763
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/O-guess.out b/netwerk/streamconv/converters/parse-ftp/O-guess.out
new file mode 100644
index 0000000000..402f77b5bf
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/R-dls.in b/netwerk/streamconv/converters/parse-ftp/R-dls.in
new file mode 100644
index 0000000000..b472a00bce
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/R-dls.out b/netwerk/streamconv/converters/parse-ftp/R-dls.out
new file mode 100644
index 0000000000..414f0320c5
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/README b/netwerk/streamconv/converters/parse-ftp/README
new file mode 100644
index 0000000000..c49e28c700
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-HellSoft.in b/netwerk/streamconv/converters/parse-ftp/U-HellSoft.in
new file mode 100644
index 0000000000..50f03d664a
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-HellSoft.out b/netwerk/streamconv/converters/parse-ftp/U-HellSoft.out
new file mode 100644
index 0000000000..a42b16315c
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-NetPresenz.in b/netwerk/streamconv/converters/parse-ftp/U-NetPresenz.in
new file mode 100644
index 0000000000..1114ffe4ba
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-NetPresenz.out b/netwerk/streamconv/converters/parse-ftp/U-NetPresenz.out
new file mode 100644
index 0000000000..754e0e3ca5
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-NetWare.in b/netwerk/streamconv/converters/parse-ftp/U-NetWare.in
new file mode 100644
index 0000000000..c7252569e8
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-NetWare.out b/netwerk/streamconv/converters/parse-ftp/U-NetWare.out
new file mode 100644
index 0000000000..6cf23ffb33
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-Novonyx.in b/netwerk/streamconv/converters/parse-ftp/U-Novonyx.in
new file mode 100644
index 0000000000..e3de9474d9
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-Novonyx.out b/netwerk/streamconv/converters/parse-ftp/U-Novonyx.out
new file mode 100644
index 0000000000..8755d5fa7b
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-Surge.in b/netwerk/streamconv/converters/parse-ftp/U-Surge.in
new file mode 100644
index 0000000000..f1e0963688
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-Surge.out b/netwerk/streamconv/converters/parse-ftp/U-Surge.out
new file mode 100644
index 0000000000..e6b6527106
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-WarFTPd.in b/netwerk/streamconv/converters/parse-ftp/U-WarFTPd.in
new file mode 100644
index 0000000000..244c087097
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-WarFTPd.out b/netwerk/streamconv/converters/parse-ftp/U-WarFTPd.out
new file mode 100644
index 0000000000..b0a0bab43c
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-WebStar.in b/netwerk/streamconv/converters/parse-ftp/U-WebStar.in
new file mode 100644
index 0000000000..4010d462e1
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-WebStar.out b/netwerk/streamconv/converters/parse-ftp/U-WebStar.out
new file mode 100644
index 0000000000..3a6720727c
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-WinNT.in b/netwerk/streamconv/converters/parse-ftp/U-WinNT.in
new file mode 100644
index 0000000000..a883fe0aae
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-WinNT.out b/netwerk/streamconv/converters/parse-ftp/U-WinNT.out
new file mode 100644
index 0000000000..857f80c3fb
--- /dev/null
+++ b/netwerk/streamconv/converters/parse-ftp/U-WinNT.out
@@ -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/. -->
+
+cmnt: ftp> open ftp.microsoft.com
+cmnt: Microsoft FTP Service
+cmnt: Name (ftp.microsoft.com:cyp):
+cmnt: 331 Anonymous access allowed, send identity (e-mail name) as password.
+cmnt: 230-This is FTP.Microsoft.Com
+cmnt: Anonymous user logged in.
+cmnt: Remote system type is Windows_NT.
+cmnt: ftp> quote dirstyle
+cmnt: MSDOS-like directory output is on
+cmnt: ftp> quote dirstyle
+cmnt: MSDOS-like directory output is off
+cmnt: ftp> ls
+cmnt: PORT command successful.
+cmnt: Opening ASCII mode data connection for /bin/ls.
+list: 02-25-00 00:00AM <DIR> channel
+list: 02-25-00 00:00AM <DIR> Education
+list: 02-25-00 00:00AM <DIR> enterprise
+list: 06-20-01 00:00AM <DIR> ISN
+list: 05-31-01 00:00AM <DIR> Museum
+list: 02-14-01 00:00AM <DIR> TechNet
+list: 10-24-01 00:00AM <DIR> whql
+list: 02-05-01 00:00AM <DIR> 20Year
+list: 09-26-00 00:00AM <DIR> AccessFoxPro
+list: 12-21-00 00:00AM <DIR> AlbGrp
+list: 05-31-01 00:00AM <DIR> Alexandra
+list: 01-19-01 00:00AM <DIR> anna
+list: 04-06-00 00:00AM <DIR> anz
+list: 05-10-00 00:00AM <DIR> Chase Bobko2
+list: 03-29-00 00:00AM <DIR> cnn
+list: 11-21-00 00:00AM <DIR> Darin
+list: 03-09-00 00:00AM <DIR> digitalchicago
+list: 09-06-00 00:00AM <DIR> Dublin
+list: 01-30-01 00:00AM <DIR> eleanorf
+list: 04-26-01 00:00AM <DIR> girvin
+list: 04-26-00 00:00AM <DIR> Hires
+list: 08-15-00 00:00AM <DIR> HR
+list: 10-24-99 00:00AM 4368384 IMG00022.PCD
+list: 05-18-01 00:00AM <DIR> jacubowsky
+list: 10-12-00 00:00AM <DIR> JFalvey
+list: 03-28-01 00:00AM <DIR> johnci
+list: 07-14-00 00:00AM <DIR> Karin
+list: 09-07-00 00:00AM <DIR> Kjung
+list: 09-28-00 00:00AM <DIR> LarryE
+list: 08-17-00 00:00AM <DIR> Larson
+list: 09-12-00 00:00AM <DIR> marion
+list: 08-09-00 00:00AM <DIR> ms25
+list: 11-16-00 00:00AM <DIR> MS25Brochure
+list: 03-29-00 00:00AM <DIR> MShistory
+list: 09-05-00 00:00AM <DIR> Neils
+list: 08-02-00 00:00AM <DIR> NLM
+list: 09-06-00 00:00AM <DIR> PageOne
+list: 06-27-00 00:00AM <DIR> pccomputing
+list: 05-09-01 00:00AM <DIR> pictures
+list: 07-21-00 00:00AM <DIR> pranks
+list: 08-22-00 00:00AM <DIR> Sean
+list: 08-10-00 00:00AM <DIR> SLeong
+list: 09-07-00 00:00AM <DIR> svr
+list: 07-21-00 00:00AM <DIR> Transcontinental
+list: 10-23-00 00:00AM <DIR> veronist
+list: 06-15-00 00:00AM <DIR> zoe
+list: 07-14-00 00:00AM 2094926 canprankdesk.tif
+list: 07-21-00 00:00AM 95077 Jon Kauffman Enjoys the Good Life.jpg
+list: 07-21-00 00:00AM 52275 Name Plate.jpg
+list: 07-14-00 00:00AM 2250540 Valentineoffprank-HiRes.jpg
+junk: Transfer complete.
+junk: ftp> close
+junk: Thank-You For Using Microsoft Products!
+junk: ftp>
diff --git a/netwerk/streamconv/converters/parse-ftp/U-hethmon.in b/netwerk/streamconv/converters/parse-ftp/U-hethmon.in
new file mode 100644
index 0000000000..2c9887cdbf
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-hethmon.out b/netwerk/streamconv/converters/parse-ftp/U-hethmon.out
new file mode 100644
index 0000000000..dada168791
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-murksw.in b/netwerk/streamconv/converters/parse-ftp/U-murksw.in
new file mode 100644
index 0000000000..0d5786cf2e
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-murksw.out b/netwerk/streamconv/converters/parse-ftp/U-murksw.out
new file mode 100644
index 0000000000..68ff9448e9
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-ncFTPd.in b/netwerk/streamconv/converters/parse-ftp/U-ncFTPd.in
new file mode 100644
index 0000000000..635f556423
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-ncFTPd.out b/netwerk/streamconv/converters/parse-ftp/U-ncFTPd.out
new file mode 100644
index 0000000000..6e3bac46f0
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-no_ug.in b/netwerk/streamconv/converters/parse-ftp/U-no_ug.in
new file mode 100644
index 0000000000..aedd9cff46
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-no_ug.out b/netwerk/streamconv/converters/parse-ftp/U-no_ug.out
new file mode 100644
index 0000000000..5200a9b0e1
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-nogid.in b/netwerk/streamconv/converters/parse-ftp/U-nogid.in
new file mode 100644
index 0000000000..15f77d1ee3
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-nogid.out b/netwerk/streamconv/converters/parse-ftp/U-nogid.out
new file mode 100644
index 0000000000..9a31407c8f
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-proftpd.in b/netwerk/streamconv/converters/parse-ftp/U-proftpd.in
new file mode 100644
index 0000000000..0f8aa138da
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-proftpd.out b/netwerk/streamconv/converters/parse-ftp/U-proftpd.out
new file mode 100644
index 0000000000..02d7a18432
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-wu.in b/netwerk/streamconv/converters/parse-ftp/U-wu.in
new file mode 100644
index 0000000000..e4a822a093
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/U-wu.out b/netwerk/streamconv/converters/parse-ftp/U-wu.out
new file mode 100644
index 0000000000..1ffbf23eb2
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/V-MultiNet.in b/netwerk/streamconv/converters/parse-ftp/V-MultiNet.in
new file mode 100644
index 0000000000..04e66774e6
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/V-MultiNet.out b/netwerk/streamconv/converters/parse-ftp/V-MultiNet.out
new file mode 100644
index 0000000000..a6cddd6191
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/V-VMS-mix.in b/netwerk/streamconv/converters/parse-ftp/V-VMS-mix.in
new file mode 100644
index 0000000000..07ebd7bbac
--- /dev/null
+++ b/netwerk/streamconv/converters/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/streamconv/converters/parse-ftp/V-VMS-mix.out b/netwerk/streamconv/converters/parse-ftp/V-VMS-mix.out
new file mode 100644
index 0000000000..2a5a704c2f
--- /dev/null
+++ b/netwerk/streamconv/converters/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 LOGIN.COM
+01-29-1996 03:33:12 CII-MANUAL.TEX
+01-29-1996 03:33:12 THIS-IS-A-LONG-VMS-FILENAME.WITH-CR-TO-NEXT-LINE
+01-29-1996 03:33:00 ANOTHER-LONG-VMS-FILENAME.WITH-LF-TO-NEXT-LINE
+03-05-1993 18:09:00 CMU-VMS-IP-FTP-FILE
+03-05-1993 18:09:00 2199023255040 MAX_FILESIZE.FILE
diff --git a/netwerk/streamconv/moz.build b/netwerk/streamconv/moz.build
new file mode 100644
index 0000000000..6b02ef5a74
--- /dev/null
+++ b/netwerk/streamconv/moz.build
@@ -0,0 +1,25 @@
+# -*- 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']
+TEST_DIRS += ['test']
+
+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..e8bfb4cb4a
--- /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.
+ */
+ wstring scanTXT(in wstring 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.
+ */
+ wstring scanHTML(in wstring 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..2b8d277700
--- /dev/null
+++ b/netwerk/streamconv/nsIDirIndex.idl
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/** 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 string contentType;
+
+ /**
+ * The fully qualified filename, expressed as a uri
+ *
+ * This is encoded with the encoding specified in
+ * the nsIDirIndexParser, and is also escaped.
+ */
+ attribute string location;
+
+ /**
+ * A description for the filename, which should be
+ * displayed by a viewer
+ */
+ attribute wstring 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;
+};
+
+%{C++
+
+#define NS_DIRINDEX_CID \
+/* { f6913e2e-1dd1-11b2-84be-f455dee342af } */ \
+{ 0xf6913e2e, \
+ 0x1dd1, \
+ 0x11b2, \
+ { 0x84, 0xbe, 0xf4, 0x55, 0xde, 0xe3, 0x42, 0xaf } \
+}
+%}
diff --git a/netwerk/streamconv/nsIDirIndexListener.idl b/netwerk/streamconv/nsIDirIndexListener.idl
new file mode 100644
index 0000000000..a32aacabfd
--- /dev/null
+++ b/netwerk/streamconv/nsIDirIndexListener.idl
@@ -0,0 +1,79 @@
+/* -*- 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);
+
+};
+
+%{C++
+#define NS_IDIRINDEXLISTENER_KEY "@mozilla.org/dirIndexListener;1"
+%}
+
+/**
+ * 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;
+};
+
+%{C++
+#define NS_DIRINDEXPARSER_CID \
+{ /* a0d6ad32-1dd1-11b2-aa55-a40187b54036 */ \
+ 0xa0d6ad32, \
+ 0x1dd1, \
+ 0x11b2, \
+ { 0xaa, 0x55, 0xa4, 0x01, 0x87, 0xb5, 0x40, 0x36 } \
+}
+
+#define NS_DIRINDEXPARSER_CONTRACTID "@mozilla.org/dirIndexParser;1"
+
+%}
diff --git a/netwerk/streamconv/nsIStreamConverter.idl b/netwerk/streamconv/nsIStreamConverter.idl
new file mode 100644
index 0000000000..81cfab8849
--- /dev/null
+++ b/netwerk/streamconv/nsIStreamConverter.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 "nsIStreamListener.idl"
+
+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);
+};
+
+%{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..4884edf282
--- /dev/null
+++ b/netwerk/streamconv/nsIStreamConverterService.idl
@@ -0,0 +1,80 @@
+/* -*- 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 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);
+
+ /**
+ * <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..8473461f5d
--- /dev/null
+++ b/netwerk/streamconv/nsStreamConverterService.cpp
@@ -0,0 +1,558 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+#include "nsStreamConverterService.h"
+#include "nsIComponentRegistrar.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsIAtom.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"
+
+///////////////////////////////////////////////////////////////////
+// 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;
+ nsAutoPtr<nsCString> predecessor;
+
+ explicit BFSTableData(const nsACString& aKey)
+ : key(aKey), color(white), distance(-1)
+ {
+ }
+};
+
+////////////////////////////////////////////////////////////
+// nsISupports methods
+NS_IMPL_ISUPPORTS(nsStreamConverterService, nsIStreamConverterService)
+
+
+////////////////////////////////////////////////////////////
+// nsIStreamConverterService methods
+
+////////////////////////////////////////////////////////////
+// nsStreamConverterService methods
+nsStreamConverterService::nsStreamConverterService()
+{
+}
+
+nsStreamConverterService::~nsStreamConverterService() = default;
+
+// 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.
+
+ nsCOMArray<nsIAtom> *fromEdges = mAdjacencyList.Get(fromStr);
+ if (!fromEdges) {
+ // There is no fromStr vertex, create one.
+ fromEdges = new nsCOMArray<nsIAtom>();
+ mAdjacencyList.Put(fromStr, fromEdges);
+ }
+
+ if (!mAdjacencyList.Get(toStr)) {
+ // There is no toStr vertex, create one.
+ mAdjacencyList.Put(toStr, new nsCOMArray<nsIAtom>());
+ }
+
+ // Now we know the FROM and TO types are represented as keys in the hashtable.
+ // Let's "connect" the verticies, making an edge.
+
+ nsCOMPtr<nsIAtom> 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;
+
+ return fromEdges->AppendObject(vertex) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+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 {
+public:
+ void* operator()(void* anObject) override {
+ nsCString *string = (nsCString*)anObject;
+ delete string;
+ return 0;
+ }
+};
+
+// 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();
+ nsCOMArray<nsIAtom> *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->Count();
+
+ for (int32_t i = 0; i < edgeCount; i++) {
+ nsIAtom* curVertexAtom = data2->ObjectAt(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 = new 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.
+ rv = shortestPath->AppendElement(newContractID) ? NS_OK : NS_ERROR_FAILURE; // XXX this method incorrectly returns a bool
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AppendElement failed");
+
+ // 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::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;
+ }
+
+ nsCOMPtr<nsIStreamListener> chainListener(do_QueryInterface(converter, &rv));
+ 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 = chainListener;
+ }
+ 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)
+{
+ NS_PRECONDITION(aStreamConv != nullptr, "null ptr");
+ if (!aStreamConv) return NS_ERROR_NULL_POINTER;
+
+ *aStreamConv = new nsStreamConverterService();
+ NS_ADDREF(*aStreamConv);
+
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/nsStreamConverterService.h b/netwerk/streamconv/nsStreamConverterService.h
new file mode 100644
index 0000000000..70f419ff91
--- /dev/null
+++ b/netwerk/streamconv/nsStreamConverterService.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsstreamconverterservice__h___
+#define __nsstreamconverterservice__h___
+
+#include "nsIStreamConverterService.h"
+
+#include "nsClassHashtable.h"
+#include "nsCOMArray.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsCString;
+class nsIAtom;
+
+class nsStreamConverterService : public nsIStreamConverterService {
+public:
+ /////////////////////////////////////////////////////
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+
+ /////////////////////////////////////////////////////
+ // nsIStreamConverterService methods
+ NS_DECL_NSISTREAMCONVERTERSERVICE
+
+ /////////////////////////////////////////////////////
+ // nsStreamConverterService methods
+ nsStreamConverterService();
+
+private:
+ virtual ~nsStreamConverterService();
+
+ // 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, nsCOMArray<nsIAtom>> mAdjacencyList;
+};
+
+#endif // __nsstreamconverterservice__h___
diff --git a/netwerk/streamconv/test/Converters.cpp b/netwerk/streamconv/test/Converters.cpp
new file mode 100644
index 0000000000..61b114d77d
--- /dev/null
+++ b/netwerk/streamconv/test/Converters.cpp
@@ -0,0 +1,140 @@
+#include "Converters.h"
+#include "nsIStringStream.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+
+#include <stdio.h>
+
+//////////////////////////////////////////////////
+// TestConverter
+//////////////////////////////////////////////////
+
+#define NS_TESTCONVERTER_CID \
+{ /* B8A067B0-4450-11d3-A16E-0050041CAF44 */ \
+ 0xb8a067b0, \
+ 0x4450, \
+ 0x11d3, \
+ {0xa1, 0x6e, 0x00, 0x50, 0x04, 0x1c, 0xaf, 0x44} \
+}
+
+NS_DEFINE_CID(kTestConverterCID, NS_TESTCONVERTER_CID);
+
+NS_IMPL_ISUPPORTS(TestConverter,
+ nsIStreamConverter,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+TestConverter::TestConverter() {
+}
+
+// Convert aFromStream (of type aFromType), to _retval (nsIInputStream of type aToType).
+// This Convert method simply converts the stream byte-by-byte, to the first character
+// in the aToType "string".
+NS_IMETHODIMP
+TestConverter::Convert(nsIInputStream *aFromStream,
+ const char *aFromType,
+ const char *aToType,
+ nsISupports *ctxt,
+ nsIInputStream **_retval) {
+ char buf[1024+1];
+ uint32_t read;
+ nsresult rv = aFromStream->Read(buf, 1024, &read);
+ if (NS_FAILED(rv) || read == 0) return rv;
+
+ // verify that the data we're converting matches the from type
+ // if it doesn't then we're being handed the wrong data.
+ char fromChar = *aFromType;
+
+ if (fromChar != buf[0]) {
+ printf("We're receiving %c, but are supposed to have %c.\n", buf[0], fromChar);
+ return NS_ERROR_FAILURE;
+ }
+
+
+ // Get the first character
+ char toChar = *aToType;
+
+ for (uint32_t i = 0; i < read; i++)
+ buf[i] = toChar;
+
+ buf[read] = '\0';
+
+ nsCOMPtr<nsIStringInputStream> str
+ (do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = str->SetData(buf, read);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*_retval = str);
+ return NS_OK;
+}
+
+/* This method initializes any internal state before the stream converter
+ * begins asynchronous conversion */
+NS_IMETHODIMP
+TestConverter::AsyncConvertData(const char *aFromType,
+ const char *aToType,
+ nsIStreamListener *aListener,
+ nsISupports *ctxt) {
+ NS_ASSERTION(aListener, "null listener");
+
+ mListener = aListener;
+
+ // based on these types, setup internal state to handle the appropriate conversion.
+ fromType = aFromType;
+ toType = aToType;
+
+ return NS_OK;
+}
+
+// nsIStreamListener method
+/* This method handles asyncronous conversion of data. */
+NS_IMETHODIMP
+TestConverter::OnDataAvailable(nsIRequest* request,
+ nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset,
+ uint32_t count) {
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> convertedStream;
+ // just make a syncronous call to the Convert() method.
+ // Anything can happen here, I just happen to be using the sync call to
+ // do the actual conversion.
+ rv = Convert(inStr, fromType.get(), toType.get(), ctxt, getter_AddRefs(convertedStream));
+ if (NS_FAILED(rv)) return rv;
+
+ uint64_t len = 0;
+ convertedStream->Available(&len);
+
+ uint64_t offset = sourceOffset;
+ while (len > 0) {
+ uint32_t count = saturated(len);
+ rv = mListener->OnDataAvailable(request, ctxt, convertedStream, offset, count);
+ if (NS_FAILED(rv)) return rv;
+
+ offset += count;
+ len -= count;
+ }
+ return NS_OK;
+}
+
+// nsIRequestObserver methods
+/* These methods just pass through directly to the mListener */
+NS_IMETHODIMP
+TestConverter::OnStartRequest(nsIRequest* request, nsISupports *ctxt) {
+ return mListener->OnStartRequest(request, ctxt);
+}
+
+NS_IMETHODIMP
+TestConverter::OnStopRequest(nsIRequest* request, nsISupports *ctxt,
+ nsresult aStatus) {
+ return mListener->OnStopRequest(request, ctxt, aStatus);
+}
+
+nsresult
+CreateTestConverter(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ nsCOMPtr<nsISupports> conv = new TestConverter();
+ return conv->QueryInterface(aIID, aResult);
+}
diff --git a/netwerk/streamconv/test/Converters.h b/netwerk/streamconv/test/Converters.h
new file mode 100644
index 0000000000..d7203262c3
--- /dev/null
+++ b/netwerk/streamconv/test/Converters.h
@@ -0,0 +1,52 @@
+#ifndef Converters_h___
+#define Converters_h___
+
+#include "nsIStreamConverter.h"
+#include "nsIFactory.h"
+#include "nsCOMPtr.h"
+#include "nsStringAPI.h"
+
+#include <algorithm>
+
+/* This file defines stream converter components, and their accompanying factory class.
+ * These converters implement the nsIStreamConverter interface and support both
+ * asynchronous and synchronous stream conversion.
+ */
+
+///////////////////////////////////////////////
+// TestConverter
+
+extern const nsCID kTestConverterCID;
+
+class TestConverter : public nsIStreamConverter {
+ virtual ~TestConverter() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ TestConverter();
+
+ // nsIStreamConverter methods
+ NS_IMETHOD Convert(nsIInputStream *aFromStream, const char *aFromType,
+ const char *aToType, nsISupports *ctxt, nsIInputStream **_retval) override;
+
+
+ NS_IMETHOD AsyncConvertData(const char *aFromType, const char *aToType,
+ nsIStreamListener *aListener, nsISupports *ctxt) override;
+
+ // member data
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCString fromType;
+ nsCString toType;
+};
+
+nsresult CreateTestConverter(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+static inline uint32_t
+saturated(uint64_t aValue)
+{
+ return (uint32_t) std::min(aValue, (uint64_t) UINT32_MAX);
+}
+
+#endif /* !Converters_h___ */
diff --git a/netwerk/streamconv/test/TestStreamConv.cpp b/netwerk/streamconv/test/TestStreamConv.cpp
new file mode 100644
index 0000000000..68b85fdd71
--- /dev/null
+++ b/netwerk/streamconv/test/TestStreamConv.cpp
@@ -0,0 +1,261 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "nsIStreamConverterService.h"
+#include "nsIStreamConverter.h"
+#include "nsICategoryManager.h"
+#include "mozilla/Module.h"
+#include "nsXULAppAPI.h"
+#include "nsIStringStream.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "nsMemory.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIRequest.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+
+#define ASYNC_TEST // undefine this if you want to test sycnronous conversion.
+
+/////////////////////////////////
+// Event pump setup
+/////////////////////////////////
+#ifdef XP_WIN
+#include <windows.h>
+#endif
+
+static int gKeepRunning = 0;
+/////////////////////////////////
+// Event pump END
+/////////////////////////////////
+
+
+/////////////////////////////////
+// Test converters include
+/////////////////////////////////
+#include "Converters.h"
+
+// CID setup
+static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////
+// EndListener - This listener is the final one in the chain. It
+// receives the fully converted data, although it doesn't do anything with
+// the data.
+////////////////////////////////////////////////////////////////////////
+class EndListener final : public nsIStreamListener {
+ ~EndListener() {}
+public:
+ // nsISupports declaration
+ NS_DECL_ISUPPORTS
+
+ EndListener() {}
+
+ // nsIStreamListener method
+ NS_IMETHOD OnDataAvailable(nsIRequest* request, nsISupports *ctxt, nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count) override
+ {
+ nsresult rv;
+ uint32_t read;
+ uint64_t len64;
+ rv = inStr->Available(&len64);
+ if (NS_FAILED(rv)) return rv;
+ uint32_t len = (uint32_t)std::min(len64, (uint64_t)(UINT32_MAX - 1));
+
+ char *buffer = (char*)moz_xmalloc(len + 1);
+ if (!buffer) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = inStr->Read(buffer, len, &read);
+ buffer[len] = '\0';
+ if (NS_SUCCEEDED(rv)) {
+ printf("CONTEXT %p: Received %u bytes and the following data: \n %s\n\n",
+ static_cast<void*>(ctxt), read, buffer);
+ }
+ free(buffer);
+
+ return NS_OK;
+ }
+
+ // nsIRequestObserver methods
+ NS_IMETHOD OnStartRequest(nsIRequest* request, nsISupports *ctxt) override { return NS_OK; }
+
+ NS_IMETHOD OnStopRequest(nsIRequest* request, nsISupports *ctxt,
+ nsresult aStatus) override { return NS_OK; }
+};
+
+NS_IMPL_ISUPPORTS(EndListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+////////////////////////////////////////////////////////////////////////
+// EndListener END
+////////////////////////////////////////////////////////////////////////
+
+nsresult SendData(const char * aData, nsIStreamListener* aListener, nsIRequest* request) {
+ nsresult rv;
+
+ nsCOMPtr<nsIStringInputStream> dataStream
+ (do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dataStream->SetData(aData, strlen(aData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t avail = 0;
+ dataStream->Available(&avail);
+
+ uint64_t offset = 0;
+ while (avail > 0) {
+ uint32_t count = saturated(avail);
+ rv = aListener->OnDataAvailable(request, nullptr, dataStream,
+ offset, count);
+ if (NS_FAILED(rv)) return rv;
+
+ offset += count;
+ avail -= count;
+ }
+ return NS_OK;
+}
+#define SEND_DATA(x) SendData(x, converterListener, request)
+
+static const mozilla::Module::CIDEntry kTestCIDs[] = {
+ { &kTestConverterCID, false, nullptr, CreateTestConverter },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kTestContracts[] = {
+ { NS_ISTREAMCONVERTER_KEY "?from=a/foo&to=b/foo", &kTestConverterCID },
+ { NS_ISTREAMCONVERTER_KEY "?from=b/foo&to=c/foo", &kTestConverterCID },
+ { NS_ISTREAMCONVERTER_KEY "?from=b/foo&to=d/foo", &kTestConverterCID },
+ { NS_ISTREAMCONVERTER_KEY "?from=c/foo&to=d/foo", &kTestConverterCID },
+ { NS_ISTREAMCONVERTER_KEY "?from=d/foo&to=e/foo", &kTestConverterCID },
+ { NS_ISTREAMCONVERTER_KEY "?from=d/foo&to=f/foo", &kTestConverterCID },
+ { NS_ISTREAMCONVERTER_KEY "?from=t/foo&to=k/foo", &kTestConverterCID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kTestCategories[] = {
+ { NS_ISTREAMCONVERTER_KEY, "?from=a/foo&to=b/foo", "x" },
+ { NS_ISTREAMCONVERTER_KEY, "?from=b/foo&to=c/foo", "x" },
+ { NS_ISTREAMCONVERTER_KEY, "?from=b/foo&to=d/foo", "x" },
+ { NS_ISTREAMCONVERTER_KEY, "?from=c/foo&to=d/foo", "x" },
+ { NS_ISTREAMCONVERTER_KEY, "?from=d/foo&to=e/foo", "x" },
+ { NS_ISTREAMCONVERTER_KEY, "?from=d/foo&to=f/foo", "x" },
+ { NS_ISTREAMCONVERTER_KEY, "?from=t/foo&to=k/foo", "x" },
+ { nullptr }
+};
+
+static const mozilla::Module kTestModule = {
+ mozilla::Module::kVersion,
+ kTestCIDs,
+ kTestContracts,
+ kTestCategories
+};
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+ {
+ XRE_AddStaticComponent(&kTestModule);
+
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ nsCOMPtr<nsICategoryManager> catman =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return -1;
+ nsCString previous;
+
+ nsCOMPtr<nsIStreamConverterService> StreamConvService =
+ do_GetService(kStreamConverterServiceCID, &rv);
+ if (NS_FAILED(rv)) return -1;
+
+ // Define the *from* content type and *to* content-type for conversion.
+ static const char fromStr[] = "a/foo";
+ static const char toStr[] = "c/foo";
+
+#ifdef ASYNC_TEST
+ // ASYNCHRONOUS conversion
+
+ // Build up a channel that represents the content we're
+ // starting the transaction with.
+ //
+ // sample multipart mixed content-type string:
+ // "multipart/x-mixed-replacE;boundary=thisrandomstring"
+#if 0
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIURI> dummyURI;
+ rv = NS_NewURI(getter_AddRefs(dummyURI), "http://meaningless");
+ if (NS_FAILED(rv)) return -1;
+
+ rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
+ dummyURI,
+ nullptr, // inStr
+ "text/plain", // content-type
+ -1); // XXX fix contentLength
+ if (NS_FAILED(rv)) return -1;
+
+ nsCOMPtr<nsIRequest> request(do_QueryInterface(channel));
+#endif
+
+ nsCOMPtr<nsIRequest> request;
+
+ // setup a listener to receive the converted data. This guy is the end
+ // listener in the chain, he wants the fully converted (toType) data.
+ // An example of this listener in mozilla would be the DocLoader.
+ nsIStreamListener *dataReceiver = new EndListener();
+ NS_ADDREF(dataReceiver);
+
+ // setup a listener to push the data into. This listener sits inbetween the
+ // unconverted data of fromType, and the final listener in the chain (in this case
+ // the dataReceiver.
+ nsIStreamListener *converterListener = nullptr;
+ rv = StreamConvService->AsyncConvertData(fromStr, toStr,
+ dataReceiver, nullptr, &converterListener);
+ if (NS_FAILED(rv)) return -1;
+ NS_RELEASE(dataReceiver);
+
+ // at this point we have a stream listener to push data to, and the one
+ // that will receive the converted data. Let's mimic On*() calls and get the conversion
+ // going. Typically these On*() calls would be made inside their respective wrappers On*()
+ // methods.
+ rv = converterListener->OnStartRequest(request, nullptr);
+ if (NS_FAILED(rv)) return -1;
+
+ rv = SEND_DATA("aaa");
+ if (NS_FAILED(rv)) return -1;
+
+ rv = SEND_DATA("aaa");
+ if (NS_FAILED(rv)) return -1;
+
+ // Finish the request.
+ rv = converterListener->OnStopRequest(request, nullptr, rv);
+ if (NS_FAILED(rv)) return -1;
+
+ NS_RELEASE(converterListener);
+#else
+ // SYNCHRONOUS conversion
+ nsCOMPtr<nsIInputStream> convertedData;
+ rv = StreamConvService->Convert(inputData, fromStr, toStr,
+ nullptr, getter_AddRefs(convertedData));
+ if (NS_FAILED(rv)) return -1;
+#endif
+
+ // Enter the message pump to allow the URL load to proceed.
+ while ( gKeepRunning ) {
+ if (!NS_ProcessNextEvent(thread))
+ break;
+ }
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/streamconv/test/moz.build b/netwerk/streamconv/test/moz.build
new file mode 100644
index 0000000000..ea081559bc
--- /dev/null
+++ b/netwerk/streamconv/test/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/.
+
+GeckoProgram('TestStreamConv', linkage='dependent')
+
+UNIFIED_SOURCES += [
+ 'Converters.cpp',
+ 'TestStreamConv.cpp',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ DEFINES['NGPREFS'] = True
+ if CONFIG['GNU_CXX']:
+ LDFLAGS += ['-mconsole']
+ else:
+ LDFLAGS += ['-SUBSYSTEM:CONSOLE']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/system/android/moz.build b/netwerk/system/android/moz.build
new file mode 100644
index 0000000000..68288ad10a
--- /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..692b69a7e4
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
@@ -0,0 +1,63 @@
+/* -*- 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 "mozilla/Services.h"
+
+#include "AndroidBridge.h"
+
+namespace java = mozilla::java;
+
+NS_IMPL_ISUPPORTS(nsAndroidNetworkLinkService,
+ nsINetworkLinkService)
+
+nsAndroidNetworkLinkService::nsAndroidNetworkLinkService()
+{
+}
+
+nsAndroidNetworkLinkService::~nsAndroidNetworkLinkService()
+{
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::GetIsLinkUp(bool *aIsUp)
+{
+ 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)
+{
+ 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;
+}
diff --git a/netwerk/system/android/nsAndroidNetworkLinkService.h b/netwerk/system/android/nsAndroidNetworkLinkService.h
new file mode 100644
index 0000000000..9fdda7cae0
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.h
@@ -0,0 +1,24 @@
+/* -*- 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"
+
+class nsAndroidNetworkLinkService: public nsINetworkLinkService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+
+ nsAndroidNetworkLinkService();
+
+private:
+ virtual ~nsAndroidNetworkLinkService();
+};
+
+#endif /* NSANDROIDNETWORKLINKSERVICE_H_ */
diff --git a/netwerk/system/linux/moz.build b/netwerk/system/linux/moz.build
new file mode 100644
index 0000000000..21fc5e2d20
--- /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 += [
+ 'nsNotifyAddrListener_Linux.cpp',
+ ]
+
+FINAL_LIBRARY = 'xul'
diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
new file mode 100644
index 0000000000..c0ec9d90e2
--- /dev/null
+++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
@@ -0,0 +1,549 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 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 <stdarg.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <errno.h>
+#ifndef MOZ_WIDGET_GONK
+#include <ifaddrs.h>
+#include <net/if.h>
+#endif
+
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNotifyAddrListener_Linux.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include <cutils/properties.h>
+#endif
+
+/* a shorter name that better explains what it does */
+#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x)
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+
+NS_IMPL_ISUPPORTS(nsNotifyAddrListener,
+ nsINetworkLinkService,
+ nsIRunnable,
+ nsIObserver)
+
+nsNotifyAddrListener::nsNotifyAddrListener()
+ : mLinkUp(true) // assume true by default
+ , mStatusKnown(false)
+ , mAllowChangedEvent(true)
+ , mCoalescingActive(false)
+{
+ mShutdownPipe[0] = -1;
+ mShutdownPipe[1] = -1;
+}
+
+nsNotifyAddrListener::~nsNotifyAddrListener()
+{
+ MOZ_ASSERT(!mThread, "nsNotifyAddrListener thread shutdown failed");
+
+ if (mShutdownPipe[0] != -1) {
+ EINTR_RETRY(close(mShutdownPipe[0]));
+ }
+ if (mShutdownPipe[1] != -1) {
+ EINTR_RETRY(close(mShutdownPipe[1]));
+ }
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetIsLinkUp(bool *aIsUp)
+{
+ // XXX This function has not yet been implemented for this platform
+ *aIsUp = mLinkUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::GetLinkStatusKnown(bool *aIsUp)
+{
+ // XXX This function has not yet been implemented for this platform
+ *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;
+}
+
+//
+// Figure out the current "network identification" string.
+//
+// It detects the IP of the default gateway in the routing table, then the MAC
+// address of that IP in the ARP table before it hashes that string (to avoid
+// information leakage).
+//
+void nsNotifyAddrListener::calculateNetworkId(void)
+{
+ const char *kProcRoute = "/proc/net/route"; /* IPv4 routes */
+ const char *kProcArp = "/proc/net/arp";
+ bool found = false;
+
+ FILE *froute = fopen(kProcRoute, "r");
+ if (froute) {
+ char buffer[512];
+ uint32_t gw = 0;
+ char *l = fgets(buffer, sizeof(buffer), froute);
+ if (l) {
+ /* skip the title line */
+ while (l) {
+ char interf[32];
+ uint32_t dest;
+ uint32_t gateway;
+ l = fgets(buffer, sizeof(buffer), froute);
+ if (l) {
+ buffer[511]=0; /* as a precaution */
+ int val = sscanf(buffer, "%31s %x %x",
+ interf, &dest, &gateway);
+ if ((3 == val) && !dest) {
+ gw = gateway;
+ break;
+ }
+ }
+ }
+ }
+ fclose(froute);
+
+ if (gw) {
+ /* create a string to search for in the arp table */
+ char searchfor[16];
+ SprintfLiteral(searchfor, "%d.%d.%d.%d",
+ gw & 0xff,
+ (gw >> 8) & 0xff,
+ (gw >> 16) & 0xff,
+ gw >> 24);
+
+ FILE *farp = fopen(kProcArp, "r");
+ if (farp) {
+ l = fgets(buffer, sizeof(buffer), farp);
+ while (l) {
+ /* skip the title line */
+ l = fgets(buffer, sizeof(buffer), farp);
+ if (l) {
+ buffer[511]=0; /* as a precaution */
+ int p[4];
+ char type[16];
+ char flags[16];
+ char hw[32];
+ if (7 == sscanf(buffer, "%u.%u.%u.%u %15s %15s %31s",
+ &p[0], &p[1], &p[2], &p[3],
+ type, flags, hw)) {
+ uint32_t searchip = p[0] | (p[1] << 8) |
+ (p[2] << 16) | (p[3] << 24);
+ if (gw == searchip) {
+ LOG(("networkid: MAC %s\n", hw));
+ nsAutoCString mac(hw);
+ // This 'addition' could potentially be a
+ // fixed number from the profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ SHA1Sum sha1;
+ nsCString combined(mac + addition);
+ sha1.update(combined.get(), combined.Length());
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsCString 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()));
+ if (mNetworkId != output) {
+ // new id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
+ }
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ fclose(farp);
+ } /* if (farp) */
+ } /* if (gw) */
+ } /* if (froute) */
+ if (!found) {
+ // no id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
+ }
+}
+
+//
+// Check if there's a network interface available to do networking on.
+//
+void nsNotifyAddrListener::checkLink(void)
+{
+#ifdef MOZ_WIDGET_GONK
+ // b2g instead has NetworkManager.js which handles UP/DOWN
+#else
+ struct ifaddrs *list;
+ struct ifaddrs *ifa;
+ bool link = false;
+ bool prevLinkUp = mLinkUp;
+
+ if (getifaddrs(&list))
+ return;
+
+ // Walk through the linked list, maintaining head pointer so we can free
+ // list later
+
+ for (ifa = list; ifa != NULL; ifa = ifa->ifa_next) {
+ int family;
+ if (ifa->ifa_addr == NULL)
+ continue;
+
+ family = ifa->ifa_addr->sa_family;
+
+ if ((family == AF_INET || family == AF_INET6) &&
+ (ifa->ifa_flags & IFF_RUNNING) &&
+ !(ifa->ifa_flags & IFF_LOOPBACK)) {
+ // An interface that is UP and not loopback
+ link = true;
+ break;
+ }
+ }
+ mLinkUp = link;
+ freeifaddrs(list);
+
+ if (prevLinkUp != mLinkUp) {
+ // UP/DOWN status changed, send appropriate UP/DOWN event
+ SendEvent(mLinkUp ?
+ NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
+ }
+#endif
+}
+
+void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
+{
+ struct nlmsghdr *nlh;
+
+ // The buffer size below, (4095) was chosen partly based on testing and
+ // partly on existing sample source code using this size. It needs to be
+ // large enough to hold the netlink messages from the kernel.
+ char buffer[4095];
+ struct rtattr *attr;
+ int attr_len;
+ const struct ifaddrmsg* newifam;
+
+
+ ssize_t rc = EINTR_RETRY(recv(aNetlinkSocket, buffer, sizeof(buffer), 0));
+ if (rc < 0) {
+ return;
+ }
+ size_t netlink_bytes = rc;
+
+ nlh = reinterpret_cast<struct nlmsghdr *>(buffer);
+
+ bool networkChange = false;
+
+ for (; NLMSG_OK(nlh, netlink_bytes);
+ nlh = NLMSG_NEXT(nlh, netlink_bytes)) {
+ char prefixaddr[INET6_ADDRSTRLEN];
+ char localaddr[INET6_ADDRSTRLEN];
+ char* addr = nullptr;
+ prefixaddr[0] = localaddr[0] = '\0';
+
+ if (NLMSG_DONE == nlh->nlmsg_type) {
+ break;
+ }
+
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address\n"));
+ newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
+
+ if ((newifam->ifa_family != AF_INET) &&
+ (newifam->ifa_family != AF_INET6)) {
+ continue;
+ }
+
+ attr = IFA_RTA (newifam);
+ attr_len = IFA_PAYLOAD (nlh);
+ for (;attr_len && RTA_OK (attr, attr_len);
+ attr = RTA_NEXT (attr, attr_len)) {
+ if (attr->rta_type == IFA_ADDRESS) {
+ if (newifam->ifa_family == AF_INET) {
+ struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
+ inet_ntop(AF_INET, in, prefixaddr, INET_ADDRSTRLEN);
+ } else {
+ struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
+ inet_ntop(AF_INET6, in, prefixaddr, INET6_ADDRSTRLEN);
+ }
+ } else if (attr->rta_type == IFA_LOCAL) {
+ if (newifam->ifa_family == AF_INET) {
+ struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
+ inet_ntop(AF_INET, in, localaddr, INET_ADDRSTRLEN);
+ } else {
+ struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
+ inet_ntop(AF_INET6, in, localaddr, INET6_ADDRSTRLEN);
+ }
+ }
+ }
+ if (localaddr[0]) {
+ addr = localaddr;
+ } else if (prefixaddr[0]) {
+ addr = prefixaddr;
+ } else {
+ continue;
+ }
+ if (nlh->nlmsg_type == RTM_NEWADDR) {
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: a new address "
+ "- %s.", addr));
+ struct ifaddrmsg* ifam;
+ nsCString addrStr;
+ addrStr.Assign(addr);
+ if (mAddressInfo.Get(addrStr, &ifam)) {
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: the address "
+ "already known."));
+ if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: but "
+ "the address info has been changed."));
+ networkChange = true;
+ memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
+ }
+ } else {
+ networkChange = true;
+ ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
+ memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
+ mAddressInfo.Put(addrStr,ifam);
+ }
+ } else {
+ LOG(("nsNotifyAddrListener::OnNetlinkMessage: an address "
+ "has been deleted - %s.", addr));
+ networkChange = true;
+ nsCString addrStr;
+ addrStr.Assign(addr);
+ mAddressInfo.Remove(addrStr);
+ }
+ }
+
+ if (networkChange && mAllowChangedEvent) {
+ NetworkChanged();
+ }
+
+ if (networkChange) {
+ checkLink();
+ }
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::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)); // clear addr
+
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
+
+ if (bind(netlinkSocket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ // failure!
+ EINTR_RETRY(close(netlinkSocket));
+ return NS_ERROR_FAILURE;
+ }
+
+ // switch the socket into non-blocking
+ int flags = fcntl(netlinkSocket, F_GETFL, 0);
+ (void)fcntl(netlinkSocket, F_SETFL, flags | O_NONBLOCK);
+
+ 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;
+
+ calculateNetworkId();
+
+ nsresult rv = NS_OK;
+ bool shutdown = false;
+ int pollWait = -1;
+ while (!shutdown) {
+ int rc = EINTR_RETRY(poll(fds, 2, pollWait));
+
+ 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;
+ }
+ if (mCoalescingActive) {
+ // check if coalescing period should continue
+ double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds();
+ if (period >= kNetworkChangeCoalescingPeriod) {
+ SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
+ calculateNetworkId();
+ mCoalescingActive = false;
+ pollWait = -1; // restore to default
+ } else {
+ // wait no longer than to the end of the period
+ pollWait = static_cast<int>
+ (kNetworkChangeCoalescingPeriod - period);
+ }
+ }
+ }
+
+ EINTR_RETRY(close(netlinkSocket));
+
+ return rv;
+}
+
+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);
+
+ Preferences::AddBoolVarCache(&mAllowChangedEvent,
+ NETWORK_NOTIFY_CHANGED_PREF, true);
+
+ if (-1 == pipe(mShutdownPipe)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsNotifyAddrListener::Shutdown(void)
+{
+ // remove xpcom shutdown observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->RemoveObserver(this, "xpcom-shutdown-threads");
+
+ LOG(("write() to signal thread shutdown\n"));
+
+ // 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 nsNotifyAddrListener holds
+ // onto the thread and the thread holds onto the nsNotifyAddrListener
+ // via its mRunnable
+ mThread = 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;
+ LOG(("NetworkChanged: coalescing period started\n"));
+ }
+ return NS_OK;
+}
+
+/* Sends the given event. Assumes aEventID never goes out of scope (static
+ * strings are ideal).
+ */
+nsresult
+nsNotifyAddrListener::SendEvent(const char *aEventID)
+{
+ if (!aEventID)
+ return NS_ERROR_NULL_POINTER;
+
+ LOG(("SendEvent: %s\n", aEventID));
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIRunnable> event = new ChangeEvent(this, aEventID);
+ if (NS_FAILED(rv = NS_DispatchToMainThread(event)))
+ NS_WARNING("Failed to dispatch ChangeEvent");
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::ChangeEvent::Run()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(
+ mService, NS_NETWORK_LINK_TOPIC,
+ NS_ConvertASCIItoUTF16(mEventID).get());
+ return NS_OK;
+}
diff --git a/netwerk/system/linux/nsNotifyAddrListener_Linux.h b/netwerk/system/linux/nsNotifyAddrListener_Linux.h
new file mode 100644
index 0000000000..a2b8f22779
--- /dev/null
+++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 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_LINUX_H_
+#define NSNOTIFYADDRLISTENER_LINUX_H_
+
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include "nsINetworkLinkService.h"
+#include "nsIRunnable.h"
+#include "nsIObserver.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "nsITimer.h"
+#include "nsClassHashtable.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);
+
+private:
+ class ChangeEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ ChangeEvent(nsINetworkLinkService *aService, const char *aEventID)
+ : mService(aService), mEventID(aEventID) {
+ }
+ private:
+ nsCOMPtr<nsINetworkLinkService> mService;
+ const char *mEventID;
+ };
+
+ // Called when xpcom-shutdown-threads is received.
+ nsresult Shutdown(void);
+
+ // Called when a network change was detected
+ nsresult NetworkChanged();
+
+ // Sends the network event.
+ nsresult SendEvent(const char *aEventID);
+
+ // Figure out the current "network identification"
+ void calculateNetworkId(void);
+ nsCString mNetworkId;
+
+ // Checks if there's a network "link"
+ void checkLink(void);
+
+ // Deals with incoming NETLINK messages.
+ void OnNetlinkMessage(int NetlinkSocket);
+
+ nsCOMPtr<nsIThread> mThread;
+
+ // The network is up.
+ bool mLinkUp;
+
+ // The network's up/down status is known.
+ bool mStatusKnown;
+
+ // A pipe to signal shutdown with.
+ int mShutdownPipe[2];
+
+ // Network changed events are enabled
+ bool mAllowChangedEvent;
+
+ // Flag set while coalescing change events
+ bool mCoalescingActive;
+
+ // Time stamp for first event during coalescing
+ mozilla::TimeStamp mChangeTime;
+
+ // Seen Ip addresses. For Ipv6 addresses some time router renews their
+ // lifetime and we should not detect this as a network link change, so we
+ // keep info about all seen addresses.
+ nsClassHashtable<nsCStringHashKey, struct ifaddrmsg> mAddressInfo;
+ };
+
+#endif /* NSNOTIFYADDRLISTENER_LINUX_H_ */
diff --git a/netwerk/system/mac/moz.build b/netwerk/system/mac/moz.build
new file mode 100644
index 0000000000..b90b8d67d2
--- /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['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h
new file mode 100644
index 0000000000..ee54622478
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.h
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSNETWORKLINKSERVICEMAC_H_
+#define NSNETWORKLINKSERVICEMAC_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+
+#include <SystemConfiguration/SCNetworkReachability.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+
+class nsNetworkLinkService : public nsINetworkLinkService,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsNetworkLinkService();
+
+ nsresult Init();
+ nsresult Shutdown();
+
+protected:
+ virtual ~nsNetworkLinkService();
+
+private:
+ bool mLinkUp;
+ bool mStatusKnown;
+
+ // Toggles allowing the sending of network-changed event.
+ bool mAllowChangedEvent;
+
+ SCNetworkReachabilityRef mReachability;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mRunLoopSource;
+ SCDynamicStoreRef mStoreRef;
+
+ void UpdateReachability();
+ void SendEvent(bool aNetworkChanged);
+ static void ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void *info);
+ static void IPConfigChanged(SCDynamicStoreRef store,
+ CFArrayRef changedKeys,
+ void *info);
+ void calculateNetworkId(void);
+ nsCString mNetworkId;
+};
+
+#endif /* NSNETWORKLINKSERVICEMAC_H_ */
diff --git a/netwerk/system/mac/nsNetworkLinkService.mm b/netwerk/system/mac/nsNetworkLinkService.mm
new file mode 100644
index 0000000000..ac6a015fbe
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -0,0 +1,530 @@
+/* -*- 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 <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 "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Telemetry.h"
+#include "nsNetworkLinkService.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)
+
+// 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 != NULL);
+}
+
+// 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)
+ , mAllowChangedEvent(true)
+ , mReachability(nullptr)
+ , mCFRunLoop(nullptr)
+ , mRunLoopSource(nullptr)
+ , mStoreRef(nullptr)
+{
+}
+
+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;
+}
+
+#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 char *getMac(struct sockaddr_dl *sdl, char *buf, size_t bufsize)
+{
+ char *cp;
+ int n, p = 0;
+
+ buf[0] = 0;
+ cp = (char *)LLADDR(sdl);
+ n = sdl->sdl_alen;
+ if (n > 0) {
+ while (--n >= 0) {
+ p += snprintf(&buf[p], bufsize - p, "%02x%s",
+ *cp++ & 0xff, n > 0 ? ":" : "");
+ }
+ }
+ return buf;
+}
+
+/* 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)) {
+ 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, NULL, &needed, NULL, 0) < 0) {
+ return false;
+ }
+ if (needed == 0) {
+ // empty table
+ return false;
+ }
+
+ UniquePtr <char[]>buf(new char[needed]);
+
+ for (;;) {
+ st = sysctl(mib, 6, &buf[0], &needed, NULL, 0);
+ if (st == 0 || errno != ENOMEM) {
+ break;
+ }
+ needed += needed / 8;
+
+ auto tmp = MakeUnique<char[]>(needed);
+ memcpy(&tmp[0], &buf[0], needed);
+ buf = 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;
+}
+
+static int routingTable(char *gw, size_t aGwLen)
+{
+ size_t needed;
+ int mib[6];
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa;
+ struct sockaddr_in *sockin;
+
+ 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, NULL, &needed, NULL, 0) < 0) {
+ return 1;
+ }
+
+ UniquePtr <char[]>buf(new char[needed]);
+
+ if (sysctl(mib, 6, &buf[0], &needed, NULL, 0) < 0) {
+ return 3;
+ }
+
+ rtm = reinterpret_cast<struct rt_msghdr *>(&buf[0]);
+ sa = reinterpret_cast<struct sockaddr *>(rtm + 1);
+ sa = reinterpret_cast<struct sockaddr *>(SA_SIZE(sa) + (char *)sa);
+ sockin = reinterpret_cast<struct sockaddr_in *>(sa);
+ inet_ntop(AF_INET, &sockin->sin_addr.s_addr, gw, aGwLen-1);
+
+ return 0;
+}
+
+//
+// Figure out the current "network identification" string.
+//
+// It detects the IP of the default gateway in the routing table, then the MAC
+// address of that IP in the ARP table before it hashes that string (to avoid
+// information leakage).
+//
+void nsNetworkLinkService::calculateNetworkId(void)
+{
+ bool found = false;
+ char hw[MAXHOSTNAMELEN];
+ if (!routingTable(hw, sizeof(hw))) {
+ char mac[256]; // big enough for a printable MAC address
+ if (scanArp(hw, mac, sizeof(mac))) {
+ LOG(("networkid: MAC %s\n", hw));
+ nsAutoCString mac(hw);
+ // This 'addition' could potentially be a
+ // fixed number from the profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ SHA1Sum sha1;
+ nsCString combined(mac + addition);
+ sha1.update(combined.get(), combined.Length());
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsCString 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()));
+ if (mNetworkId != output) {
+ // new id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
+ }
+ found = true;
+ }
+ }
+ if (!found) {
+ // no id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
+ }
+}
+
+
+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::IPConfigChanged(SCDynamicStoreRef aStoreREf,
+ CFArrayRef aChangedKeys,
+ void *aInfo)
+{
+ nsNetworkLinkService *service =
+ static_cast<nsNetworkLinkService*>(aInfo);
+ service->SendEvent(true);
+}
+
+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);
+
+ Preferences::AddBoolVarCache(&mAllowChangedEvent,
+ NETWORK_NOTIFY_CHANGED_PREF, true);
+
+ // 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(NULL,
+ (struct sockaddr *)&addr);
+ if (!mReachability) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCNetworkReachabilityContext context = {0, this, NULL, NULL, NULL};
+ if (!::SCNetworkReachabilitySetCallback(mReachability,
+ ReachabilityChanged,
+ &context)) {
+ NS_WARNING("SCNetworkReachabilitySetCallback failed.");
+ ::CFRelease(mReachability);
+ mReachability = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCDynamicStoreContext storeContext = {0, this, NULL, NULL, NULL};
+ mStoreRef =
+ ::SCDynamicStoreCreate(NULL,
+ CFSTR("AddIPAddressListChangeCallbackSCF"),
+ IPConfigChanged, &storeContext);
+
+ CFStringRef patterns[2] = {NULL, NULL};
+ OSStatus err = getErrorCodePtr(mStoreRef);
+ if (err == noErr) {
+ // This pattern is "State:/Network/Service/[^/]+/IPv4".
+ patterns[0] =
+ ::SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+ kSCDynamicStoreDomainState,
+ kSCCompAnyRegex,
+ kSCEntNetIPv4);
+ err = getErrorCodePtr(patterns[0]);
+ if (err == noErr) {
+ // This pattern is "State:/Network/Service/[^/]+/IPv6".
+ patterns[1] =
+ ::SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+ kSCDynamicStoreDomainState,
+ kSCCompAnyRegex,
+ kSCEntNetIPv6);
+ err = getErrorCodePtr(patterns[1]);
+ }
+ }
+
+ CFArrayRef patternList = NULL;
+ // 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(NULL, (const void **) patterns,
+ 2, &kCFTypeArrayCallBacks);
+ if (!patternList) {
+ err = -1;
+ }
+ }
+ if (err == noErr) {
+ err =
+ getErrorCodeBool(::SCDynamicStoreSetNotificationKeys(mStoreRef,
+ NULL,
+ patternList));
+ }
+
+ if (err == noErr) {
+ mRunLoopSource =
+ ::SCDynamicStoreCreateRunLoopSource(NULL, mStoreRef, 0);
+ err = getErrorCodePtr(mRunLoopSource);
+ }
+
+ CFReleaseSafe(patterns[0]);
+ CFReleaseSafe(patterns[1]);
+ 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 = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ ::CFRetain(mCFRunLoop);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ if (!::SCNetworkReachabilityScheduleWithRunLoop(mReachability, mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityScheduleWIthRunLoop failed.");
+ ::CFRelease(mReachability);
+ mReachability = NULL;
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UpdateReachability();
+
+ 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;
+
+ 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::SendEvent(bool aNetworkChanged)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (!observerService) {
+ return;
+ }
+
+ const char *event;
+ if (aNetworkChanged) {
+ if (!mAllowChangedEvent) {
+ return;
+ }
+ event = NS_NETWORK_LINK_DATA_CHANGED;
+ } else if (!mStatusKnown) {
+ event = NS_NETWORK_LINK_DATA_UNKNOWN;
+ } else {
+ event = mLinkUp ? NS_NETWORK_LINK_DATA_UP
+ : NS_NETWORK_LINK_DATA_DOWN;
+ }
+ LOG(("SendEvent: network is '%s'\n", event));
+
+ observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this),
+ NS_NETWORK_LINK_TOPIC,
+ NS_ConvertASCIItoUTF16(event).get());
+}
+
+/* static */
+void
+nsNetworkLinkService::ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void *info)
+{
+ nsNetworkLinkService *service =
+ static_cast<nsNetworkLinkService*>(info);
+
+ service->UpdateReachability();
+ service->SendEvent(false);
+ service->calculateNetworkId();
+}
diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build
new file mode 100644
index 0000000000..08c41342bd
--- /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']
+
+elif CONFIG['OS_ARCH'] == 'Linux':
+ DIRS += ['linux']
diff --git a/netwerk/system/win32/moz.build b/netwerk/system/win32/moz.build
new file mode 100644
index 0000000000..31b46b6b57
--- /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..5d1ec3a61f
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.cpp
@@ -0,0 +1,743 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 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 <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 <iprtrmib.h>
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNotifyAddrListener.h"
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Services.h"
+#include "nsCRT.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.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)
+
+static HMODULE sNetshell;
+static decltype(NcFreeNetconProperties)* sNcFreeNetconProperties;
+
+static HMODULE sIphlpapi;
+static decltype(NotifyIpInterfaceChange)* sNotifyIpInterfaceChange;
+static decltype(CancelMibChangeNotify2)* sCancelMibChangeNotify2;
+
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+#define NETWORK_NOTIFY_IPV6_PREF "network.notify.IPv6"
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+static void InitIphlpapi(void)
+{
+ if (!sIphlpapi) {
+ sIphlpapi = LoadLibraryW(L"Iphlpapi.dll");
+ if (sIphlpapi) {
+ sNotifyIpInterfaceChange = (decltype(NotifyIpInterfaceChange)*)
+ GetProcAddress(sIphlpapi, "NotifyIpInterfaceChange");
+ sCancelMibChangeNotify2 = (decltype(CancelMibChangeNotify2)*)
+ GetProcAddress(sIphlpapi, "CancelMibChangeNotify2");
+ } else {
+ NS_WARNING("Failed to load Iphlpapi.dll - cannot detect network"
+ " changes!");
+ }
+ }
+}
+
+static void InitNetshellLibrary(void)
+{
+ if (!sNetshell) {
+ sNetshell = LoadLibraryW(L"Netshell.dll");
+ if (sNetshell) {
+ sNcFreeNetconProperties = (decltype(NcFreeNetconProperties)*)
+ GetProcAddress(sNetshell, "NcFreeNetconProperties");
+ }
+ }
+}
+
+static void FreeDynamicLibraries(void)
+{
+ if (sNetshell) {
+ sNcFreeNetconProperties = nullptr;
+ FreeLibrary(sNetshell);
+ sNetshell = nullptr;
+ }
+ if (sIphlpapi) {
+ sNotifyIpInterfaceChange = nullptr;
+ sCancelMibChangeNotify2 = nullptr;
+ FreeLibrary(sIphlpapi);
+ sIphlpapi = nullptr;
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsNotifyAddrListener,
+ nsINetworkLinkService,
+ nsIRunnable,
+ nsIObserver)
+
+nsNotifyAddrListener::nsNotifyAddrListener()
+ : mLinkUp(true) // assume true by default
+ , mStatusKnown(false)
+ , mCheckAttempted(false)
+ , mCheckEvent(nullptr)
+ , mShutdown(false)
+ , mIPInterfaceChecksum(0)
+ , mAllowChangedEvent(true)
+ , mIPv6Changes(false)
+ , mCoalescingActive(false)
+{
+ InitIphlpapi();
+}
+
+nsNotifyAddrListener::~nsNotifyAddrListener()
+{
+ NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed");
+ FreeDynamicLibraries();
+}
+
+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;
+}
+
+static bool macAddr(BYTE addr[], DWORD len, char *buf, size_t buflen)
+{
+ buf[0] = '\0';
+ if (!addr || !len || (len * 3 > buflen)) {
+ return false;
+ }
+
+ for (DWORD i = 0; i < len; ++i) {
+ sprintf_s(buf + (i * 3), sizeof(buf + (i * 3)),
+ "%02x%s", addr[i], (i == len-1) ? "" : ":");
+ }
+ return true;
+}
+
+bool nsNotifyAddrListener::findMac(char *gateway)
+{
+ // query for buffer size needed
+ DWORD dwActualSize = 0;
+ bool found = FALSE;
+
+ // GetIpNetTable gets the IPv4 to physical address mapping table
+ DWORD status = GetIpNetTable(NULL, &dwActualSize, FALSE);
+ if (status == ERROR_INSUFFICIENT_BUFFER) {
+ // the expected route, now with a known buffer size
+ UniquePtr <char[]>buf(new char[dwActualSize]);
+ PMIB_IPNETTABLE pIpNetTable =
+ reinterpret_cast<PMIB_IPNETTABLE>(&buf[0]);
+
+ status = GetIpNetTable(pIpNetTable, &dwActualSize, FALSE);
+
+ if (status == NO_ERROR) {
+ for (DWORD i = 0; i < pIpNetTable->dwNumEntries; ++i) {
+ DWORD dwCurrIndex = pIpNetTable->table[i].dwIndex;
+ char hw[256];
+
+ if (!macAddr(pIpNetTable->table[i].bPhysAddr,
+ pIpNetTable->table[i].dwPhysAddrLen,
+ hw, sizeof(hw))) {
+ // failed to get the MAC
+ continue;
+ }
+
+ struct in_addr addr;
+ addr.s_addr = pIpNetTable->table[i].dwAddr;
+
+ if (!strcmp(gateway, inet_ntoa(addr))) {
+ LOG(("networkid: MAC %s\n", hw));
+ nsAutoCString mac(hw);
+ // This 'addition' could potentially be a
+ // fixed number from the profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ SHA1Sum sha1;
+ nsCString combined(mac + addition);
+ sha1.update(combined.get(), combined.Length());
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsCString newString(reinterpret_cast<char*>(digest),
+ SHA1Sum::kHashSize);
+ Base64Encode(newString, output);
+ LOG(("networkid: id %s\n", output.get()));
+ if (mNetworkId != output) {
+ // new id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 1);
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 2);
+ }
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ return found;
+}
+
+// returns 'true' when the gw is found and stored
+static bool defaultgw(char *aGateway, size_t aGatewayLen)
+{
+ PMIB_IPFORWARDTABLE pIpForwardTable = NULL;
+
+ DWORD dwSize = 0;
+ if (GetIpForwardTable(NULL, &dwSize, 0) != ERROR_INSUFFICIENT_BUFFER) {
+ return false;
+ }
+
+ UniquePtr <char[]>buf(new char[dwSize]);
+ pIpForwardTable = reinterpret_cast<PMIB_IPFORWARDTABLE>(&buf[0]);
+
+ // Note that the IPv4 addresses returned in GetIpForwardTable entries are
+ // in network byte order
+
+ DWORD retVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0);
+ if (retVal == NO_ERROR) {
+ for (unsigned int i = 0; i < pIpForwardTable->dwNumEntries; ++i) {
+ // Convert IPv4 addresses to strings
+ struct in_addr IpAddr;
+ IpAddr.S_un.S_addr = static_cast<u_long>
+ (pIpForwardTable->table[i].dwForwardDest);
+ char *ipStr = inet_ntoa(IpAddr);
+ if (ipStr && !strcmp("0.0.0.0", ipStr)) {
+ // Default gateway!
+ IpAddr.S_un.S_addr = static_cast<u_long>
+ (pIpForwardTable->table[i].dwForwardNextHop);
+ ipStr = inet_ntoa(IpAddr);
+ if (ipStr) {
+ strcpy_s(aGateway, aGatewayLen, ipStr);
+ return true;
+ }
+ }
+ } // for loop
+ }
+
+ return false;
+}
+
+//
+// Figure out the current "network identification" string.
+//
+// It detects the IP of the default gateway in the routing table, then the MAC
+// address of that IP in the ARP table before it hashes that string (to avoid
+// information leakage).
+//
+void nsNotifyAddrListener::calculateNetworkId(void)
+{
+ bool found = FALSE;
+ char gateway[128];
+ if (defaultgw(gateway, sizeof(gateway) )) {
+ found = findMac(gateway);
+ }
+ if (!found) {
+ // no id
+ Telemetry::Accumulate(Telemetry::NETWORK_ID, 0);
+ }
+}
+
+// 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) {
+ calculateNetworkId();
+ SendEvent(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()
+{
+ PR_SetCurrentThreadName("Link Monitor");
+
+ mStartTime = TimeStamp::Now();
+
+ calculateNetworkId();
+
+ DWORD waitTime = INFINITE;
+
+ if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 || !mIPv6Changes) {
+ // For Windows versions which are older than Vista which lack
+ // NotifyIpInterfaceChange. Note this means no IPv6 support.
+ HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY);
+
+ HANDLE handles[2] = { ev, mCheckEvent };
+ OVERLAPPED overlapped = { 0 };
+ bool shuttingDown = false;
+
+ overlapped.hEvent = ev;
+ while (!shuttingDown) {
+ HANDLE h;
+ DWORD ret = NotifyAddrChange(&h, &overlapped);
+
+ if (ret == ERROR_IO_PENDING) {
+ ret = WaitForMultipleObjects(2, handles, FALSE, waitTime);
+ if (ret == WAIT_OBJECT_0) {
+ CheckLinkStatus();
+ } else if (!mShutdown) {
+ waitTime = nextCoalesceWaitTime();
+ } else {
+ shuttingDown = true;
+ }
+ } else {
+ shuttingDown = true;
+ }
+ }
+ CloseHandle(ev);
+ } else {
+ // Windows Vista and newer versions.
+ HANDLE interfacechange;
+ // The callback will simply invoke CheckLinkStatus()
+ DWORD ret = sNotifyIpInterfaceChange(
+ AF_UNSPEC, // IPv4 and IPv6
+ (PIPINTERFACE_CHANGE_CALLBACK)OnInterfaceChange,
+ this, // pass to callback
+ false, // no initial notification
+ &interfacechange);
+
+ if (ret == NO_ERROR) {
+ do {
+ ret = WaitForSingleObject(mCheckEvent, waitTime);
+ if (!mShutdown) {
+ waitTime = nextCoalesceWaitTime();
+ }
+ else {
+ break;
+ }
+ } while (ret != WAIT_FAILED);
+ sCancelMibChangeNotify2(interfacechange);
+ } else {
+ LOG(("Link Monitor: sNotifyIpInterfaceChange 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);
+
+ Preferences::AddBoolVarCache(&mAllowChangedEvent,
+ NETWORK_NOTIFY_CHANGED_PREF, true);
+ Preferences::AddBoolVarCache(&mIPv6Changes,
+ NETWORK_NOTIFY_IPV6_PREF, false);
+
+ mCheckEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ NS_ENSURE_TRUE(mCheckEvent, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = NS_NewThread(getter_AddRefs(mThread), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+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->Shutdown() : 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 aEventID never goes out of scope (static
+ * strings are ideal).
+ */
+nsresult
+nsNotifyAddrListener::SendEvent(const char *aEventID)
+{
+ if (!aEventID)
+ return NS_ERROR_NULL_POINTER;
+
+ LOG(("SendEvent: network is '%s'\n", aEventID));
+
+ nsresult rv;
+ nsCOMPtr<nsIRunnable> event = new ChangeEvent(this, aEventID);
+ if (NS_FAILED(rv = NS_DispatchToMainThread(event)))
+ NS_WARNING("Failed to dispatch ChangeEvent");
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNotifyAddrListener::ChangeEvent::Run()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(
+ mService, NS_NETWORK_LINK_TOPIC,
+ NS_ConvertASCIItoUTF16(mEventID).get());
+ return NS_OK;
+}
+
+
+// Bug 465158 features an explanation for this check. ICS being "Internet
+// Connection Sharing). The description says it is always IP address
+// 192.168.0.1 for this case.
+bool
+nsNotifyAddrListener::CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter)
+{
+ if (!aAdapter->FirstUnicastAddress)
+ return false;
+
+ LPSOCKADDR aAddress = aAdapter->FirstUnicastAddress->Address.lpSockaddr;
+ if (!aAddress)
+ return false;
+
+ PSOCKADDR_IN in_addr = (PSOCKADDR_IN)aAddress;
+ bool isGateway = (aAddress->sa_family == AF_INET &&
+ in_addr->sin_addr.S_un.S_un_b.s_b1 == 192 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b2 == 168 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b3 == 0 &&
+ in_addr->sin_addr.S_un.S_un_b.s_b4 == 1);
+
+ if (isGateway)
+ isGateway = CheckICSStatus(aAdapter->FriendlyName);
+
+ return isGateway;
+}
+
+bool
+nsNotifyAddrListener::CheckICSStatus(PWCHAR aAdapterName)
+{
+ InitNetshellLibrary();
+
+ // This method enumerates all privately shared connections and checks if some
+ // of them has the same name as the one provided in aAdapterName. If such
+ // connection is found in the collection the adapter is used as ICS gateway
+ bool isICSGatewayAdapter = false;
+
+ HRESULT hr;
+ RefPtr<INetSharingManager> netSharingManager;
+ hr = CoCreateInstance(
+ CLSID_NetSharingManager,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_INetSharingManager,
+ getter_AddRefs(netSharingManager));
+
+ RefPtr<INetSharingPrivateConnectionCollection> privateCollection;
+ if (SUCCEEDED(hr)) {
+ hr = netSharingManager->get_EnumPrivateConnections(
+ ICSSC_DEFAULT,
+ getter_AddRefs(privateCollection));
+ }
+
+ RefPtr<IEnumNetSharingPrivateConnection> privateEnum;
+ if (SUCCEEDED(hr)) {
+ RefPtr<IUnknown> privateEnumUnknown;
+ hr = privateCollection->get__NewEnum(getter_AddRefs(privateEnumUnknown));
+ if (SUCCEEDED(hr)) {
+ hr = privateEnumUnknown->QueryInterface(
+ IID_IEnumNetSharingPrivateConnection,
+ getter_AddRefs(privateEnum));
+ }
+ }
+
+ if (SUCCEEDED(hr)) {
+ ULONG fetched;
+ VARIANT connectionVariant;
+ while (!isICSGatewayAdapter &&
+ SUCCEEDED(hr = privateEnum->Next(1, &connectionVariant,
+ &fetched)) &&
+ fetched) {
+ if (connectionVariant.vt != VT_UNKNOWN) {
+ // We should call VariantClear here but it needs to link
+ // with oleaut32.lib that produces a Ts incrase about 10ms
+ // that is undesired. As it is quit unlikely the result would
+ // be of a different type anyway, let's pass the variant
+ // unfreed here.
+ NS_ERROR("Variant of unexpected type, expecting VT_UNKNOWN, we probably leak it!");
+ continue;
+ }
+
+ RefPtr<INetConnection> connection;
+ if (SUCCEEDED(connectionVariant.punkVal->QueryInterface(
+ IID_INetConnection,
+ getter_AddRefs(connection)))) {
+ connectionVariant.punkVal->Release();
+
+ NETCON_PROPERTIES *properties;
+ if (SUCCEEDED(connection->GetProperties(&properties))) {
+ if (!wcscmp(properties->pszwName, aAdapterName))
+ isICSGatewayAdapter = true;
+
+ if (sNcFreeNetconProperties)
+ sNcFreeNetconProperties(properties);
+ }
+ }
+ }
+ }
+
+ return isICSGatewayAdapter;
+}
+
+DWORD
+nsNotifyAddrListener::CheckAdaptersAddresses(void)
+{
+ 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 sum = 0;
+
+ if (ret == ERROR_SUCCESS) {
+ bool linkUp = false;
+
+ for (PIP_ADAPTER_ADDRESSES adapter = adapterList; adapter;
+ adapter = adapter->Next) {
+ if (adapter->OperStatus != IfOperStatusUp ||
+ !adapter->FirstUnicastAddress ||
+ adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK ||
+ CheckICSGateway(adapter) ) {
+ continue;
+ }
+
+ // Add chars from AdapterName to the checksum.
+ for (int i = 0; adapter->AdapterName[i]; ++i) {
+ sum <<= 2;
+ 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];
+ }
+ }
+ linkUp = true;
+ }
+ mLinkUp = linkUp;
+ mStatusKnown = true;
+ }
+ free(adapterList);
+
+ if (mLinkUp) {
+ /* Store the checksum only if one or more interfaces are up */
+ mIPInterfaceChecksum = sum;
+ }
+
+ CoUninitialize();
+
+ 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) {
+ SendEvent(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 (mAllowChangedEvent && (since.ToMilliseconds() > 2000)) {
+ NetworkChanged();
+ }
+ }
+ if (prevLinkUp != mLinkUp) {
+ // UP/DOWN status changed, send appropriate UP/DOWN event
+ SendEvent(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..47a9ed6451
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et sw=4 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 "nsCOMPtr.h"
+#include "mozilla/TimeStamp.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);
+
+protected:
+ class ChangeEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ ChangeEvent(nsINetworkLinkService *aService, const char *aEventID)
+ : mService(aService), mEventID(aEventID) {
+ }
+ private:
+ nsCOMPtr<nsINetworkLinkService> mService;
+ const char *mEventID;
+ };
+
+ bool mLinkUp;
+ bool mStatusKnown;
+ bool mCheckAttempted;
+
+ nsresult Shutdown(void);
+ nsresult SendEvent(const char *aEventID);
+
+ DWORD CheckAdaptersAddresses(void);
+
+ // Checks for an Internet Connection Sharing (ICS) gateway.
+ bool CheckICSGateway(PIP_ADAPTER_ADDRESSES aAdapter);
+ bool CheckICSStatus(PWCHAR aAdapterName);
+
+ nsCOMPtr<nsIThread> 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);
+ nsCString mNetworkId;
+
+ HANDLE mCheckEvent;
+
+ // set true when mCheckEvent means shutdown
+ bool mShutdown;
+
+ // 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;
+
+ // Network changed events are enabled
+ bool mAllowChangedEvent;
+
+ // Check for IPv6 network changes
+ bool mIPv6Changes;
+
+ // 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/NetwerkTestLogging.h b/netwerk/test/NetwerkTestLogging.h
new file mode 100644
index 0000000000..68e955fd39
--- /dev/null
+++ b/netwerk/test/NetwerkTestLogging.h
@@ -0,0 +1,23 @@
+/* -*- 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 NetwerkTestLogging_h
+#define NetwerkTestLogging_h
+
+#include "mozilla/Logging.h"
+
+// The netwerk standalone cpp unit tests will just use PR_LogPrint as they don't
+// have access to mozilla::detail::log_print. To support that MOZ_LOG is
+// redefined.
+#undef MOZ_LOG
+#define MOZ_LOG(_module,_level,_args) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(_module,_level)) { \
+ PR_LogPrint _args; \
+ } \
+ PR_END_MACRO
+
+#endif
diff --git a/netwerk/test/PropertiesTest.cpp b/netwerk/test/PropertiesTest.cpp
new file mode 100644
index 0000000000..5099d7b5b9
--- /dev/null
+++ b/netwerk/test/PropertiesTest.cpp
@@ -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/. */
+
+#include "TestCommon.h"
+#include "mozilla/Sprintf.h"
+#include "nsXPCOM.h"
+#include "nsStringAPI.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsIChannel.h"
+#include "nsIComponentManager.h"
+#include <stdio.h>
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsILoadInfo.h"
+#include "nsNetUtil.h"
+
+#define TEST_URL "resource:/res/test.properties"
+static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
+
+/***************************************************************************/
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult ret;
+
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ nsIInputStream* in = nullptr;
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &ret);
+ if (NS_FAILED(ret)) return 1;
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ ret = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ if (NS_FAILED(ret)) return 1;
+
+ nsCOMPtr<nsIURI> uri;
+ ret = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING(TEST_URL));
+ if (NS_FAILED(ret)) return 1;
+
+ nsIChannel *channel = nullptr;
+ ret = NS_NewChannel(&channel,
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(ret)) return 1;
+
+ ret = channel->Open2(&in);
+ if (NS_FAILED(ret)) return 1;
+
+ nsIPersistentProperties* props;
+ ret = CallCreateInstance(kPersistentPropertiesCID, &props);
+ if (NS_FAILED(ret) || (!props)) {
+ printf("create nsIPersistentProperties failed\n");
+ return 1;
+ }
+ ret = props->Load(in);
+ if (NS_FAILED(ret)) {
+ printf("cannot load properties\n");
+ return 1;
+ }
+ int i = 1;
+ while (true) {
+ char name[16];
+ name[0] = 0;
+ SprintfLiteral(name, "%d", i);
+ nsAutoString v;
+ ret = props->GetStringProperty(nsDependentCString(name), v);
+ if (NS_FAILED(ret) || (!v.Length())) {
+ break;
+ }
+ printf("\"%d\"=\"%s\"\n", i, NS_ConvertUTF16toUTF8(v).get());
+ i++;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> propEnum;
+ ret = props->Enumerate(getter_AddRefs(propEnum));
+
+ if (NS_FAILED(ret)) {
+ printf("cannot enumerate properties\n");
+ return 1;
+ }
+
+
+ printf("\nKey\tValue\n");
+ printf( "---\t-----\n");
+
+ bool hasMore;
+ while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsISupports> sup;
+ ret = propEnum->GetNext(getter_AddRefs(sup));
+
+ nsCOMPtr<nsIPropertyElement> propElem = do_QueryInterface(sup, &ret);
+ if (NS_FAILED(ret)) {
+ printf("failed to get current item\n");
+ return 1;
+ }
+
+ nsAutoCString key;
+ nsAutoString value;
+
+ ret = propElem->GetKey(key);
+ if (NS_FAILED(ret)) {
+ printf("failed to get current element's key\n");
+ return 1;
+ }
+ ret = propElem->GetValue(value);
+ if (NS_FAILED(ret)) {
+ printf("failed to get current element's value\n");
+ return 1;
+ }
+
+ printf("%s\t%s\n", key.get(), NS_ConvertUTF16toUTF8(value).get());
+ }
+ return 0;
+}
diff --git a/netwerk/test/ReadNTLM.cpp b/netwerk/test/ReadNTLM.cpp
new file mode 100644
index 0000000000..20411e3369
--- /dev/null
+++ b/netwerk/test/ReadNTLM.cpp
@@ -0,0 +1,325 @@
+/* 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 <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "plbase64.h"
+#include "nsStringAPI.h"
+#include "prmem.h"
+
+/*
+ * ReadNTLM : reads NTLM messages.
+ *
+ * based on http://davenport.sourceforge.net/ntlm.html
+ */
+
+#define kNegotiateUnicode 0x00000001
+#define kNegotiateOEM 0x00000002
+#define kRequestTarget 0x00000004
+#define kUnknown1 0x00000008
+#define kNegotiateSign 0x00000010
+#define kNegotiateSeal 0x00000020
+#define kNegotiateDatagramStyle 0x00000040
+#define kNegotiateLanManagerKey 0x00000080
+#define kNegotiateNetware 0x00000100
+#define kNegotiateNTLMKey 0x00000200
+#define kUnknown2 0x00000400
+#define kUnknown3 0x00000800
+#define kNegotiateDomainSupplied 0x00001000
+#define kNegotiateWorkstationSupplied 0x00002000
+#define kNegotiateLocalCall 0x00004000
+#define kNegotiateAlwaysSign 0x00008000
+#define kTargetTypeDomain 0x00010000
+#define kTargetTypeServer 0x00020000
+#define kTargetTypeShare 0x00040000
+#define kNegotiateNTLM2Key 0x00080000
+#define kRequestInitResponse 0x00100000
+#define kRequestAcceptResponse 0x00200000
+#define kRequestNonNTSessionKey 0x00400000
+#define kNegotiateTargetInfo 0x00800000
+#define kUnknown4 0x01000000
+#define kUnknown5 0x02000000
+#define kUnknown6 0x04000000
+#define kUnknown7 0x08000000
+#define kUnknown8 0x10000000
+#define kNegotiate128 0x20000000
+#define kNegotiateKeyExchange 0x40000000
+#define kNegotiate56 0x80000000
+
+static const char NTLM_SIGNATURE[] = "NTLMSSP";
+static const char NTLM_TYPE1_MARKER[] = { 0x01, 0x00, 0x00, 0x00 };
+static const char NTLM_TYPE2_MARKER[] = { 0x02, 0x00, 0x00, 0x00 };
+static const char NTLM_TYPE3_MARKER[] = { 0x03, 0x00, 0x00, 0x00 };
+
+#define NTLM_MARKER_LEN 4
+#define NTLM_TYPE1_HEADER_LEN 32
+#define NTLM_TYPE2_HEADER_LEN 32
+#define NTLM_TYPE3_HEADER_LEN 64
+
+#define LM_HASH_LEN 16
+#define LM_RESP_LEN 24
+
+#define NTLM_HASH_LEN 16
+#define NTLM_RESP_LEN 24
+
+static void PrintFlags(uint32_t flags)
+{
+#define TEST(_flag) \
+ if (flags & k ## _flag) \
+ printf(" 0x%08x (" # _flag ")\n", k ## _flag)
+
+ TEST(NegotiateUnicode);
+ TEST(NegotiateOEM);
+ TEST(RequestTarget);
+ TEST(Unknown1);
+ TEST(NegotiateSign);
+ TEST(NegotiateSeal);
+ TEST(NegotiateDatagramStyle);
+ TEST(NegotiateLanManagerKey);
+ TEST(NegotiateNetware);
+ TEST(NegotiateNTLMKey);
+ TEST(Unknown2);
+ TEST(Unknown3);
+ TEST(NegotiateDomainSupplied);
+ TEST(NegotiateWorkstationSupplied);
+ TEST(NegotiateLocalCall);
+ TEST(NegotiateAlwaysSign);
+ TEST(TargetTypeDomain);
+ TEST(TargetTypeServer);
+ TEST(TargetTypeShare);
+ TEST(NegotiateNTLM2Key);
+ TEST(RequestInitResponse);
+ TEST(RequestAcceptResponse);
+ TEST(RequestNonNTSessionKey);
+ TEST(NegotiateTargetInfo);
+ TEST(Unknown4);
+ TEST(Unknown5);
+ TEST(Unknown6);
+ TEST(Unknown7);
+ TEST(Unknown8);
+ TEST(Negotiate128);
+ TEST(NegotiateKeyExchange);
+ TEST(Negotiate56);
+
+#undef TEST
+}
+
+static void
+PrintBuf(const char *tag, const uint8_t *buf, uint32_t bufLen)
+{
+ int i;
+
+ printf("%s =\n", tag);
+ while (bufLen > 0)
+ {
+ int count = bufLen;
+ if (count > 8)
+ count = 8;
+
+ printf(" ");
+ for (i=0; i<count; ++i)
+ {
+ printf("0x%02x ", int(buf[i]));
+ }
+ for (; i<8; ++i)
+ {
+ printf(" ");
+ }
+
+ printf(" ");
+ for (i=0; i<count; ++i)
+ {
+ if (isprint(buf[i]))
+ printf("%c", buf[i]);
+ else
+ printf(".");
+ }
+ printf("\n");
+
+ bufLen -= count;
+ buf += count;
+ }
+}
+
+static uint16_t
+ReadUint16(const uint8_t *&buf)
+{
+ uint16_t x;
+#ifdef IS_BIG_ENDIAN
+ x = ((uint16_t) buf[1]) | ((uint16_t) buf[0] << 8);
+#else
+ x = ((uint16_t) buf[0]) | ((uint16_t) buf[1] << 8);
+#endif
+ buf += sizeof(x);
+ return x;
+}
+
+static uint32_t
+ReadUint32(const uint8_t *&buf)
+{
+ uint32_t x;
+#ifdef IS_BIG_ENDIAN
+ x = ( (uint32_t) buf[3]) |
+ (((uint32_t) buf[2]) << 8) |
+ (((uint32_t) buf[1]) << 16) |
+ (((uint32_t) buf[0]) << 24);
+#else
+ x = ( (uint32_t) buf[0]) |
+ (((uint32_t) buf[1]) << 8) |
+ (((uint32_t) buf[2]) << 16) |
+ (((uint32_t) buf[3]) << 24);
+#endif
+ buf += sizeof(x);
+ return x;
+}
+
+typedef struct {
+ uint16_t length;
+ uint16_t capacity;
+ uint32_t offset;
+} SecBuf;
+
+static void
+ReadSecBuf(SecBuf *s, const uint8_t *&buf)
+{
+ s->length = ReadUint16(buf);
+ s->capacity = ReadUint16(buf);
+ s->offset = ReadUint32(buf);
+}
+
+static void
+ReadType1MsgBody(const uint8_t *inBuf, uint32_t start)
+{
+ const uint8_t *cursor = inBuf + start;
+ uint32_t flags;
+
+ PrintBuf("flags", cursor, 4);
+ // read flags
+ flags = ReadUint32(cursor);
+ PrintFlags(flags);
+
+ // type 1 message may not include trailing security buffers
+ if ((flags & kNegotiateDomainSupplied) |
+ (flags & kNegotiateWorkstationSupplied))
+ {
+ SecBuf secbuf;
+ ReadSecBuf(&secbuf, cursor);
+ PrintBuf("supplied domain", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor);
+ PrintBuf("supplied workstation", inBuf + secbuf.offset, secbuf.length);
+ }
+}
+
+static void
+ReadType2MsgBody(const uint8_t *inBuf, uint32_t start)
+{
+ uint16_t targetLen, offset;
+ uint32_t flags;
+ const uint8_t *target;
+ const uint8_t *cursor = inBuf + start;
+
+ // read target name security buffer
+ targetLen = ReadUint16(cursor);
+ ReadUint16(cursor); // discard next 16-bit value
+ offset = ReadUint32(cursor); // get offset from inBuf
+ target = inBuf + offset;
+
+ PrintBuf("target", target, targetLen);
+
+ PrintBuf("flags", cursor, 4);
+ // read flags
+ flags = ReadUint32(cursor);
+ PrintFlags(flags);
+
+ // read challenge
+ PrintBuf("challenge", cursor, 8);
+ cursor += 8;
+
+ PrintBuf("context", cursor, 8);
+ cursor += 8;
+
+ SecBuf secbuf;
+ ReadSecBuf(&secbuf, cursor);
+ PrintBuf("target information", inBuf + secbuf.offset, secbuf.length);
+}
+
+static void
+ReadType3MsgBody(const uint8_t *inBuf, uint32_t start)
+{
+ const uint8_t *cursor = inBuf + start;
+
+ SecBuf secbuf;
+
+ ReadSecBuf(&secbuf, cursor); // LM response
+ PrintBuf("LM response", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // NTLM response
+ PrintBuf("NTLM response", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // domain name
+ PrintBuf("domain name", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // user name
+ PrintBuf("user name", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // workstation name
+ PrintBuf("workstation name", inBuf + secbuf.offset, secbuf.length);
+
+ ReadSecBuf(&secbuf, cursor); // session key
+ PrintBuf("session key", inBuf + secbuf.offset, secbuf.length);
+
+ uint32_t flags = ReadUint32(cursor);
+ PrintBuf("flags", (const uint8_t *) &flags, sizeof(flags));
+ PrintFlags(flags);
+}
+
+static void
+ReadMsg(const char *base64buf, uint32_t bufLen)
+{
+ uint8_t *inBuf = (uint8_t *) PL_Base64Decode(base64buf, bufLen, nullptr);
+ if (!inBuf)
+ {
+ printf("PL_Base64Decode failed\n");
+ return;
+ }
+
+ const uint8_t *cursor = inBuf;
+
+ PrintBuf("signature", cursor, 8);
+
+ // verify NTLMSSP signature
+ if (memcmp(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)) != 0)
+ {
+ printf("### invalid or corrupt NTLM signature\n");
+ }
+ cursor += sizeof(NTLM_SIGNATURE);
+
+ PrintBuf("message type", cursor, 4);
+
+ if (memcmp(cursor, NTLM_TYPE1_MARKER, sizeof(NTLM_MARKER_LEN)) == 0)
+ ReadType1MsgBody(inBuf, 12);
+ else if (memcmp(cursor, NTLM_TYPE2_MARKER, sizeof(NTLM_MARKER_LEN)) == 0)
+ ReadType2MsgBody(inBuf, 12);
+ else if (memcmp(cursor, NTLM_TYPE3_MARKER, sizeof(NTLM_MARKER_LEN)) == 0)
+ ReadType3MsgBody(inBuf, 12);
+ else
+ printf("### invalid or unknown message type\n");
+
+ PR_Free(inBuf);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc == 1)
+ {
+ printf("usage: ntlmread <msg>\n");
+ return -1;
+ }
+ ReadMsg(argv[1], (uint32_t) strlen(argv[1]));
+ return 0;
+}
diff --git a/netwerk/test/TestBind.cpp b/netwerk/test/TestBind.cpp
new file mode 100644
index 0000000000..3ce7438b37
--- /dev/null
+++ b/netwerk/test/TestBind.cpp
@@ -0,0 +1,210 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "TestHarness.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIServerSocket.h"
+#include "nsIAsyncInputStream.h"
+#include "nsINetAddr.h"
+#include "mozilla/net/DNS.h"
+#include "prerror.h"
+
+using namespace mozilla::net;
+using namespace mozilla;
+
+class ServerListener: public nsIServerSocketListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ ServerListener();
+
+ // Port that is got from server side will be store here.
+ uint32_t mClientPort;
+ bool mFailed;
+private:
+ virtual ~ServerListener();
+};
+
+NS_IMPL_ISUPPORTS(ServerListener, nsIServerSocketListener)
+
+ServerListener::ServerListener()
+ : mClientPort(-1)
+ , mFailed(false)
+{
+}
+
+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;
+ fail("Server: not able to get peer address.");
+ QuitPumpingEvents();
+ return NS_OK;
+ }
+ mClientPort = PR_ntohs(peerAddr.inet.port);
+ passed("Server: received connection");
+ QuitPumpingEvents();
+ 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
+
+ ClientInputCallback();
+
+ bool mFailed;
+private:
+ virtual ~ClientInputCallback();
+};
+
+NS_IMPL_ISUPPORTS(ClientInputCallback, nsIInputStreamCallback)
+
+ClientInputCallback::ClientInputCallback()
+ : mFailed(false)
+{
+}
+
+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;
+ }
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+int
+main(int32_t argc, char *argv[])
+{
+ ScopedXPCOM xpcom("SocketTransport");
+ if (xpcom.failed()) {
+ fail("Unable to initalize XPCOM.");
+ return -1;
+ }
+
+ //
+ // Server side.
+ //
+ nsCOMPtr<nsIServerSocket> server = do_CreateInstance("@mozilla.org/network/server-socket;1");
+ if (!server) {
+ fail("Failed to create server socket.");
+ return -1;
+ }
+
+ nsresult rv = server->Init(-1, true, -1);
+ if (NS_FAILED(rv)) {
+ fail("Failed to initialize server.");
+ return -1;
+ }
+
+ int32_t serverPort;
+ rv = server->GetPort(&serverPort);
+ if (NS_FAILED(rv)) {
+ fail("Unable to get server port.");
+ return -1;
+ }
+
+ // Listening.
+ RefPtr<ServerListener> serverListener = new ServerListener();
+ rv = server->AsyncListen(serverListener);
+ if (NS_FAILED(rv)) {
+ fail("Server fail to start listening.");
+ return -1;
+ }
+
+ //
+ // Client side
+ //
+ uint32_t bindingPort = 20000;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ fail("Unable to get socket transport service.");
+ return -1;
+ }
+
+ for (int32_t tried = 0; tried < 100; tried++) {
+ nsCOMPtr<nsISocketTransport> client;
+ rv = sts->CreateTransport(nullptr, 0, NS_LITERAL_CSTRING("127.0.0.1"),
+ serverPort, nullptr, getter_AddRefs(client));
+ if (NS_FAILED(rv)) {
+ fail("Unable to create transport.");
+ return -1;
+ }
+
+ // 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);
+ if (NS_FAILED(rv)) {
+ fail("Unable to bind a port.");
+ return -1;
+ }
+
+ // Open IO streams, to make client SocketTransport connect to server.
+ RefPtr<ClientInputCallback> clientCallback = new ClientInputCallback();
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = client->OpenInputStream(nsITransport::OPEN_UNBUFFERED,
+ 0, 0, getter_AddRefs(inputStream));
+ if (NS_FAILED(rv)) {
+ fail("Failed to open an input stream.");
+ return -1;
+ }
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream = do_QueryInterface(inputStream);
+ rv = asyncInputStream->AsyncWait(clientCallback, 0, 0, nullptr);
+
+ // Wait for server's response or callback of input stream.
+ PumpEvents();
+ 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;
+ }
+ }
+
+ if (serverListener->mFailed) {
+ fail("Server failure.");
+ return -1;
+ }
+ if (serverListener->mClientPort != bindingPort) {
+ fail("Port that server got doesn't match what we are expecting.");
+ return -1;
+ }
+ passed("Port matched");
+ return 0;
+}
diff --git a/netwerk/test/TestBlockingSocket.cpp b/netwerk/test/TestBlockingSocket.cpp
new file mode 100644
index 0000000000..d4b4a615fb
--- /dev/null
+++ b/netwerk/test/TestBlockingSocket.cpp
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIComponentRegistrar.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsStringAPI.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIOutputStream.h"
+#include "NetwerkTestLogging.h"
+#include "prenv.h"
+#include "prthread.h"
+#include <stdlib.h>
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+static nsresult
+RunBlockingTest(const nsACString &host, int32_t port, nsIFile *file)
+{
+ nsresult rv;
+
+ LOG(("RunBlockingTest\n"));
+
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> input;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(input), file);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISocketTransport> trans;
+ rv = sts->CreateTransport(nullptr, 0, host, port, nullptr, getter_AddRefs(trans));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> output;
+ rv = trans->OpenOutputStream(nsITransport::OPEN_BLOCKING, 100, 10, getter_AddRefs(output));
+ if (NS_FAILED(rv)) return rv;
+
+ char buf[120];
+ uint32_t nr, nw;
+ for (;;) {
+ rv = input->Read(buf, sizeof(buf), &nr);
+ if (NS_FAILED(rv) || (nr == 0)) return rv;
+
+/*
+ const char *p = buf;
+ while (nr) {
+ rv = output->Write(p, nr, &nw);
+ if (NS_FAILED(rv)) return rv;
+
+ nr -= nw;
+ p += nw;
+ }
+*/
+
+ rv = output->Write(buf, nr, &nw);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(nr == nw, "not all written");
+ }
+
+ LOG((" done copying data.\n"));
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 4) {
+ printf("usage: %s <host> <port> <file-to-read>\n", argv[0]);
+ return -1;
+ }
+ char* hostName = argv[1];
+ int32_t port = atoi(argv[2]);
+ char* fileName = argv[3];
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ gTestLog = PR_NewLogModule("Test");
+
+ nsCOMPtr<nsIFile> file;
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileName), false, getter_AddRefs(file));
+ if (NS_FAILED(rv)) return -1;
+
+ rv = RunBlockingTest(nsDependentCString(hostName), port, file);
+ if (NS_FAILED(rv))
+ LOG(("RunBlockingTest failed [rv=%x]\n", rv));
+
+ // give background threads a chance to finish whatever work they may
+ // be doing.
+ LOG(("sleeping for 5 seconds...\n"));
+ PR_Sleep(PR_SecondsToInterval(5));
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return 0;
+}
diff --git a/netwerk/test/TestCacheBlockFiles.cpp b/netwerk/test/TestCacheBlockFiles.cpp
new file mode 100644
index 0000000000..0a309fdf9c
--- /dev/null
+++ b/netwerk/test/TestCacheBlockFiles.cpp
@@ -0,0 +1,873 @@
+/*
+ TestCacheBlockFiles.cpp
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <utime.h>
+
+#include <Files.h>
+#include <Strings.h>
+#include <Errors.h>
+#include <Resources.h>
+#include <Aliases.h>
+
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsError.h"
+#include "nsIComponentManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsMemory.h"
+#include "nsIComponentRegistrar.h"
+#include "nsANSIFileStreams.h"
+#include "nsDiskCacheBlockFile.h"
+
+#include "prclist.h"
+
+/**
+ * StressTest()
+ */
+
+typedef struct Allocation {
+ int32_t start;
+ int32_t count;
+} Allocation;
+
+nsresult
+StressTest(nsIFile * localFile, int32_t testNumber, bool readWrite)
+{
+ nsresult rv = NS_OK;
+
+#define ITERATIONS 1024
+#define MAX_ALLOCATIONS 256
+ Allocation block[MAX_ALLOCATIONS];
+ int32_t currentAllocations = 0;
+ int32_t i;
+ uint32_t a;
+
+ char * writeBuf[4];
+ char readBuf[256 * 4];
+
+
+ if (readWrite) {
+ for (i = 0; i < 4; i++) {
+ writeBuf[i] = new char[256 * i];
+ if (!writeBuf[i]) {
+ printf("Test %d: failed - out of memory\n", testNumber);
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto exit;
+ }
+
+ memset(writeBuf[i], i, 256 * i);
+ }
+ }
+
+ nsDiskCacheBlockFile * blockFile = new nsDiskCacheBlockFile;
+ if (!blockFile) {
+ printf("Test %d failed (unable to allocate nsDiskCacheBlockFile", testNumber);
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto exit;
+ }
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (Open returned: 0x%.8x)\n", testNumber, rv);
+ goto exit;
+ }
+
+ i = ITERATIONS;
+ while (i > 0) {
+ if ((currentAllocations >= MAX_ALLOCATIONS) ||
+ ((currentAllocations > 0) && (rand() % 4 == 0))) {
+ // deallocate if we've reached the limit, or 25% of the time we have allocations
+ a = rand() % currentAllocations;
+
+ if (readWrite) {
+ // read verify deallocation
+ rv = blockFile->ReadBlocks(readBuf, block[a].start, block[a].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (ReadBlocks() returned 0x%.8x)\n", testNumber, rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * block[a].count; i++) {
+ if (readBuf[i] != block[a].count) {
+ printf("Test %d: failed (verifying buffer 1)\n", testNumber);
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+ }
+
+ rv = blockFile->DeallocateBlocks(block[a].start, block[a].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (DeallocateBlocks() returned %d)\n", testNumber, rv);
+ goto exit;
+ }
+
+ --currentAllocations;
+ if (currentAllocations > 0)
+ block[a] = block[currentAllocations];
+
+ } else {
+ // allocate blocks
+ --i;
+ a = currentAllocations++;
+ block[a].count = rand() % 4 + 1; // allocate 1 to 4 blocks
+ block[a].start = blockFile->AllocateBlocks(block[a].count);
+ if (block[a].start < 0) {
+ printf("Test %d: failed (AllocateBlocks() failed.)\n", testNumber);
+ goto exit;
+ }
+
+ if (readWrite) {
+ // write buffer
+ rv = blockFile->WriteBlocks(writeBuf[block[a].count], block[a].start, block[a].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (WriteBlocks() returned 0x%.8x)\n",testNumber, rv);
+ goto exit;
+ }
+ }
+ }
+ }
+
+ // now deallocate remaining allocations
+ i = currentAllocations;
+ while (i--) {
+
+ if (readWrite) {
+ // read verify deallocation
+ rv = blockFile->ReadBlocks(readBuf, block[a].start, block[a].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (ReadBlocks(1) returned 0x%.8x)\n", testNumber, rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * block[a].count; i++) {
+ if (readBuf[i] != block[a].count) {
+ printf("Test %d: failed (verifying buffer 1)\n", testNumber);
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+ }
+
+ rv = blockFile->DeallocateBlocks(block[i].start, block[i].count);
+ if (NS_FAILED(rv)) {
+ printf("Test %d: failed (DeallocateBlocks() returned %d)\n", testNumber, rv);
+ goto exit;
+ }
+ }
+
+
+
+exit:
+ nsresult rv2 = blockFile->Close();
+ if (NS_FAILED(rv2)) {
+ printf("Test %d: failed (Close returned: 0x%.8x)\n", testNumber, rv2);
+ }
+
+ return rv ? rv : rv2;
+}
+
+/**
+ * main()
+ */
+
+int
+main(void)
+{
+// OSErr err;
+ printf("hello world\n");
+
+ unsigned long now = time(0);
+ srand(now);
+
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_OK;
+ {
+ // Start up XPCOM
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ // Get default directory
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ printf("NS_GetSpecialDirectory() failed : 0x%.8x\n", rv);
+ goto exit;
+ }
+ char * currentDirPath;
+ rv = file->GetPath(&currentDirPath);
+ if (NS_FAILED(rv)) {
+ printf("currentProcessDir->GetPath() failed : 0x%.8x\n", rv);
+ goto exit;
+ }
+
+ printf("Current Process Directory: %s\n", currentDirPath);
+
+
+ // Generate name for cache block file
+ rv = file->Append("_CACHE_001_");
+ if (NS_FAILED(rv)) goto exit;
+
+ // Delete existing file
+ rv = file->Delete(false);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) goto exit;
+
+ // Need nsIFile to open
+ localFile = do_QueryInterface(file, &rv);
+ if (NS_FAILED(rv)) {
+ printf("do_QueryInterface(file) failed : 0x%.8x\n", rv);
+ goto exit;
+ }
+
+ nsDiskCacheBlockFile * blockFile = new nsDiskCacheBlockFile;
+ if (!blockFile) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto exit;
+ }
+
+ //----------------------------------------------------------------
+ // local variables used in tests
+ //----------------------------------------------------------------
+ uint32_t bytesWritten = 0;
+ int32_t startBlock;
+ int32_t i = 0;
+
+
+ //----------------------------------------------------------------
+ // Test 1: Open nonexistent file
+ //----------------------------------------------------------------
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 1: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 1: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 1: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 2: Open existing file (with no allocation)
+ //----------------------------------------------------------------
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 2: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 2: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 2: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 3: Open existing file (bad format) size < kBitMapBytes
+ //----------------------------------------------------------------
+
+ // Delete existing file
+ rv = localFile->Delete(false);
+ if (NS_FAILED(rv)) {
+ printf("Test 3 failed (Delete returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // write < kBitMapBytes to file
+ nsANSIFileStream * stream = new nsANSIFileStream;
+ if (!stream) {
+ printf("Test 3 failed (unable to allocate stream\n", rv);
+ goto exit;
+ }
+ NS_ADDREF(stream);
+ rv = stream->Open(localFile);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(stream);
+ printf("Test 3 failed (stream->Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ bytesWritten = 0;
+ rv = stream->Write("Tell me something good.\n", 24, &bytesWritten);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(stream);
+ printf("Test 3 failed (stream->Write returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = stream->Close();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(stream);
+ printf("Test 3 failed (stream->Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+ NS_RELEASE(stream);
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 3: failed (Open erroneously succeeded)\n", rv);
+
+ (void) blockFile->Close();
+ goto exit;
+ }
+
+ printf("Test 3: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 4: Open nonexistent file (again)
+ //----------------------------------------------------------------
+
+ // Delete existing file
+ rv = localFile->Delete(false);
+ if (NS_FAILED(rv)) {
+ printf("Test 4 failed (Delete returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 4: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 4: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 5: AllocateBlocks: invalid block count (0, 5)
+ //----------------------------------------------------------------
+
+
+ startBlock = blockFile->AllocateBlocks(0);
+ if (startBlock > -1) {
+ printf("Test 5: failed (AllocateBlocks(0) erroneously succeeded)\n");
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(5);
+ if (startBlock > -1) {
+ printf("Test 5: failed (AllocateBlocks(5) erroneously succeeded)\n");
+ goto exit;
+ }
+ printf("Test 5: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 6: AllocateBlocks: valid block count (1, 2, 3, 4)
+ //----------------------------------------------------------------
+ startBlock = blockFile->AllocateBlocks(1);
+ if (startBlock != 0) {
+ printf("Test 6: failed (AllocateBlocks(1) failed)\n");
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(2);
+ if (startBlock != 1) {
+ printf("Test 6: failed (AllocateBlocks(2) failed)\n");
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(3);
+ if (startBlock != 4) {
+ printf("Test 6: failed (AllocateBlocks(3) failed)\n");
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(4);
+ if (startBlock != 8) {
+ printf("Test 6: failed (AllocateBlocks(4) failed)\n");
+ goto exit;
+ }
+
+ // blocks allocated should be 1220 3330 4444
+ printf("Test 6: passed\n"); // but bits could be mis-allocated
+
+
+
+ //----------------------------------------------------------------
+ // Test 7: VerifyAllocation
+ //----------------------------------------------------------------
+ rv = blockFile->VerifyAllocation(0,1);
+ if (NS_FAILED(rv)) {
+ printf("Test 7: failed (VerifyAllocation(0,1) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->VerifyAllocation(1,2);
+ if (NS_FAILED(rv)) {
+ printf("Test 7: failed (VerifyAllocation(1,2) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->VerifyAllocation(4,3);
+ if (NS_FAILED(rv)) {
+ printf("Test 7: failed (VerifyAllocation(4,3) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->VerifyAllocation(8,4);
+ if (NS_FAILED(rv)) {
+ printf("Test 7: failed (VerifyAllocation(8,4) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+ printf("Test 7: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 8: LastBlock
+ //----------------------------------------------------------------
+ int32_t lastBlock = blockFile->LastBlock();
+ if (lastBlock != 11) {
+ printf("Test 8: failed (LastBlock() returned: %d)\n", lastBlock);
+ goto exit;
+ }
+ printf("Test 8: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 9: DeallocateBlocks: bad startBlock ( < 0)
+ //----------------------------------------------------------------
+ rv = blockFile->DeallocateBlocks(-1, 4);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 9: failed (DeallocateBlocks(-1, 4) erroneously succeeded)\n");
+ goto exit;
+ }
+ printf("Test 9: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 10: DeallocateBlocks: bad numBlocks (0, 5)
+ //----------------------------------------------------------------
+ rv = blockFile->DeallocateBlocks(0, 0);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 10: failed (DeallocateBlocks(0, 0) erroneously succeeded)\n");
+ goto exit;
+ }
+
+ rv = blockFile->DeallocateBlocks(0, 5);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 10: failed (DeallocateBlocks(0, 5) erroneously succeeded)\n");
+ goto exit;
+ }
+
+ printf("Test 10: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 11: DeallocateBlocks: unallocated blocks
+ //----------------------------------------------------------------
+ rv = blockFile->DeallocateBlocks(12, 1);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 11: failed (DeallocateBlocks(12, 1) erroneously succeeded)\n");
+ goto exit;
+ }
+
+ printf("Test 11: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 12: DeallocateBlocks: 1, 2, 3, 4 (allocated in Test 6)
+ //----------------------------------------------------------------
+ rv = blockFile->DeallocateBlocks(0, 1);
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (DeallocateBlocks(12, 1) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->DeallocateBlocks(1, 2);
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (DeallocateBlocks(1, 2) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->DeallocateBlocks(4, 3);
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (DeallocateBlocks(4, 3) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->DeallocateBlocks(8, 4);
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (DeallocateBlocks(8, 4) returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // zero blocks should be allocated
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 12: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 12: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 13: Allocate/Deallocate boundary test
+ //----------------------------------------------------------------
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // fully allocate, 1 block at a time
+ for (i=0; i< kBitMapBytes * 8; ++i) {
+ startBlock = blockFile->AllocateBlocks(1);
+ if (startBlock < 0) {
+ printf("Test 13: failed (AllocateBlocks(1) failed on i=%d)\n", i);
+ goto exit;
+ }
+ }
+
+ // attempt allocation with full bit map
+ startBlock = blockFile->AllocateBlocks(1);
+ if (startBlock >= 0) {
+ printf("Test 13: failed (AllocateBlocks(1) erroneously succeeded i=%d)\n", i);
+ goto exit;
+ }
+
+ // deallocate all the bits
+ for (i=0; i< kBitMapBytes * 8; ++i) {
+ rv = blockFile->DeallocateBlocks(i,1);
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (DeallocateBlocks(%d,1) returned: 0x%.8x)\n", i,rv);
+ goto exit;
+ }
+ }
+
+ // attempt deallocation beyond end of bit map
+ rv = blockFile->DeallocateBlocks(i,1);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 13: failed (DeallocateBlocks(%d,1) erroneously succeeded)\n", i);
+ goto exit;
+ }
+
+ // bit map should be empty
+
+ // fully allocate, 2 block at a time
+ for (i=0; i< kBitMapBytes * 8; i+=2) {
+ startBlock = blockFile->AllocateBlocks(2);
+ if (startBlock < 0) {
+ printf("Test 13: failed (AllocateBlocks(2) failed on i=%d)\n", i);
+ goto exit;
+ }
+ }
+
+ // attempt allocation with full bit map
+ startBlock = blockFile->AllocateBlocks(2);
+ if (startBlock >= 0) {
+ printf("Test 13: failed (AllocateBlocks(2) erroneously succeeded i=%d)\n", i);
+ goto exit;
+ }
+
+ // deallocate all the bits
+ for (i=0; i< kBitMapBytes * 8; i+=2) {
+ rv = blockFile->DeallocateBlocks(i,2);
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (DeallocateBlocks(%d,2) returned: 0x%.8x)\n", i,rv);
+ goto exit;
+ }
+ }
+
+ // bit map should be empty
+
+ // fully allocate, 4 block at a time
+ for (i=0; i< kBitMapBytes * 8; i+=4) {
+ startBlock = blockFile->AllocateBlocks(4);
+ if (startBlock < 0) {
+ printf("Test 13: failed (AllocateBlocks(4) failed on i=%d)\n", i);
+ goto exit;
+ }
+ }
+
+ // attempt allocation with full bit map
+ startBlock = blockFile->AllocateBlocks(4);
+ if (startBlock >= 0) {
+ printf("Test 13: failed (AllocateBlocks(4) erroneously succeeded i=%d)\n", i);
+ goto exit;
+ }
+
+ // deallocate all the bits
+ for (i=0; i< kBitMapBytes * 8; i+=4) {
+ rv = blockFile->DeallocateBlocks(i,4);
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (DeallocateBlocks(%d,4) returned: 0x%.8x)\n", i,rv);
+ goto exit;
+ }
+ }
+
+ // bit map should be empty
+
+ // allocate as many triple-blocks as possible
+ for (i=0; i< kBitMapBytes * 8; i+=4) {
+ startBlock = blockFile->AllocateBlocks(3);
+ if (startBlock < 0) {
+ printf("Test 13: failed (AllocateBlocks(3) failed on i=%d)\n", i);
+ goto exit;
+ }
+ }
+
+ // attempt allocation with "full" bit map
+ startBlock = blockFile->AllocateBlocks(3);
+ if (startBlock >= 0) {
+ printf("Test 13: failed (AllocateBlocks(3) erroneously succeeded i=%d)\n", i);
+ goto exit;
+ }
+
+ // leave some blocks allocated
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 13: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 13: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 14: ValidateFile (open existing file w/size < allocated blocks
+ //----------------------------------------------------------------
+ rv = blockFile->Open(localFile, 256);
+ if (NS_SUCCEEDED(rv)) {
+ printf("Test 14: failed (Open erroneously succeeded)\n");
+ goto exit;
+ }
+
+ // Delete existing file
+ rv = localFile->Delete(false);
+ if (NS_FAILED(rv)) {
+ printf("Test 14 failed (Delete returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+ printf("Test 14: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 15: Allocate/Deallocate stress test
+ //----------------------------------------------------------------
+
+ rv = StressTest(localFile, 15, false);
+ if (NS_FAILED(rv))
+ goto exit;
+
+ printf("Test 15: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 16: WriteBlocks
+ //----------------------------------------------------------------
+
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ char * one = new char[256 * 1];
+ char * two = new char[256 * 2];
+ char * three = new char[256 * 3];
+ char * four = new char[256 * 4];
+ if (!one || !two || !three || !four) {
+ printf("Test 16: failed - out of memory\n");
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto exit;
+ }
+
+ memset(one, 1, 256);
+ memset(two, 2, 256 * 2);
+ memset(three, 3, 256 * 3);
+ memset(four, 4, 256 * 4);
+
+ startBlock = blockFile->AllocateBlocks(1);
+ if (startBlock != 0) {
+ printf("Test 16: failed (AllocateBlocks(1) failed)\n");
+ goto exit;
+ }
+
+ rv = blockFile->WriteBlocks(one, startBlock, 1);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (WriteBlocks(1) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(2);
+ if (startBlock != 1) { // starting with empy map, this allocation should begin at block 1
+ printf("Test 16: failed (AllocateBlocks(2) failed)\n");
+ goto exit;
+ }
+
+ rv = blockFile->WriteBlocks(two, startBlock, 2);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (WriteBlocks(2) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(3);
+ if (startBlock != 4) { // starting with empy map, this allocation should begin at block 4
+ printf("Test 16: failed (AllocateBlocks(3) failed)\n");
+ goto exit;
+ }
+
+ rv = blockFile->WriteBlocks(three, startBlock, 3);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (WriteBlocks(3) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ startBlock = blockFile->AllocateBlocks(4);
+ if (startBlock != 8) { // starting with empy map, this allocation should begin at block 8
+ printf("Test 16: failed (AllocateBlocks(4) failed)\n");
+ goto exit;
+ }
+
+ rv = blockFile->WriteBlocks(four, startBlock, 4);
+ if (NS_FAILED(rv)) {
+ printf("Test 16: failed (WriteBlocks(4) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 16: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 17: ReadBlocks
+ //----------------------------------------------------------------
+
+ rv = blockFile->ReadBlocks(one, 0, 1);
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (ReadBlocks(1) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256; i++) {
+ if (one[i] != 1) {
+ printf("Test 17: failed (verifying buffer 1)\n");
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+
+ rv = blockFile->ReadBlocks(two, 1, 2);
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (ReadBlocks(2) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * 2; i++) {
+ if (two[i] != 2) {
+ printf("Test 17: failed (verifying buffer 2)\n");
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+
+ rv = blockFile->ReadBlocks(three, 4, 3);
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (ReadBlocks(3) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * 3; i++) {
+ if (three[i] != 3) {
+ printf("Test 17: failed (verifying buffer 3)\n");
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+
+ rv = blockFile->ReadBlocks(four, 8, 4);
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (ReadBlocks(4) returned 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ // Verify buffer
+ for (i = 0; i < 256 * 4; i++) {
+ if (four[i] != 4) {
+ printf("Test 17: failed (verifying buffer 4)\n");
+ rv = NS_ERROR_FAILURE;
+ goto exit;
+ }
+ }
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 17: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 17: passed\n");
+
+
+ //----------------------------------------------------------------
+ // Test 18: ValidateFile (open existing file with blocks allocated)
+ //----------------------------------------------------------------
+ rv = blockFile->Open(localFile, 256);
+ if (NS_FAILED(rv)) {
+ printf("Test 18: failed (Open returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ rv = blockFile->Close();
+ if (NS_FAILED(rv)) {
+ printf("Test 18: failed (Close returned: 0x%.8x)\n", rv);
+ goto exit;
+ }
+
+ printf("Test 18: passed\n");
+
+ //----------------------------------------------------------------
+ // Test 19: WriteBlocks/ReadBlocks stress
+ //----------------------------------------------------------------
+
+ rv = StressTest(localFile, 19, false);
+ if (NS_FAILED(rv))
+ goto exit;
+
+ printf("Test 19: passed\n");
+
+
+exit:
+
+ if (currentDirPath)
+ free(currentDirPath);
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ if (NS_FAILED(rv))
+ printf("Test failed: 0x%.8x\n", rv);
+
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+
+ printf("XPCOM shut down.\n\n");
+ return 0;
+}
+
diff --git a/netwerk/test/TestCachePrefixKeyParser.cpp b/netwerk/test/TestCachePrefixKeyParser.cpp
new file mode 100644
index 0000000000..88e544accf
--- /dev/null
+++ b/netwerk/test/TestCachePrefixKeyParser.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "TestHarness.h"
+#include "nsILoadContextInfo.h"
+#include "../cache2/CacheFileUtils.h"
+
+int
+main(int32_t argc, char *argv[])
+{
+ nsCOMPtr<nsILoadContextInfo> info;
+ nsAutoCString key, enh;
+
+#define CHECK(a) MOZ_ASSERT(a)
+
+ info = ParseKey(NS_LITERAL_CSTRING(""));
+ CHECK(info && !info->IsPrivate() && !info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING(":"));
+ CHECK(info && !info->IsPrivate() && !info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING("a,"));
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING("a,:"));
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING("a,:xxx"), &enh, &key);
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ CHECK(NS_LITERAL_CSTRING("xxx").Equals(key));
+ info = ParseKey(NS_LITERAL_CSTRING("b,:xxx"));
+ CHECK(info && !info->IsPrivate() && !info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 0);
+ info = ParseKey(NS_LITERAL_CSTRING("a,b,:xxx"), &enh, &key);
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 0);
+ CHECK(NS_LITERAL_CSTRING("xxx").Equals(key));
+ CHECK(enh.IsEmpty());
+ info = ParseKey(NS_LITERAL_CSTRING("a,b,i123,:xxx"));
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 123);
+ info = ParseKey(NS_LITERAL_CSTRING("a,b,c,h***,i123,:xxx"), &enh, &key);
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 123);
+ CHECK(NS_LITERAL_CSTRING("xxx").Equals(key));
+ info = ParseKey(NS_LITERAL_CSTRING("a,b,c,h***,i123,~enh,:xxx"), &enh, &key);
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 123);
+ CHECK(NS_LITERAL_CSTRING("xxx").Equals(key));
+ CHECK(NS_LITERAL_CSTRING("enh").Equals(enh));
+ info = ParseKey(NS_LITERAL_CSTRING("0x,1,a,b,i123,:xxx"));
+ CHECK(info && !info->IsPrivate() && info->IsAnonymous() && info->IsInBrowserElement() && info->AppId() == 123);
+
+ nsAutoCString test;
+ AppendTagWithValue(test, '~', NS_LITERAL_CSTRING("e,nh,"));
+ info = ParseKey(test, &enh, &key);
+ CHECK(info && !info->IsPrivate() && !info->IsAnonymous() && !info->IsInBrowserElement() && info->AppId() == 0);
+ CHECK(NS_LITERAL_CSTRING("e,nh,").Equals(enh));
+
+ info = ParseKey(NS_LITERAL_CSTRING("a,i123,b,:xxx"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("a"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("a:"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("a:xxx"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i123"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i123:"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i123:xxx"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i123,x:"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i,x,:"));
+ CHECK(!info);
+ info = ParseKey(NS_LITERAL_CSTRING("i:"));
+ CHECK(!info);
+
+#undef CHECK
+
+ passed("ok");
+ return 0;
+}
diff --git a/netwerk/test/TestCommon.h b/netwerk/test/TestCommon.h
new file mode 100644
index 0000000000..72fb89afad
--- /dev/null
+++ b/netwerk/test/TestCommon.h
@@ -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/. */
+
+#ifndef TestCommon_h__
+#define TestCommon_h__
+
+#include <stdlib.h>
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+
+inline int test_common_init(int *argc, char ***argv)
+{
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+
+static bool gKeepPumpingEvents = false;
+
+class nsQuitPumpingEvent final : public nsIRunnable {
+ ~nsQuitPumpingEvent() {}
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_IMETHOD Run() override {
+ gKeepPumpingEvents = false;
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(nsQuitPumpingEvent, nsIRunnable)
+
+static inline void PumpEvents()
+{
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ gKeepPumpingEvents = true;
+ while (gKeepPumpingEvents)
+ NS_ProcessNextEvent(thread);
+
+ NS_ProcessPendingEvents(thread);
+}
+
+static inline void QuitPumpingEvents()
+{
+ // Dispatch a task that toggles gKeepPumpingEvents so that we flush all
+ // of the pending tasks before exiting from PumpEvents.
+ nsCOMPtr<nsIRunnable> event = new nsQuitPumpingEvent();
+ NS_DispatchToMainThread(event);
+}
+
+#endif
diff --git a/netwerk/test/TestCookie.cpp b/netwerk/test/TestCookie.cpp
new file mode 100644
index 0000000000..68c7ff1b3e
--- /dev/null
+++ b/netwerk/test/TestCookie.cpp
@@ -0,0 +1,921 @@
+/* -*- 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 "TestCommon.h"
+#include "TestHarness.h"
+#include "nsIServiceManager.h"
+#include "nsICookieService.h"
+#include "nsICookieManager.h"
+#include "nsICookieManager2.h"
+#include "nsICookie2.h"
+#include <stdio.h>
+#include "plstr.h"
+#include "prprf.h"
+#include "nsNetUtil.h"
+#include "nsISimpleEnumerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsStringAPI.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+
+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 kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
+static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
+static const char kCookiesLifetimeCurrentSession[] = "network.cookie.lifetime.behavior";
+static const char kCookiesMaxPerHost[] = "network.cookie.maxPerHost";
+static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
+
+static char *sBuffer;
+
+#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.Replace(0, strlen("test=expiry; expires=") + strlen(timeStringPreset) + 1, "test=expiry; expires=");
+ cookieString.Append(timeStringPreset);
+}
+
+nsresult
+SetACookie(nsICookieService *aCookieService, const char *aSpec1, const char *aSpec2, const char* aCookieString, const char *aServerTime)
+{
+ nsCOMPtr<nsIURI> uri1, uri2;
+ NS_NewURI(getter_AddRefs(uri1), aSpec1);
+ if (aSpec2)
+ NS_NewURI(getter_AddRefs(uri2), aSpec2);
+
+ sBuffer = PR_sprintf_append(sBuffer, R"( for host "%s": SET )", aSpec1);
+ nsresult rv = aCookieService->SetCookieStringFromHttp(uri1, uri2, nullptr, (char *)aCookieString, aServerTime, nullptr);
+ // the following code is useless. the cookieservice blindly returns NS_OK
+ // from SetCookieString. we have to call GetCookie to see if the cookie was
+ // set correctly...
+ if (NS_FAILED(rv)) {
+ sBuffer = PR_sprintf_append(sBuffer, "nothing\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "\"%s\"\n", aCookieString);
+ }
+ return rv;
+}
+
+nsresult
+SetACookieNoHttp(nsICookieService *aCookieService, const char *aSpec, const char* aCookieString)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aSpec);
+
+ sBuffer = PR_sprintf_append(sBuffer, R"( for host "%s": SET )", aSpec);
+ nsresult rv = aCookieService->SetCookieString(uri, nullptr, (char *)aCookieString, nullptr);
+ // the following code is useless. the cookieservice blindly returns NS_OK
+ // from SetCookieString. we have to call GetCookie to see if the cookie was
+ // set correctly...
+ if (NS_FAILED(rv)) {
+ sBuffer = PR_sprintf_append(sBuffer, "nothing\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "\"%s\"\n", aCookieString);
+ }
+ return rv;
+}
+
+// returns true if cookie(s) for the given host were found; else false.
+// the cookie string is returned via aCookie.
+bool
+GetACookie(nsICookieService *aCookieService, const char *aSpec1, const char *aSpec2, char **aCookie)
+{
+ nsCOMPtr<nsIURI> uri1, uri2;
+ NS_NewURI(getter_AddRefs(uri1), aSpec1);
+ if (aSpec2)
+ NS_NewURI(getter_AddRefs(uri2), aSpec2);
+
+ sBuffer = PR_sprintf_append(sBuffer, R"( "%s": GOT )", aSpec1);
+ nsresult rv = aCookieService->GetCookieStringFromHttp(uri1, uri2, nullptr, aCookie);
+ if (NS_FAILED(rv)) {
+ sBuffer = PR_sprintf_append(sBuffer, "XXX GetCookieString() failed!\n");
+ }
+ if (!*aCookie) {
+ sBuffer = PR_sprintf_append(sBuffer, "nothing\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "\"%s\"\n", *aCookie);
+ }
+ return *aCookie != nullptr;
+}
+
+// returns true if cookie(s) for the given host were found; else false.
+// the cookie string is returned via aCookie.
+bool
+GetACookieNoHttp(nsICookieService *aCookieService, const char *aSpec, char **aCookie)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aSpec);
+
+ sBuffer = PR_sprintf_append(sBuffer, R"( "%s": GOT )", aSpec);
+ nsresult rv = aCookieService->GetCookieString(uri, nullptr, aCookie);
+ if (NS_FAILED(rv)) {
+ sBuffer = PR_sprintf_append(sBuffer, "XXX GetCookieString() failed!\n");
+ }
+ if (!*aCookie) {
+ sBuffer = PR_sprintf_append(sBuffer, "nothing\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "\"%s\"\n", *aCookie);
+ }
+ return *aCookie != nullptr;
+}
+
+// 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
+ }
+}
+
+// helper function that ensures the first aSize elements of aResult are
+// true (i.e. all tests succeeded). prints the result of the tests (if any
+// tests failed, it prints the zero-based index of each failed test).
+bool
+PrintResult(const bool aResult[], uint32_t aSize)
+{
+ bool failed = false;
+ sBuffer = PR_sprintf_append(sBuffer, "*** tests ");
+ for (uint32_t i = 0; i < aSize; ++i) {
+ if (!aResult[i]) {
+ failed = true;
+ sBuffer = PR_sprintf_append(sBuffer, "%d ", i);
+ }
+ }
+ if (failed) {
+ sBuffer = PR_sprintf_append(sBuffer, "FAILED!\a\n");
+ } else {
+ sBuffer = PR_sprintf_append(sBuffer, "passed.\n");
+ }
+ return !failed;
+}
+
+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
+ aPrefBranch->SetBoolPref(kCookiesLifetimeEnabled, true);
+ aPrefBranch->SetIntPref(kCookiesLifetimeCurrentSession, 0);
+ aPrefBranch->SetIntPref(kCookiesLifetimeDays, 1);
+ aPrefBranch->SetBoolPref(kCookieLeaveSecurityAlone, true);
+ // Set the base domain limit to 50 so we have a known value.
+ aPrefBranch->SetIntPref(kCookiesMaxPerHost, 50);
+}
+
+
+int
+main(int32_t argc, char *argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ bool allTestsPassed = true;
+
+ ScopedXPCOM xpcom("TestCookie");
+
+ {
+ nsresult rv0;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv0);
+ if (NS_FAILED(rv0)) return -1;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(kPrefServiceCID, &rv0);
+ if (NS_FAILED(rv0)) return -1;
+
+ InitPrefs(prefBranch);
+
+ bool rv[20];
+ 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.
+ *
+ * the results of each individual testing operation from CheckResult() is
+ * stored in an array of bools, which is then checked against the expected
+ * outcomes (all successes), by PrintResult(). the overall result of all
+ * tests to date is kept in |allTestsPassed|, for convenient display at the
+ * end.
+ *
+ * Interpreting the output:
+ * each setting/getting operation will print output saying exactly what
+ * it's doing and the outcome, respectively. this information is only
+ * useful for debugging purposes; the actual result of the tests is
+ * printed at the end of each block of tests. this will either be "all
+ * tests passed" or "tests X Y Z failed", where X, Y, Z are the indexes
+ * of rv (i.e. zero-based). at the conclusion of all tests, the overall
+ * passed/failed result is printed.
+ *
+ * NOTE: this testsuite is not yet comprehensive or complete, and is
+ * somewhat contrived - still under development, and needs improving!
+ */
+
+ // *** basic tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning basic tests...\n");
+
+ // test some basic variations of the domain & path
+ SetACookie(cookieService, "http://www.basic.com", nullptr, "test=basic", nullptr);
+ GetACookie(cookieService, "http://www.basic.com", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test=basic");
+ GetACookie(cookieService, "http://www.basic.com/testPath/testfile.txt", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_EQUAL, "test=basic");
+ GetACookie(cookieService, "http://www.basic.com./", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://www.basic.com.", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://www.basic.com./testPath/testfile.txt", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://www.basic2.com/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://www.basic.com", nullptr, "test=basic; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://www.basic.com/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 7) && allTestsPassed;
+
+
+ // *** domain tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning domain tests...\n");
+
+ // test some variations of the domain & path, for different domains of
+ // a domain cookie
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=domain.com", nullptr);
+ GetACookie(cookieService, "http://domain.com", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ GetACookie(cookieService, "http://domain.com.", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://www.domain.com", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=domain.com; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://domain.com", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=.domain.com", nullptr);
+ GetACookie(cookieService, "http://domain.com", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ GetACookie(cookieService, "http://www.domain.com", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ GetACookie(cookieService, "http://bah.domain.com", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_EQUAL, "test=domain");
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=.domain.com; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://domain.com", nullptr, getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=.foo.domain.com", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[9] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=moose.com", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[10] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=domain.com.", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[11] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=..domain.com", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[12] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://www.domain.com", nullptr, "test=domain; domain=..domain.com.", nullptr);
+ GetACookie(cookieService, "http://foo.domain.com", nullptr, getter_Copies(cookie));
+ rv[13] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, R"(test=taco; path="/bogus")", nullptr);
+ GetACookie(cookieService, "http://path.net/path/file", nullptr, getter_Copies(cookie));
+ rv[14] = CheckResult(cookie.get(), MUST_EQUAL, "test=taco");
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=taco; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/path/file", nullptr, getter_Copies(cookie));
+ rv[15] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 16) && allTestsPassed;
+
+
+ // *** path tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning path tests...\n");
+
+ // test some variations of the domain & path, for different paths of
+ // a path cookie
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/path", nullptr);
+ GetACookie(cookieService, "http://path.net/path", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path/hithere.foo", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path?hithere/foo", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path2", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://path.net/path2/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/path; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/path/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/path/", nullptr);
+ GetACookie(cookieService, "http://path.net/path", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ GetACookie(cookieService, "http://path.net/path/", nullptr, getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/path/; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/path/", nullptr, getter_Copies(cookie));
+ rv[9] = 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
+ // nsCookieService::CheckPath()), so we test this functionality too
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/foo/", nullptr);
+ GetACookie(cookieService, "http://path.net/path", nullptr, getter_Copies(cookie));
+ rv[10] = CheckResult(cookie.get(), MUST_BE_NULL);
+ GetACookie(cookieService, "http://path.net/foo", nullptr, getter_Copies(cookie));
+ rv[11] = CheckResult(cookie.get(), MUST_EQUAL, "test=path");
+ SetACookie(cookieService, "http://path.net/path/file", nullptr, "test=path; path=/foo/; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/foo/", nullptr, getter_Copies(cookie));
+ rv[12] = 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/", nullptr, "test=path; path=/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890/", nullptr);
+ GetACookie(cookieService, "http://path.net/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", nullptr, getter_Copies(cookie));
+ rv[13] = 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/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890/", nullptr, "test=path", nullptr);
+ GetACookie(cookieService, "http://path.net/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890/", nullptr, getter_Copies(cookie));
+ rv[14] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // the following cookie includes a tab in the path
+ SetACookie(cookieService, "http://path.net/", nullptr, "test=path; path=/foo\tbar/", nullptr);
+ GetACookie(cookieService, "http://path.net/foo\tbar/", nullptr, getter_Copies(cookie));
+ rv[15] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // the following cookie includes a tab in the name
+ SetACookie(cookieService, "http://path.net/", nullptr, "test\ttabs=tab", nullptr);
+ GetACookie(cookieService, "http://path.net/", nullptr, getter_Copies(cookie));
+ rv[16] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // the following cookie includes a tab in the value - allowed
+ SetACookie(cookieService, "http://path.net/", nullptr, "test=tab\ttest", nullptr);
+ GetACookie(cookieService, "http://path.net/", nullptr, getter_Copies(cookie));
+ rv[17] = CheckResult(cookie.get(), MUST_EQUAL, "test=tab\ttest");
+ SetACookie(cookieService, "http://path.net/", nullptr, "test=tab\ttest; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://path.net/", nullptr, getter_Copies(cookie));
+ rv[18] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 19) && allTestsPassed;
+
+
+ // *** expiry & deletion tests
+ // XXX add server time str parsing tests here
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning expiry & deletion tests...\n");
+
+ // test some variations of the expiry time,
+ // and test deletion of previously set cookies
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=0", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; expires=bad", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; expires=Thu, 10 Apr 1980 16:33:12 GMT", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, R"(test=expiry; expires="Thu, 10 Apr 1980 16:33:12 GMT)", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, R"(test=expiry; expires="Thu, 10 Apr 1980 16:33:12 GMT")", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=60", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=-20", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=60", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; expires=Thu, 10 Apr 1980 16:33:12 GMT", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[9] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=expiry; max-age=60", nullptr);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "newtest=expiry; max-age=60", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[10] = CheckResult(cookie.get(), MUST_CONTAIN, "test=expiry");
+ rv[11] = CheckResult(cookie.get(), MUST_CONTAIN, "newtest=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "test=differentvalue; max-age=0", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[12] = CheckResult(cookie.get(), MUST_EQUAL, "newtest=expiry");
+ SetACookie(cookieService, "http://expireme.org/", nullptr, "newtest=evendifferentvalue; max-age=0", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[13] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ SetACookie(cookieService, "http://foo.expireme.org/", nullptr, "test=expiry; domain=.expireme.org; max-age=60", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[14] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ SetACookie(cookieService, "http://bar.expireme.org/", nullptr, "test=differentvalue; domain=.expireme.org; max-age=0", nullptr);
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[15] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ nsAutoCString ServerTime;
+ nsAutoCString CookieString;
+
+ SetTime(-OFFSET_ONE_WEEK, ServerTime, CookieString, true);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, CookieString.get(), ServerTime.get());
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[16] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Set server time earlier than client time for one year + one day, and expirty time earlier than server time for one day.
+ SetTime(-(OFFSET_ONE_DAY + OFFSET_ONE_WEEK), ServerTime, CookieString, false);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, CookieString.get(), ServerTime.get());
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[17] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Set server time later than client time for one year, and expiry time later than server time for one day.
+ SetTime(OFFSET_ONE_WEEK, ServerTime, CookieString, false);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, CookieString.get(), ServerTime.get());
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[18] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ // Set server time later than client time for one year + one day, and expiry time earlier than server time for one day.
+ SetTime((OFFSET_ONE_DAY + OFFSET_ONE_WEEK), ServerTime, CookieString, true);
+ SetACookie(cookieService, "http://expireme.org/", nullptr, CookieString.get(), ServerTime.get());
+ GetACookie(cookieService, "http://expireme.org/", nullptr, getter_Copies(cookie));
+ rv[19] = CheckResult(cookie.get(), MUST_EQUAL, "test=expiry");
+ allTestsPassed = PrintResult(rv, 20) && allTestsPassed;
+
+ // *** multiple cookie tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning multiple cookie tests...\n");
+
+ // 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/", nullptr, "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", nullptr);
+ GetACookie(cookieService, "http://multiple.cookies/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=multiple");
+ rv[1] = CheckResult(cookie.get(), MUST_CONTAIN, "test=different");
+ rv[2] = CheckResult(cookie.get(), MUST_CONTAIN, "test=same");
+ rv[3] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "newtest=ciao");
+ rv[4] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "newtest=foo");
+ rv[5] = CheckResult(cookie.get(), MUST_CONTAIN, "newtest=reincarnated");
+ SetACookie(cookieService, "http://multiple.cookies/", nullptr, "test=expiry; domain=.multiple.cookies; max-age=0", nullptr);
+ GetACookie(cookieService, "http://multiple.cookies/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=same");
+ SetACookie(cookieService, "http://multiple.cookies/", nullptr, "\n test=different; max-age=0 \n", nullptr);
+ GetACookie(cookieService, "http://multiple.cookies/", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test=different");
+ SetACookie(cookieService, "http://multiple.cookies/", nullptr, "newtest=dead; max-age=0", nullptr);
+ GetACookie(cookieService, "http://multiple.cookies/", nullptr, getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 9) && allTestsPassed;
+
+
+ // *** parser tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning parser tests...\n");
+
+ // test the cookie header parser, under various circumstances.
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=parser; domain=.parser.test; ;; ;=; ,,, ===,abc,=; abracadabra! max-age=20;=;;", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test=parser");
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=parser; domain=.parser.test; max-age=0", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=\"fubar! = foo;bar\\\";\" parser; domain=.parser.test; max-age=6\nfive; max-age=2.63,", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_CONTAIN, R"(test="fubar! = foo)");
+ rv[3] = CheckResult(cookie.get(), MUST_CONTAIN, "five");
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=kill; domain=.parser.test; max-age=0 \n five; max-age=0", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[4] = 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/", nullptr, "six", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "six");
+ SetACookie(cookieService, "http://parser.test/", nullptr, "seven", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "seven");
+ SetACookie(cookieService, "http://parser.test/", nullptr, " =eight", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_EQUAL, "eight");
+ SetACookie(cookieService, "http://parser.test/", nullptr, "test=six", nullptr);
+ GetACookie(cookieService, "http://parser.test/", nullptr, getter_Copies(cookie));
+ rv[9] = CheckResult(cookie.get(), MUST_CONTAIN, "test=six");
+
+ allTestsPassed = PrintResult(rv, 10) && allTestsPassed;
+
+
+ // *** path ordering tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning path ordering tests...\n");
+
+ // 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/", nullptr, "test1=path; path=/one/two/three", nullptr);
+ SetACookie(cookieService, "http://multi.path.tests/", nullptr, "test2=path; path=/one \n test3=path; path=/one/two/three/four \n test4=path; path=/one/two \n test5=path; path=/one/two/", nullptr);
+ SetACookie(cookieService, "http://multi.path.tests/one/two/three/four/five/", nullptr, "test6=path", nullptr);
+ SetACookie(cookieService, "http://multi.path.tests/one/two/three/four/five/six/", nullptr, "test7=path; path=", nullptr);
+ SetACookie(cookieService, "http://multi.path.tests/", nullptr, "test8=path; path=/", nullptr);
+ GetACookie(cookieService, "http://multi.path.tests/one/two/three/four/five/six/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, "test7=path; test6=path; test3=path; test1=path; test5=path; test4=path; test2=path; test8=path");
+
+ allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
+
+
+ // *** httponly tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning httponly tests...\n");
+
+ // Since this cookie is NOT set via http, setting it fails
+ SetACookieNoHttp(cookieService, "http://httponly.test/", "test=httponly; httponly");
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Since this cookie is set via http, it can be retrieved
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly", nullptr);
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_EQUAL, "test=httponly");
+ // ... but not by web content
+ GetACookieNoHttp(cookieService, "http://httponly.test/", getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Non-Http cookies should not replace HttpOnly cookies
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly", nullptr);
+ SetACookieNoHttp(cookieService, "http://httponly.test/", "test=not-httponly");
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[3] = CheckResult(cookie.get(), MUST_EQUAL, "test=httponly");
+ // ... and, if an HttpOnly cookie already exists, should not be set at all
+ GetACookieNoHttp(cookieService, "http://httponly.test/", getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Non-Http cookies should not delete HttpOnly cookies
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly", nullptr);
+ SetACookieNoHttp(cookieService, "http://httponly.test/", "test=httponly; max-age=-1");
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "test=httponly");
+ // ... but HttpOnly cookies should
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly; max-age=-1", nullptr);
+ GetACookie(cookieService, "http://httponly.test/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_BE_NULL);
+ // Non-Httponly cookies can replace HttpOnly cookies when set over http
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=httponly; httponly", nullptr);
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=not-httponly", nullptr);
+ GetACookieNoHttp(cookieService, "http://httponly.test/", getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_EQUAL, "test=not-httponly");
+ // scripts should not be able to set httponly cookies by replacing an existing non-httponly cookie
+ SetACookie(cookieService, "http://httponly.test/", nullptr, "test=not-httponly", nullptr);
+ SetACookieNoHttp(cookieService, "http://httponly.test/", "test=httponly; httponly");
+ GetACookieNoHttp(cookieService, "http://httponly.test/", getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_EQUAL, "test=not-httponly");
+
+ allTestsPassed = PrintResult(rv, 9) && allTestsPassed;
+
+
+ // *** Cookie prefix tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning cookie prefix tests...\n");
+
+ // prefixed cookies can't be set from insecure HTTP
+ SetACookie(cookieService, "http://prefixed.test/", nullptr, "__Secure-test1=test", nullptr);
+ SetACookie(cookieService, "http://prefixed.test/", nullptr, "__Secure-test2=test; secure", nullptr);
+ SetACookie(cookieService, "http://prefixed.test/", nullptr, "__Host-test1=test", nullptr);
+ SetACookie(cookieService, "http://prefixed.test/", nullptr, "__Host-test2=test; secure", nullptr);
+ GetACookie(cookieService, "http://prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // prefixed cookies won't be set without the secure flag
+ SetACookie(cookieService, "https://prefixed.test/", nullptr, "__Secure-test=test", nullptr);
+ SetACookie(cookieService, "https://prefixed.test/", nullptr, "__Host-test=test", nullptr);
+ GetACookie(cookieService, "https://prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[1] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // prefixed cookies can be set when done correctly
+ SetACookie(cookieService, "https://prefixed.test/", nullptr, "__Secure-test=test; secure", nullptr);
+ SetACookie(cookieService, "https://prefixed.test/", nullptr, "__Host-test=test; secure", nullptr);
+ GetACookie(cookieService, "https://prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[2] = CheckResult(cookie.get(), MUST_CONTAIN, "__Secure-test=test");
+ rv[3] = CheckResult(cookie.get(), MUST_CONTAIN, "__Host-test=test");
+
+ // but when set must not be returned to the host insecurely
+ GetACookie(cookieService, "http://prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // Host-prefixed cookies cannot specify a domain
+ SetACookie(cookieService, "https://host.prefixed.test/", nullptr, "__Host-a=test; secure; domain=prefixed.test", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/", nullptr, "__Host-b=test; secure; domain=.prefixed.test", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/", nullptr, "__Host-c=test; secure; domain=host.prefixed.test", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/", nullptr, "__Host-d=test; secure; domain=.host.prefixed.test", nullptr);
+ GetACookie(cookieService, "https://host.prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ // Host-prefixed cookies can only have a path of "/"
+ SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-e=test; secure", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-f=test; secure; path=/", nullptr);
+ SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-g=test; secure; path=/some", nullptr);
+ GetACookie(cookieService, "https://host.prefixed.test/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "__Host-f=test");
+
+ allTestsPassed = PrintResult(rv, 7) && allTestsPassed;
+
+ // *** leave-secure-alone tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning leave-secure-alone tests...\n");
+
+ // testing items 0 & 1 for 3.1 of spec Deprecate modification of ’secure’
+ // cookies from non-secure origins
+ SetACookie(cookieService, "http://www.security.test/", nullptr, "test=non-security; secure", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+ SetACookie(cookieService, "https://www.security.test/path/", nullptr, "test=security; secure; path=/path/", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/", getter_Copies(cookie));
+ rv[1] = 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/", nullptr, "test=security2; secure; path=/path/", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/", getter_Copies(cookie));
+ rv[2] = 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/", nullptr, "test=non-security; path=/path/foo", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/path/foo/", getter_Copies(cookie));
+ rv[3] = 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/", nullptr, "test=non-security; path=/bar", nullptr);
+ GetACookieNoHttp(cookieService, "http://www.security.test/bar/", getter_Copies(cookie));
+ rv[4] = CheckResult(cookie.get(), MUST_EQUAL, "test=non-security");
+ // Modify value and downgrade secure level.
+ SetACookie(cookieService, "https://www.security.test/", nullptr, "test_modify_cookie=security-cookie; secure; domain=.security.test", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+ rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "test_modify_cookie=security-cookie");
+ SetACookie(cookieService, "https://www.security.test/", nullptr, "test_modify_cookie=non-security-cookie; domain=.security.test", nullptr);
+ GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+ rv[6] = 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/", nullptr, "test=security3", nullptr);
+ GetACookieNoHttp(cookieService, "http://www.security.test/", getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_CONTAIN, "test=security3");
+ SetACookie(cookieService, "http://www.security.test/", nullptr, "test=non-security2; domain=security.test", nullptr);
+ GetACookieNoHttp(cookieService, "http://www.security.test/", getter_Copies(cookie));
+ rv[8] = CheckResult(cookie.get(), MUST_CONTAIN, "test=non-security2");
+
+ allTestsPassed = PrintResult(rv, 9) && allTestsPassed;
+
+ // *** nsICookieManager{2} interface tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning nsICookieManager{2} interface tests...\n");
+ nsCOMPtr<nsICookieManager> cookieMgr = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv0);
+ if (NS_FAILED(rv0)) return -1;
+ nsCOMPtr<nsICookieManager2> cookieMgr2 = do_QueryInterface(cookieMgr);
+ if (!cookieMgr2) return -1;
+
+ mozilla::NeckoOriginAttributes attrs;
+
+ // first, ensure a clean slate
+ rv[0] = NS_SUCCEEDED(cookieMgr->RemoveAll());
+ // add some cookies
+ rv[1] = NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("cookiemgr.test"), // domain
+ NS_LITERAL_CSTRING("/foo"), // path
+ NS_LITERAL_CSTRING("test1"), // name
+ NS_LITERAL_CSTRING("yes"), // value
+ false, // is secure
+ false, // is httponly
+ true, // is session
+ INT64_MAX, // expiry time
+ &attrs)); // originAttributes
+ rv[2] = NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("cookiemgr.test"), // domain
+ NS_LITERAL_CSTRING("/foo"), // path
+ NS_LITERAL_CSTRING("test2"), // name
+ NS_LITERAL_CSTRING("yes"), // value
+ false, // is secure
+ true, // is httponly
+ true, // is session
+ PR_Now() / PR_USEC_PER_SEC + 2, // expiry time
+ &attrs)); // originAttributes
+ rv[3] = NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"), // domain
+ NS_LITERAL_CSTRING("/rabbit"), // path
+ NS_LITERAL_CSTRING("test3"), // name
+ NS_LITERAL_CSTRING("yes"), // value
+ false, // is secure
+ false, // is httponly
+ true, // is session
+ INT64_MAX, // expiry time
+ &attrs)); // originAttributes
+ // confirm using enumerator
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv[4] = NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator)));
+ int32_t i = 0;
+ bool more;
+ nsCOMPtr<nsICookie2> expiredCookie, newDomainCookie;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> cookie;
+ if (NS_FAILED(enumerator->GetNext(getter_AddRefs(cookie)))) break;
+ ++i;
+
+ // keep tabs on the second and third cookies, so we can check them later
+ nsCOMPtr<nsICookie2> cookie2(do_QueryInterface(cookie));
+ if (!cookie2) break;
+ nsAutoCString name;
+ cookie2->GetName(name);
+ if (name.EqualsLiteral("test2"))
+ expiredCookie = cookie2;
+ else if (name.EqualsLiteral("test3"))
+ newDomainCookie = cookie2;
+ }
+ rv[5] = i == 3;
+ // check the httpOnly attribute of the second cookie is honored
+ GetACookie(cookieService, "http://cookiemgr.test/foo/", nullptr, getter_Copies(cookie));
+ rv[6] = CheckResult(cookie.get(), MUST_CONTAIN, "test2=yes");
+ GetACookieNoHttp(cookieService, "http://cookiemgr.test/foo/", getter_Copies(cookie));
+ rv[7] = CheckResult(cookie.get(), MUST_NOT_CONTAIN, "test2=yes");
+ // check CountCookiesFromHost()
+ uint32_t hostCookies = 0;
+ rv[8] = NS_SUCCEEDED(cookieMgr2->CountCookiesFromHost(NS_LITERAL_CSTRING("cookiemgr.test"), &hostCookies)) &&
+ hostCookies == 2;
+ // check CookieExistsNative() using the third cookie
+ bool found;
+ rv[9] = NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)) && found;
+
+
+ // remove the cookie, block it, and ensure it can't be added again
+ rv[10] = NS_SUCCEEDED(cookieMgr->RemoveNative(NS_LITERAL_CSTRING("new.domain"), // domain
+ NS_LITERAL_CSTRING("test3"), // name
+ NS_LITERAL_CSTRING("/rabbit"), // path
+ true, // is blocked
+ &attrs)); // originAttributes
+ rv[11] = NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)) && !found;
+ rv[12] = NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"), // domain
+ NS_LITERAL_CSTRING("/rabbit"), // path
+ NS_LITERAL_CSTRING("test3"), // name
+ NS_LITERAL_CSTRING("yes"), // value
+ false, // is secure
+ false, // is httponly
+ true, // is session
+ INT64_MIN, // expiry time
+ &attrs)); // originAttributes
+ rv[13] = NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)) && !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
+ rv[14] = NS_SUCCEEDED(cookieMgr2->CountCookiesFromHost(NS_LITERAL_CSTRING("cookiemgr.test"), &hostCookies)) &&
+ hostCookies == 2;
+ rv[15] = NS_SUCCEEDED(cookieMgr2->CookieExistsNative(expiredCookie, &attrs, &found)) && found;
+ // double-check RemoveAll() using the enumerator
+ rv[16] = NS_SUCCEEDED(cookieMgr->RemoveAll());
+ rv[17] = NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator))) &&
+ NS_SUCCEEDED(enumerator->HasMoreElements(&more)) &&
+ !more;
+
+ allTestsPassed = PrintResult(rv, 18) && allTestsPassed;
+
+
+ // *** eviction and creation ordering tests
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning eviction and creation ordering tests...\n");
+
+ // 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 = NS_LITERAL_CSTRING("test");
+ name.AppendInt(i);
+ name += NS_LITERAL_CSTRING("=creation");
+ SetACookie(cookieService, "http://creation.ordering.tests/", nullptr, name.get(), nullptr);
+
+ if (i >= 10) {
+ expected += name;
+ if (i < 59)
+ expected += NS_LITERAL_CSTRING("; ");
+ }
+ }
+ GetACookie(cookieService, "http://creation.ordering.tests/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_EQUAL, expected.get());
+
+ allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
+
+ // *** eviction and creation ordering tests after enable network.cookie.leave-secure-alone
+ sBuffer = PR_sprintf_append(sBuffer, "*** Beginning eviction and creation tests after enable nework.cookie.leave-secure-alone...\n");
+ // reset cookie
+ cookieMgr->RemoveAll();
+
+ for (int32_t i = 0; i < 60; ++i) {
+ name = NS_LITERAL_CSTRING("test");
+ name.AppendInt(i);
+ name += NS_LITERAL_CSTRING("=delete_non_security");
+
+ // Create 50 cookies that include the secure flag.
+ if (i < 50) {
+ name += NS_LITERAL_CSTRING("; secure");
+ SetACookie(cookieService, "https://creation.ordering.tests/", nullptr, name.get(), nullptr);
+ } else {
+ // non-security cookies will be removed beside the latest cookie that be created.
+ SetACookie(cookieService, "http://creation.ordering.tests/", nullptr, name.get(), nullptr);
+ }
+ }
+ GetACookie(cookieService, "http://creation.ordering.tests/", nullptr, getter_Copies(cookie));
+ rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+ allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
+
+
+ // XXX the following are placeholders: add these tests please!
+ // *** "noncompliant cookie" tests
+ // *** IP address tests
+ // *** speed tests
+
+
+ sBuffer = PR_sprintf_append(sBuffer, "\n*** Result: %s!\n\n", allTestsPassed ? "all tests passed" : "TEST(S) FAILED");
+ }
+
+ if (!allTestsPassed) {
+ // print the entire log
+ printf("%s", sBuffer);
+ return 1;
+ }
+
+ PR_smprintf_free(sBuffer);
+ sBuffer = nullptr;
+
+ return 0;
+}
+
+// Stubs to make this test happy
+
+mozilla::dom::OriginAttributesDictionary::OriginAttributesDictionary()
+ : mAppId(0),
+ mInIsolatedMozBrowser(false),
+ mPrivateBrowsingId(0),
+ mUserContextId(0)
+{}
diff --git a/netwerk/test/TestDNS.cpp b/netwerk/test/TestDNS.cpp
new file mode 100644
index 0000000000..94a09a62a8
--- /dev/null
+++ b/netwerk/test/TestDNS.cpp
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <stdio.h>
+#include <stdlib.h>
+#include "nsIServiceManager.h"
+#include "nsPIDNSService.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsICancelable.h"
+#include "nsCOMPtr.h"
+#include "nsStringAPI.h"
+#include "nsNetCID.h"
+#include "prinrval.h"
+#include "prthread.h"
+#include "prnetdb.h"
+#include "nsXPCOM.h"
+#include "nsServiceManagerUtils.h"
+
+class myDNSListener : public nsIDNSListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ myDNSListener(const char *host, int32_t index)
+ : mHost(host)
+ , mIndex(index) {}
+
+ NS_IMETHOD OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status) override
+ {
+ printf("%d: OnLookupComplete called [host=%s status=%x rec=%p]\n",
+ mIndex, mHost.get(), static_cast<uint32_t>(status), (void*)rec);
+
+ if (NS_SUCCEEDED(status)) {
+ nsAutoCString buf;
+
+ rec->GetCanonicalName(buf);
+ printf("%d: canonname=%s\n", mIndex, buf.get());
+
+ bool hasMore;
+ while (NS_SUCCEEDED(rec->HasMore(&hasMore)) && hasMore) {
+ rec->GetNextAddrAsString(buf);
+ printf("%d: => %s\n", mIndex, buf.get());
+ }
+ }
+
+ return NS_OK;
+ }
+
+private:
+ virtual ~myDNSListener() = default;
+
+ nsCString mHost;
+ int32_t mIndex;
+};
+
+
+NS_IMPL_ISUPPORTS(myDNSListener, nsIDNSListener)
+
+static bool IsAscii(const char *s)
+{
+ for (; *s; ++s) {
+ if (*s & 0x80)
+ return false;
+ }
+
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ int sleepLen = 10; // default: 10 seconds
+
+ if (argc == 1) {
+ printf("usage: TestDNS [-N] hostname1 [hostname2 ...]\n");
+ return -1;
+ }
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ nsCOMPtr<nsPIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns)
+ return -1;
+
+ if (argv[1][0] == '-') {
+ sleepLen = atoi(argv[1]+1);
+ argv++;
+ argc--;
+ }
+
+ for (int j=0; j<2; ++j) {
+ for (int i=1; i<argc; ++i) {
+ // assume non-ASCII input is given in the native charset
+ nsAutoCString hostBuf;
+ if (IsAscii(argv[i]))
+ hostBuf.Assign(argv[i]);
+ else
+ hostBuf = NS_ConvertUTF16toUTF8(NS_ConvertASCIItoUTF16(argv[i]));
+
+ nsCOMPtr<nsIDNSListener> listener = new myDNSListener(argv[i], i);
+
+ nsCOMPtr<nsICancelable> req;
+ nsresult rv = dns->AsyncResolve(hostBuf,
+ nsIDNSService::RESOLVE_CANONICAL_NAME,
+ listener, nullptr, getter_AddRefs(req));
+ if (NS_FAILED(rv))
+ printf("### AsyncResolve failed [rv=%x]\n",
+ static_cast<uint32_t>(rv));
+ }
+
+ printf("main thread sleeping for %d seconds...\n", sleepLen);
+ PR_Sleep(PR_SecondsToInterval(sleepLen));
+ }
+
+ printf("shutting down main thread...\n");
+ dns->Shutdown();
+ }
+
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestDNSDaemon.cpp b/netwerk/test/TestDNSDaemon.cpp
new file mode 100644
index 0000000000..fa347aa0c7
--- /dev/null
+++ b/netwerk/test/TestDNSDaemon.cpp
@@ -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/. */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+
+#if defined(AIX) || defined(__linux)
+#include <sys/select.h> // for fd_set
+#endif
+
+#if defined(__linux)
+// Didn't find gettdtablehi() or gettdtablesize() on linux. Using FD_SETSIZE
+#define getdtablehi() FD_SETSIZE
+#else
+#define getdtablehi() getdtablesize()
+
+// If you find a system doesn't have getdtablesize try #define getdtablesize
+// to FD_SETSIZE. And if you encounter a system that doesn't even have
+// FD_SETSIZE, just grab your ankles and use 255.
+#endif
+
+
+#include "nspr.h"
+#include "nsCRT.h"
+#include "unix_dns.h"
+
+struct sockaddr_un unix_addr;
+
+int async_dns_lookup(char* hostName)
+{
+ fprintf(stderr, "start async_dns_lookup\n");
+ int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (socket_fd == -1) {
+ fprintf(stderr, "socket returned error.\n");
+ return -1;
+ }
+
+ unix_addr.sun_family = AF_UNIX;
+ strcpy(unix_addr.sun_path, DNS_SOCK_NAME);
+
+ int err = connect(socket_fd,(struct sockaddr*)&unix_addr, sizeof(unix_addr));
+ if (err == -1) {
+ fprintf(stderr, "connect failed (errno = %d).\n",errno);
+ close(socket_fd);
+ return -1;
+ }
+
+ char buf[256];
+ strcpy(buf, "lookup: ");
+ strcpy(&buf[8], hostName);
+
+ err = send(socket_fd, buf, strlen(buf)+1, 0);
+ if (err < 0)
+ fprintf(stderr, "send(%s) returned error (errno=%d).\n",buf, errno);
+
+ // receive 4 byte ID
+ err = recv(socket_fd, buf, 256, 0);
+ if (err < 0)
+ fprintf(stderr, "recv() returned error (errno=%d).\n", errno);
+ else
+ {
+ // printf("recv() returned %d bytes.");
+ int id = *(int *)buf;
+ fprintf(stderr, "id: %d\n", id);
+ }
+
+ return socket_fd;
+}
+
+static char *
+string_trim(char *s)
+{
+ char *s2;
+ if (!s) return 0;
+ s2 = s + strlen(s) - 1;
+ while (s2 > s && (*s2 == '\n' || *s2 == '\r' || *s2 == ' ' || *s2 == '\t'))
+ *s2-- = 0;
+ while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
+ s++;
+ return s;
+}
+
+hostent *
+bytesToHostent(char *buf)
+{
+ int i;
+ // int size = 0;
+ int len, aliasCount, addressCount;
+ int addrtype, addrlength;
+ char* p = buf;
+ char s[1024];
+
+ len = *(int *)p; // length of name
+ p += sizeof(int); // advance past name length
+
+ memcpy(s, p, len); s[len] = 0;
+ fprintf(stderr, "hostname: %s\n", s);
+
+ p += len; // advance past name
+ aliasCount = *(int *)p; // number of aliases
+ p += sizeof(int); // advance past alias count
+
+ for (i=0; i<aliasCount; i++) {
+ len = *(int *)p; // length of alias name
+ p += sizeof(int); // advance past alias name length
+
+ memcpy(s, p, len); s[len] = 0;
+ fprintf(stderr, "alias: %s\n", s);
+
+ p += len; // advance past alias name
+ }
+
+ addrtype = *(int *)p;
+
+ fprintf(stderr, "addrtype: %d\n", addrtype);
+
+ p += sizeof(int);
+ addrlength = *(int *)p;
+
+ fprintf(stderr, "addrlength: %d\n", addrlength);
+
+ p += sizeof(int);
+ addressCount = *(int *)p;
+ p += sizeof(int);
+
+ for (i=0; i<addressCount; i++) {
+ len = *(int *)p;
+ p += sizeof(int);
+
+ fprintf(stderr, "addr len: %d\n", len);
+ fprintf(stderr, "addr : %x\n", *(int *)p);
+
+ p += len;
+ }
+
+ // size = p - buf;
+ // size += 1 + aliasCount;
+ return 0;
+}
+
+int
+main(int argc, char* argv[])
+{
+ PRStatus status;
+
+ // launch daemon
+ printf("### launch daemon...\n");
+
+ PRProcessAttr *attributes = PR_NewProcessAttr();
+ if (attributes == nullptr) {
+ printf("PR_NewProcessAttr() failed.\n");
+ return -1;
+ }
+
+ PRProcess *daemon = PR_CreateProcess("nsDnsAsyncLookup", nullptr, nullptr, attributes);
+ if (daemon == nullptr) {
+ printf("PR_CreateProcess failed.\n");
+ } else {
+ // status = PR_DetachProcess(daemon);
+ //if (status != 0)
+ // printf("PR_DetachProcess returned %d\n", status);
+ //daemon = nullptr;
+ }
+
+ PR_DestroyProcessAttr(attributes);
+
+ // create socket and connect to daemon
+ int socket_fd = 0;
+
+
+ bool notDone = true;
+ char buf[1024];
+
+ while(notDone) {
+ int status = 0;
+ fd_set fdset;
+
+ FD_ZERO(&fdset);
+ FD_SET(fileno(stdin), &fdset);
+ if (socket_fd > 0)
+ FD_SET(socket_fd, &fdset);
+
+ status = select(getdtablehi(), &fdset, 0, 0, 0);
+ if (status <= 0)
+ {
+ fprintf(stderr, "%s: select() returned %d\n", argv[0], status);
+ exit(-1);
+ }
+
+ // which fd is set?
+
+ if (FD_ISSET(fileno(stdin), &fdset))
+ {
+ char *line = fgets(buf, sizeof(buf)-1, stdin);
+ line = string_trim(line);
+
+ if(!strcmp(line, "quit") || !strcmp(line, "exit"))
+ {
+ fprintf(stderr, "bye now.\n");
+ notDone = false;
+ }
+ else if (!strncmp(line, "abort ", 6))
+ {
+ // abort id
+ }
+ else if (strchr(line, ' ') || strchr(line, '\t'))
+ {
+ fprintf(stderr, "%s: unrecognized command %s.\n", argv[0], line);
+ }
+ else
+ {
+ fprintf(stderr, "%s: looking up %s...\n", argv[0], line);
+ // initiate dns lookup
+ socket_fd = async_dns_lookup(line);
+ }
+ }
+
+ if (socket_fd && FD_ISSET(socket_fd, &fdset))
+ {
+ // read from socket, parse results
+ int size = read(socket_fd, buf, 1024);
+ if (size > 0)
+ {
+ // parse buffer into hostent
+ char *p = buf;
+ fprintf(stderr, "bytes read: %d\n", size);
+ fprintf(stderr, "response code: %d\n", *(int *)p);
+ p += sizeof(int);
+
+ for (int i=0; i < size; i++) {
+ if (!(i%8))
+ fprintf(stderr, "\n");
+ fprintf(stderr, "%2.2x ",(unsigned char)buf[i]);
+ }
+ fprintf(stderr, "\n");
+ hostent *h;
+ h = bytesToHostent(p);
+ }
+ close(socket_fd);
+ socket_fd = 0;
+ }
+ }
+
+ return 0;
+}
+
+/*
+buffer
+
+int nameLen;
+
+if (nameLen > 0)
+ char [nameLen+1] name
+
+int aliasCount
+for each alias
+ int aliasNameLen
+ char [aliasNameLen+1] aliasName
+
+int h_addrtype
+int h_length
+int addrCount
+for each addr
+ char[h_length] addr
+
+
+
+*/
diff --git a/netwerk/test/TestFileInput2.cpp b/netwerk/test/TestFileInput2.cpp
new file mode 100644
index 0000000000..f51988010e
--- /dev/null
+++ b/netwerk/test/TestFileInput2.cpp
@@ -0,0 +1,480 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+#include "nsCOMArray.h"
+#include "nsISimpleEnumerator.h"
+#include "prinrval.h"
+#include "nsIFileStreams.h"
+#include "nsIFileChannel.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include <stdio.h>
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include <math.h>
+#include "prprf.h"
+#include "nsAutoLock.h"
+
+class nsTimeSampler {
+public:
+ nsTimeSampler();
+ void Reset();
+ void StartTime();
+ void EndTime();
+ void AddTime(PRIntervalTime time);
+ PRIntervalTime LastInterval() { return mLastInterval; }
+ char* PrintStats();
+protected:
+ PRIntervalTime mStartTime;
+ double mSquares;
+ double mTotalTime;
+ uint32_t mCount;
+ PRIntervalTime mLastInterval;
+};
+
+nsTimeSampler::nsTimeSampler()
+{
+ Reset();
+}
+
+void
+nsTimeSampler::Reset()
+{
+ mStartTime = 0;
+ mSquares = 0;
+ mTotalTime = 0;
+ mCount = 0;
+ mLastInterval = 0;
+}
+
+void
+nsTimeSampler::StartTime()
+{
+ mStartTime = PR_IntervalNow();
+}
+
+void
+nsTimeSampler::EndTime()
+{
+ NS_ASSERTION(mStartTime != 0, "Forgot to call StartTime");
+ PRIntervalTime endTime = PR_IntervalNow();
+ mLastInterval = endTime - mStartTime;
+ AddTime(mLastInterval);
+ mStartTime = 0;
+}
+
+void
+nsTimeSampler::AddTime(PRIntervalTime time)
+{
+ nsAutoCMonitor mon(this);
+ mTotalTime += time;
+ mSquares += (double)time * (double)time;
+ mCount++;
+}
+
+char*
+nsTimeSampler::PrintStats()
+{
+ double mean = mTotalTime / mCount;
+ double variance = fabs(mSquares / mCount - mean * mean);
+ double stddev = sqrt(variance);
+ uint32_t imean = (uint32_t)mean;
+ uint32_t istddev = (uint32_t)stddev;
+ return PR_smprintf("%d +/- %d ms",
+ PR_IntervalToMilliseconds(imean),
+ PR_IntervalToMilliseconds(istddev));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsTimeSampler gTimeSampler;
+
+typedef nsresult (*CreateFun)(nsIRunnable* *result,
+ nsIFile* inPath,
+ nsIFile* outPath,
+ uint32_t bufferSize);
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+Copy(nsIInputStream* inStr, nsIOutputStream* outStr,
+ char* buf, uint32_t bufSize, uint32_t *copyCount)
+{
+ nsresult rv;
+ while (true) {
+ uint32_t count;
+ rv = inStr->Read(buf, bufSize, &count);
+ if (NS_FAILED(rv)) return rv;
+ if (count == 0) break;
+
+ uint32_t writeCount;
+ rv = outStr->Write(buf, count, &writeCount);
+ if (NS_FAILED(rv)) return rv;
+ NS_ASSERTION(writeCount == count, "didn't write all the data");
+ *copyCount += writeCount;
+ }
+ rv = outStr->Flush();
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class FileSpecWorker : public nsIRunnable {
+public:
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ PRIntervalTime startTime = PR_IntervalNow();
+ PRIntervalTime endTime;
+ nsCOMPtr<nsIInputStream> inStr;
+ nsCOMPtr<nsIOutputStream> outStr;
+ uint32_t copyCount = 0;
+
+ // Open the input stream:
+ nsCOMPtr<nsIInputStream> fileIn;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileIn), mInPath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_NewBufferedInputStream(getter_AddRefs(inStr), fileIn, 65535);
+ if (NS_FAILED(rv)) return rv;
+
+ // Open the output stream:
+ nsCOMPtr<nsIOutputStream> fileOut;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOut),
+ mOutPath,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(outStr), fileOut, 65535);
+ if (NS_FAILED(rv)) return rv;
+
+ // Copy from one to the other
+ rv = Copy(inStr, outStr, mBuffer, mBufferSize, &copyCount);
+ if (NS_FAILED(rv)) return rv;
+
+ endTime = PR_IntervalNow();
+ gTimeSampler.AddTime(endTime - startTime);
+
+ return rv;
+ }
+
+ NS_DECL_ISUPPORTS
+
+ FileSpecWorker()
+ : mInPath(nullptr), mOutPath(nullptr), mBuffer(nullptr),
+ mBufferSize(0)
+ {
+ }
+
+ nsresult Init(nsIFile* inPath, nsIFile* outPath,
+ uint32_t bufferSize)
+ {
+ mInPath = inPath;
+ mOutPath = outPath;
+ mBuffer = new char[bufferSize];
+ mBufferSize = bufferSize;
+ return (mInPath && mOutPath && mBuffer)
+ ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ static nsresult Create(nsIRunnable* *result,
+ nsIFile* inPath,
+ nsIFile* outPath,
+ uint32_t bufferSize)
+ {
+ FileSpecWorker* worker = new FileSpecWorker();
+ if (worker == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(worker);
+
+ nsresult rv = worker->Init(inPath, outPath, bufferSize);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(worker);
+ return rv;
+ }
+ *result = worker;
+ return NS_OK;
+ }
+
+ virtual ~FileSpecWorker() {
+ delete[] mBuffer;
+ }
+
+protected:
+ nsCOMPtr<nsIFile> mInPath;
+ nsCOMPtr<nsIFile> mOutPath;
+ char* mBuffer;
+ uint32_t mBufferSize;
+};
+
+NS_IMPL_ISUPPORTS(FileSpecWorker, nsIRunnable)
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include "nsIIOService.h"
+#include "nsIChannel.h"
+
+class FileChannelWorker : public nsIRunnable {
+public:
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ PRIntervalTime startTime = PR_IntervalNow();
+ PRIntervalTime endTime;
+ uint32_t copyCount = 0;
+ nsCOMPtr<nsIFileChannel> inCh;
+ nsCOMPtr<nsIFileChannel> outCh;
+ nsCOMPtr<nsIInputStream> inStr;
+ nsCOMPtr<nsIOutputStream> outStr;
+
+ rv = NS_NewLocalFileChannel(getter_AddRefs(inCh), mInPath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = inCh->Open(getter_AddRefs(inStr));
+ if (NS_FAILED(rv)) return rv;
+
+ //rv = NS_NewLocalFileChannel(getter_AddRefs(outCh), mOutPath);
+ //if (NS_FAILED(rv)) return rv;
+
+ //rv = outCh->OpenOutputStream(0, -1, 0, getter_AddRefs(outStr));
+ //if (NS_FAILED(rv)) return rv;
+
+ // Copy from one to the other
+ rv = Copy(inStr, outStr, mBuffer, mBufferSize, &copyCount);
+ if (NS_FAILED(rv)) return rv;
+
+ endTime = PR_IntervalNow();
+ gTimeSampler.AddTime(endTime - startTime);
+
+ return rv;
+ }
+
+ NS_DECL_ISUPPORTS
+
+ FileChannelWorker()
+ : mInPath(nullptr), mOutPath(nullptr), mBuffer(nullptr),
+ mBufferSize(0)
+ {
+ }
+
+ nsresult Init(nsIFile* inPath, nsIFile* outPath,
+ uint32_t bufferSize)
+ {
+ mInPath = inPath;
+ mOutPath = outPath;
+ mBuffer = new char[bufferSize];
+ mBufferSize = bufferSize;
+ return (mInPath && mOutPath && mBuffer)
+ ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ static nsresult Create(nsIRunnable* *result,
+ nsIFile* inPath,
+ nsIFile* outPath,
+ uint32_t bufferSize)
+ {
+ FileChannelWorker* worker = new FileChannelWorker();
+ if (worker == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(worker);
+
+ nsresult rv = worker->Init(inPath, outPath, bufferSize);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(worker);
+ return rv;
+ }
+ *result = worker;
+ return NS_OK;
+ }
+
+ virtual ~FileChannelWorker() {
+ delete[] mBuffer;
+ }
+
+protected:
+ nsCOMPtr<nsIFile> mInPath;
+ nsCOMPtr<nsIFile> mOutPath;
+ char* mBuffer;
+ uint32_t mBufferSize;
+};
+
+NS_IMPL_ISUPPORTS(FileChannelWorker, nsIRunnable)
+
+////////////////////////////////////////////////////////////////////////////////
+
+void
+Test(CreateFun create, uint32_t count,
+ nsIFile* inDirSpec, nsIFile* outDirSpec, uint32_t bufSize)
+{
+ nsresult rv;
+ uint32_t i;
+
+ nsAutoCString inDir;
+ nsAutoCString outDir;
+ (void)inDirSpec->GetNativePath(inDir);
+ (void)outDirSpec->GetNativePath(outDir);
+ printf("###########\nTest: from %s to %s, bufSize = %d\n",
+ inDir.get(), outDir.get(), bufSize);
+ gTimeSampler.Reset();
+ nsTimeSampler testTime;
+ testTime.StartTime();
+
+ nsCOMArray<nsIThread> threads;
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = inDirSpec->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetDirectoryEntries failed");
+
+ i = 0;
+ bool hasMore;
+ while (i < count && NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> next;
+ rv = entries->GetNext(getter_AddRefs(next));
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIFile> inSpec = do_QueryInterface(next, &rv);
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIFile> outSpec;
+ rv = outDirSpec->Clone(getter_AddRefs(outSpec)); // don't munge the original
+ if (NS_FAILED(rv)) goto done;
+
+ nsAutoCString leafName;
+ rv = inSpec->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) goto done;
+
+ rv = outSpec->AppendNative(leafName);
+ if (NS_FAILED(rv)) goto done;
+
+ bool exists;
+ rv = outSpec->Exists(&exists);
+ if (NS_FAILED(rv)) goto done;
+
+ if (exists) {
+ rv = outSpec->Remove(false);
+ if (NS_FAILED(rv)) goto done;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ nsCOMPtr<nsIRunnable> worker;
+ rv = create(getter_AddRefs(worker),
+ inSpec,
+ outSpec,
+ bufSize);
+ if (NS_FAILED(rv)) goto done;
+
+ rv = NS_NewThread(getter_AddRefs(thread), worker, 0, PR_JOINABLE_THREAD);
+ if (NS_FAILED(rv)) goto done;
+
+ bool inserted = threads.InsertObjectAt(thread, i);
+ NS_ASSERTION(inserted, "not inserted");
+
+ i++;
+ }
+
+ uint32_t j;
+ for (j = 0; j < i; j++) {
+ nsIThread* thread = threads.ObjectAt(j);
+ thread->Join();
+ }
+
+ done:
+ NS_ASSERTION(rv == NS_OK, "failed");
+
+ testTime.EndTime();
+ char* testStats = testTime.PrintStats();
+ char* workerStats = gTimeSampler.PrintStats();
+ printf(" threads = %d\n work time = %s,\n test time = %s\n",
+ i, workerStats, testStats);
+ PR_smprintf_free(workerStats);
+ PR_smprintf_free(testStats);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+
+ if (argc < 2) {
+ printf("usage: %s <in-dir> <out-dir>\n", argv[0]);
+ return -1;
+ }
+ char* inDir = argv[1];
+ char* outDir = argv[2];
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ nsCOMPtr<nsIFile> inDirFile;
+ rv = NS_NewNativeLocalFile(nsDependentCString(inDir), false, getter_AddRefs(inDirFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> outDirFile;
+ rv = NS_NewNativeLocalFile(nsDependentCString(outDir), false, getter_AddRefs(outDirFile));
+ if (NS_FAILED(rv)) return rv;
+
+ CreateFun create = FileChannelWorker::Create;
+ Test(create, 1, inDirFile, outDirFile, 16 * 1024);
+#if 1
+ printf("FileChannelWorker *****************************\n");
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+#endif
+ create = FileSpecWorker::Create;
+ printf("FileSpecWorker ********************************\n");
+#if 1
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 16 * 1024);
+#endif
+#if 1
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+ Test(create, 20, inDirFile, outDirFile, 4 * 1024);
+#endif
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/test/TestIDN.cpp b/netwerk/test/TestIDN.cpp
new file mode 100644
index 0000000000..77d57f14d6
--- /dev/null
+++ b/netwerk/test/TestIDN.cpp
@@ -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/. */
+
+#include "TestCommon.h"
+#include <stdio.h>
+#include "nsIIDNService.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsStringAPI.h"
+
+int main(int argc, char **argv) {
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ // Test case from RFC 3492 (7.1 - Simplified Chinese)
+ const char plain[] =
+ "\xE4\xBB\x96\xE4\xBB\xAC\xE4\xB8\xBA\xE4\xBB\x80\xE4\xB9\x88\xE4\xB8\x8D\xE8\xAF\xB4\xE4\xB8\xAD\xE6\x96\x87";
+ const char encoded[] = "xn--ihqwcrb4cv8a8dqg056pqjye";
+
+ nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ NS_ASSERTION(converter, "idnSDK not installed!");
+ if (converter) {
+ nsAutoCString buf;
+ nsresult rv = converter->ConvertUTF8toACE(NS_LITERAL_CSTRING(plain), buf);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error ConvertUTF8toACE");
+ NS_ASSERTION(buf.Equals(NS_LITERAL_CSTRING(encoded)),
+ "encode result incorrect");
+ printf("encoded = %s\n", buf.get());
+
+ buf.Truncate();
+ rv = converter->ConvertACEtoUTF8(NS_LITERAL_CSTRING(encoded), buf);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error ConvertACEtoUTF8");
+ NS_ASSERTION(buf.Equals(NS_LITERAL_CSTRING(plain)),
+ "decode result incorrect");
+ printf("decoded = ");
+ NS_ConvertUTF8toUTF16 utf(buf);
+ const char16_t *u = utf.get();
+ for (int i = 0; u[i]; i++) {
+ printf("U+%.4X ", u[i]);
+ }
+ printf("\n");
+
+ bool isAce;
+ rv = converter->IsACE(NS_LITERAL_CSTRING("www.xn--ihqwcrb4cv8a8dqg056pqjye.com"), &isAce);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error IsACE");
+ NS_ASSERTION(isAce, "IsACE incorrect result");
+ }
+ return 0;
+}
diff --git a/netwerk/test/TestIOThreads.cpp b/netwerk/test/TestIOThreads.cpp
new file mode 100644
index 0000000000..94aade27e3
--- /dev/null
+++ b/netwerk/test/TestIOThreads.cpp
@@ -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/. */
+
+#include "TestCommon.h"
+#include "nsXPCOM.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIEventTarget.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "mozilla/Logging.h"
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+class nsIOEvent : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsIOEvent(int i) : mIndex(i) {}
+
+ NS_IMETHOD Run() override {
+ LOG(("Run [%d]\n", mIndex));
+ return NS_OK;
+ }
+
+private:
+ int mIndex;
+};
+NS_IMPL_ISUPPORTS(nsIOEvent, nsIRunnable)
+
+static nsresult RunTest()
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ for (int i=0; i<10; ++i) {
+ nsCOMPtr<nsIRunnable> event = new nsIOEvent(i);
+ LOG(("Dispatch %d\n", i));
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+int main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ gTestLog = PR_NewLogModule("Test");
+
+ rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = RunTest();
+ if (NS_FAILED(rv))
+ LOG(("RunTest failed [rv=%x]\n", rv));
+
+ LOG(("sleeping main thread for 2 seconds...\n"));
+ PR_Sleep(PR_SecondsToInterval(2));
+
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestIncrementalDownload.cpp b/netwerk/test/TestIncrementalDownload.cpp
new file mode 100644
index 0000000000..19308b2fa9
--- /dev/null
+++ b/netwerk/test/TestIncrementalDownload.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 cin 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 <inttypes.h>
+#include <stdlib.h>
+#include "TestCommon.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIIncrementalDownload.h"
+#include "nsIRequestObserver.h"
+#include "nsIProgressEventSink.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "prprf.h"
+#include "prenv.h"
+#include "mozilla/Attributes.h"
+
+//-----------------------------------------------------------------------------
+
+class FetchObserver final : public nsIRequestObserver
+ , public nsIProgressEventSink
+{
+ ~FetchObserver() = default;
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIPROGRESSEVENTSINK
+};
+
+NS_IMPL_ISUPPORTS(FetchObserver, nsIRequestObserver,
+ nsIProgressEventSink)
+
+NS_IMETHODIMP
+FetchObserver::OnStartRequest(nsIRequest *request, nsISupports *context)
+{
+ printf("FetchObserver::OnStartRequest\n");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FetchObserver::OnProgress(nsIRequest *request, nsISupports *context,
+ int64_t progress, int64_t progressMax)
+{
+ printf("FetchObserver::OnProgress [%" PRId64 "/%" PRId64 "]\n",
+ progress, progressMax);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FetchObserver::OnStatus(nsIRequest *request, nsISupports *context,
+ nsresult status, const char16_t *statusText)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FetchObserver::OnStopRequest(nsIRequest *request, nsISupports *context,
+ nsresult status)
+{
+ printf("FetchObserver::OnStopRequest [status=%x]\n",
+ static_cast<uint32_t>(status));
+
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+static nsresult
+DoIncrementalFetch(const char *uriSpec, const char *resultPath, int32_t chunkSize,
+ int32_t interval)
+{
+ nsCOMPtr<nsIFile> resultFile;
+ nsresult rv = NS_NewNativeLocalFile(nsDependentCString(resultPath),
+ false, getter_AddRefs(resultFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIRequestObserver> observer = new FetchObserver();
+ if (!observer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIIncrementalDownload> download =
+ do_CreateInstance(NS_INCREMENTALDOWNLOAD_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = download->Init(uri, resultFile, chunkSize, interval);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = download->Start(observer, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ PumpEvents();
+ return NS_OK;
+}
+
+int
+main(int argc, char **argv)
+{
+ if (PR_GetEnv("MOZ_BREAK_ON_MAIN"))
+ NS_BREAK();
+
+ if (argc < 5) {
+ fprintf(stderr, "USAGE: TestIncrementalDownload <url> <file> <chunksize> <interval-in-seconds>\n");
+ return -1;
+ }
+
+ nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv))
+ return -1;
+
+ int32_t chunkSize = atoi(argv[3]);
+ int32_t interval = atoi(argv[4]);
+
+ rv = DoIncrementalFetch(argv[1], argv[2], chunkSize, interval);
+ if (NS_FAILED(rv))
+ fprintf(stderr, "ERROR: DoIncrementalFetch failed [%x]\n",
+ static_cast<uint32_t>(rv));
+
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestMakeAbs.cpp b/netwerk/test/TestMakeAbs.cpp
new file mode 100644
index 0000000000..51c21edd91
--- /dev/null
+++ b/netwerk/test/TestMakeAbs.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nspr.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIIOService.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIURI.h"
+
+static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
+
+nsresult ServiceMakeAbsolute(nsIURI *baseURI, char *relativeInfo, char **_retval) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> serv(do_GetService(kIOServiceCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ return serv->MakeAbsolute(relativeInfo, baseURI, _retval);
+}
+
+nsresult URLMakeAbsolute(nsIURI *baseURI, char *relativeInfo, char **_retval) {
+ return baseURI->MakeAbsolute(relativeInfo, _retval);
+}
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv = NS_OK;
+ if (argc < 4) {
+ printf("usage: %s int (loop count) baseURL relativeSpec\n", argv[0]);
+ return -1;
+ }
+
+ uint32_t cycles = atoi(argv[1]);
+ char *base = argv[2];
+ char *rel = argv[3];
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ nsCOMPtr<nsIIOService> serv(do_GetService(kIOServiceCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = serv->NewURI(base, nullptr, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ char *absURLString;
+ uint32_t i = 0;
+ while (i++ < cycles) {
+ rv = ServiceMakeAbsolute(uri, rel, &absURLString);
+ if (NS_FAILED(rv)) return rv;
+ free(absURLString);
+
+ rv = URLMakeAbsolute(uri, rel, &absURLString);
+ free(absURLString);
+ }
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return rv;
+}
diff --git a/netwerk/test/TestNamedPipeService.cpp b/netwerk/test/TestNamedPipeService.cpp
new file mode 100644
index 0000000000..7d3f2b58cb
--- /dev/null
+++ b/netwerk/test/TestNamedPipeService.cpp
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "TestHarness.h"
+
+#include <Windows.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Monitor.h"
+#include "nsINamedPipeService.h"
+#include "nsNetCID.h"
+
+#define PIPE_NAME "\\\\.\\pipe\\TestNPS"
+#define TEST_STR "Hello World"
+
+using namespace mozilla;
+
+class nsNamedPipeDataObserver : 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;
+ Monitor mMonitor;
+};
+
+NS_IMPL_ISUPPORTS(nsNamedPipeDataObserver, nsINamedPipeDataObserver)
+
+nsNamedPipeDataObserver::nsNamedPipeDataObserver(HANDLE aPipe)
+ : mPipe(aPipe)
+ , mOverlapped()
+ , mBytesTransferred(0)
+ , mMonitor("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:
+ {
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Wait();
+ }
+ if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesRead, FALSE)) {
+ fail("GetOverlappedResult failed");
+ return -1;
+ }
+ if (mBytesTransferred != bytesRead) {
+ fail("GetOverlappedResult mismatch");
+ return -1;
+ }
+
+ break;
+ default:
+ fail("ReadFile error %d", GetLastError());
+ return -1;
+ }
+ } else {
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Wait();
+
+ if (mBytesTransferred != bytesRead) {
+ fail("GetOverlappedResult mismatch");
+ return -1;
+ }
+ }
+
+ mBytesTransferred = 0;
+ passed("[read] match");
+ 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:
+ {
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Wait();
+ }
+ if (!GetOverlappedResult(mPipe, &mOverlapped, &bytesWritten, FALSE)) {
+ fail("GetOverlappedResult failed");
+ return -1;
+ }
+ if (mBytesTransferred != bytesWritten) {
+ fail("GetOverlappedResult mismatch");
+ return -1;
+ }
+
+ break;
+ default:
+ fail("WriteFile error %d", GetLastError());
+ return -1;
+ }
+ } else {
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Wait();
+
+ if (mBytesTransferred != bytesWritten) {
+ fail("GetOverlappedResult mismatch");
+ return -1;
+ }
+ }
+
+ mBytesTransferred = 0;
+ passed("[write] match");
+ return bytesWritten;
+}
+
+NS_IMETHODIMP
+nsNamedPipeDataObserver::OnDataAvailable(uint32_t aBytesTransferred,
+ void *aOverlapped)
+{
+ if (aOverlapped != &mOverlapped) {
+ fail("invalid overlapped object");
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD bytesTransferred = 0;
+ BOOL ret = GetOverlappedResult(mPipe,
+ reinterpret_cast<LPOVERLAPPED>(aOverlapped),
+ &bytesTransferred,
+ FALSE);
+
+ if (!ret) {
+ fail("GetOverlappedResult failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bytesTransferred != aBytesTransferred) {
+ fail("GetOverlappedResult mismatch");
+ return NS_ERROR_FAILURE;
+ }
+
+ mBytesTransferred += aBytesTransferred;
+ MonitorAutoLock lock(mMonitor);
+ mMonitor.Notify();
+
+ 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)
+{
+ if (!aPipe) {
+ fail("Parameter aPipe is NULL\n");
+ return FALSE;
+ }
+
+ // FIXME: adjust parameters
+ *aPipe = CreateNamedPipeA(
+ 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) {
+ fail("CreateNamedPipe failed [%d]\n", GetLastError());
+ return FALSE;
+ }
+
+ return ConnectToNewClient(*aPipe, aOverlapped);
+}
+
+BOOL ConnectToNewClient(HANDLE aPipe, LPOVERLAPPED aOverlapped)
+{
+ if (ConnectNamedPipe(aPipe, aOverlapped)) {
+ fail("Unexpected, overlapped ConnectNamedPipe() always returns 0.\n");
+ return FALSE;
+ }
+
+ switch (GetLastError())
+ {
+ case ERROR_IO_PENDING:
+ return TRUE;
+
+ case ERROR_PIPE_CONNECTED:
+ if (SetEvent(aOverlapped->hEvent))
+ break;
+
+ default: // error
+ fail("ConnectNamedPipe failed [%d]\n", 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) {
+ fail("pipe server should be pending");
+ return NS_ERROR_FAILURE;
+ }
+
+ *aClient = CreateFileA(PIPE_NAME,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ nullptr,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED,
+ nullptr);
+
+ if (*aClient == INVALID_HANDLE_VALUE) {
+ fail("Unable to create pipe client");
+ CloseHandle(*aServer);
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD pipeMode = PIPE_READMODE_MESSAGE;
+ if (!SetNamedPipeHandleState(*aClient, &pipeMode, nullptr, nullptr)) {
+ fail("SetNamedPipeHandleState error (%d)", GetLastError());
+ CloseHandle(*aServer);
+ CloseHandle(*aClient);
+ return NS_ERROR_FAILURE;
+ }
+
+ WaitForSingleObjectEx(overlapped.hEvent, INFINITE, TRUE);
+
+ return NS_OK;
+}
+
+int
+main(int32_t argc, char* argv[])
+{
+ ScopedXPCOM xpcom("NamedPipeService");
+ if (xpcom.failed()) {
+ fail("Unable to initalize XPCOM.");
+ return -1;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsINamedPipeService> svc =
+ do_GetService(NS_NAMEDPIPESERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ fail("Unable to create named pipe service");
+ return -1;
+ }
+
+ HANDLE readPipe, writePipe;
+ if (NS_FAILED(rv = CreateNamedPipe(&readPipe, &writePipe))) {
+ fail("Unable to create pipes %d", GetLastError());
+ return -1;
+ }
+
+ RefPtr<nsNamedPipeDataObserver> readObserver =
+ new nsNamedPipeDataObserver(readPipe);
+ RefPtr<nsNamedPipeDataObserver> writeObserver =
+ new nsNamedPipeDataObserver(writePipe);
+
+ if (NS_WARN_IF(NS_FAILED(svc->AddDataObserver(readPipe, readObserver)))) {
+ fail("Unable to add read data observer");
+ return -1;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(svc->AddDataObserver(writePipe, writeObserver)))) {
+ fail("Unable to add read data observer");
+ return -1;
+ }
+
+ if (writeObserver->Write(TEST_STR, sizeof(TEST_STR)) != sizeof(TEST_STR)) {
+ fail("write error");
+ return -1;
+ }
+
+ char buffer[sizeof(TEST_STR)];
+ if (readObserver->Read(buffer, sizeof(buffer)) != sizeof(TEST_STR)) {
+ fail("read error");
+ return -1;
+ }
+
+ if (strcmp(buffer, TEST_STR) != 0) {
+ fail("I/O mismatch");
+ return -1;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(svc->RemoveDataObserver(readPipe, readObserver)))) {
+ fail("Unable to remove read data observer");
+ return -1;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(svc->RemoveDataObserver(writePipe, writeObserver)))) {
+ fail("Unable to remove read data observer");
+ return -1;
+ }
+
+ passed("Finish");
+ return 0;
+} \ No newline at end of file
diff --git a/netwerk/test/TestOpen.cpp b/netwerk/test/TestOpen.cpp
new file mode 100644
index 0000000000..43d518b4e3
--- /dev/null
+++ b/netwerk/test/TestOpen.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsCOMPtr.h"
+#include "nsStringAPI.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Unused.h"
+#include "nsIScriptSecurityManager.h"
+
+#include <stdio.h>
+
+using namespace mozilla;
+
+/*
+ * Test synchronous Open.
+ */
+
+#define RETURN_IF_FAILED(rv, what) \
+ PR_BEGIN_MACRO \
+ if (NS_FAILED(rv)) { \
+ printf(what ": failed - %08x\n", static_cast<uint32_t>(rv)); \
+ return -1; \
+ } \
+ PR_END_MACRO
+
+int
+main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return -1;
+
+ char buf[256];
+
+ if (argc != 3) {
+ printf("Usage: TestOpen url filename\nLoads a URL using ::Open, writing it to a file\n");
+ return -1;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIInputStream> stream;
+
+ rv = NS_NewURI(getter_AddRefs(uri), argv[1]);
+ RETURN_IF_FAILED(rv, "NS_NewURI");
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ RETURN_IF_FAILED(rv, "Couldn't get script security manager!");
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ RETURN_IF_FAILED(rv, "Couldn't get system principal!");
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ RETURN_IF_FAILED(rv, "NS_NewChannel");
+
+ rv = channel->Open2(getter_AddRefs(stream));
+ RETURN_IF_FAILED(rv, "channel->Open2()");
+
+ FILE* outfile = fopen(argv[2], "wb");
+ if (!outfile) {
+ printf("error opening %s\n", argv[2]);
+ return 1;
+ }
+
+ uint32_t read;
+ while (NS_SUCCEEDED(stream->Read(buf, sizeof(buf), &read)) && read) {
+ Unused << fwrite(buf, 1, read, outfile);
+ }
+ printf("Done\n");
+
+ fclose(outfile);
+
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestOverlappedIO.cpp b/netwerk/test/TestOverlappedIO.cpp
new file mode 100644
index 0000000000..db954b4f62
--- /dev/null
+++ b/netwerk/test/TestOverlappedIO.cpp
@@ -0,0 +1,313 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+#include <signal.h>
+#include <algorithm>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "nspr.h"
+#include "nscore.h"
+#include "nsISocketTransportService.h"
+#include "nsIEventQueueService.h"
+#include "nsIServiceManager.h"
+#include "nsITransport.h"
+#include "nsIRequest.h"
+#include "nsIStreamProvider.h"
+#include "nsIStreamListener.h"
+#include "nsIPipe.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsIByteArrayInputStream.h"
+
+static PRLogModuleInfo *gTestSocketIOLog;
+#define LOG(args) MOZ_LOG(gTestSocketIOLog, mozilla::LogLevel::Debug, args)
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
+
+static PRTime gElapsedTime;
+static int gKeepRunning = 1;
+static nsIEventQueue* gEventQ = nullptr;
+
+//
+//----------------------------------------------------------------------------
+// Test Listener
+//----------------------------------------------------------------------------
+//
+
+class TestListener : public nsIStreamListener
+{
+public:
+ TestListener() { }
+ virtual ~TestListener() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+};
+
+NS_IMPL_ISUPPORTS(TestListener,
+ nsIRequestObserver,
+ nsIStreamListener);
+
+NS_IMETHODIMP
+TestListener::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ LOG(("TestListener::OnStartRequest\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestListener::OnDataAvailable(nsIRequest* request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ LOG(("TestListener::OnDataAvailable [offset=%llu length=%u]\n",
+ aSourceOffset, aLength));
+ char buf[1025];
+ uint32_t amt;
+ while (1) {
+ aIStream->Read(buf, 1024, &amt);
+ if (amt == 0)
+ break;
+ buf[amt] = '\0';
+ //puts(buf);
+ printf("read %d bytes\n", amt);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestListener::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("TestListener::OnStopRequest [aStatus=%x]\n", aStatus));
+ //gKeepRunning = 0;
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Test Provider
+//----------------------------------------------------------------------------
+//
+
+class TestProvider : public nsIStreamProvider
+{
+public:
+ TestProvider(char *data);
+ virtual ~TestProvider();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMPROVIDER
+
+protected:
+ char *mData;
+ uint32_t mOffset;
+ uint32_t mDataLen;
+ uint32_t mRequestCount;
+};
+
+NS_IMPL_ISUPPORTS(TestProvider,
+ nsIStreamProvider,
+ nsIRequestObserver)
+
+TestProvider::TestProvider(char *data)
+{
+ mData = data;
+ mDataLen = strlen(data);
+ mOffset = 0;
+ mRequestCount = 0;
+ LOG(("Constructing TestProvider [this=%p]\n", this));
+}
+
+TestProvider::~TestProvider()
+{
+ LOG(("Destroying TestProvider [this=%p]\n", this));
+}
+
+NS_IMETHODIMP
+TestProvider::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ LOG(("TestProvider::OnStartRequest [this=%p]\n", this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestProvider::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("TestProvider::OnStopRequest [status=%x]\n", aStatus));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestProvider::OnDataWritable(nsIRequest *request, nsISupports *context,
+ nsIOutputStream *output, uint32_t offset, uint32_t count)
+{
+ LOG(("TestProvider::OnDataWritable [offset=%u, count=%u]\n", offset, count));
+
+ // Stop at 5 requests
+ if (mRequestCount == 5)
+ return NS_BASE_STREAM_CLOSED;
+
+ uint32_t writeCount, amount;
+ amount = std::min(count, mDataLen - mOffset);
+ nsresult rv = output->Write(mData + mOffset, amount, &writeCount);
+ if (NS_SUCCEEDED(rv)) {
+ printf("wrote %u bytes\n", writeCount);
+ mOffset += writeCount;
+ if (mOffset == mDataLen) {
+ printf("done sending packet %u\n", mRequestCount);
+ mOffset = 0;
+ mRequestCount++;
+ }
+ }
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Synchronous IO
+//----------------------------------------------------------------------------
+//
+nsresult
+WriteRequest(nsIOutputStream *os, const char *request)
+{
+ LOG(("WriteRequest [request=%s]\n", request));
+ uint32_t n;
+ return os->Write(request, strlen(request), &n);
+}
+
+nsresult
+ReadResponse(nsIInputStream *is)
+{
+ uint32_t bytesRead;
+ char buf[2048];
+ do {
+ is->Read(buf, sizeof(buf), &bytesRead);
+ if (bytesRead > 0)
+ fwrite(buf, 1, bytesRead, stdout);
+ } while (bytesRead > 0);
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Startup...
+//----------------------------------------------------------------------------
+//
+
+void
+sighandler(int sig)
+{
+ LOG(("got signal: %d\n", sig));
+ NS_BREAK();
+}
+
+void
+usage(char **argv)
+{
+ printf("usage: %s <host> <path>\n", argv[0]);
+ exit(1);
+}
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+
+ signal(SIGSEGV, sighandler);
+
+ gTestSocketIOLog = PR_NewLogModule("TestSocketIO");
+
+ if (argc < 3)
+ usage(argv);
+
+ char *hostName = argv[1];
+ char *fileName = argv[2];
+ int port = 80;
+
+ // Create the Event Queue for this thread...
+ nsCOMPtr<nsIEventQueueService> eventQService =
+ do_GetService(kEventQueueServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: event queue service!");
+ return rv;
+ }
+
+ rv = eventQService->CreateMonitoredThreadEventQueue();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: thread event queue!");
+ return rv;
+ }
+
+ eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, &gEventQ);
+
+ // Create the Socket transport service...
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: socket transport service!");
+ return rv;
+ }
+
+ char *buffer = PR_smprintf("GET %s HTTP/1.1" CRLF
+ "host: %s" CRLF
+ "user-agent: Mozilla/5.0 (X11; N; Linux 2.2.16-22smp i686; en-US; m18) Gecko/20001220" CRLF
+ "accept: */*" CRLF
+ "accept-language: en" CRLF
+ "accept-encoding: gzip,deflate,compress,identity" CRLF
+ "keep-alive: 300" CRLF
+ "connection: keep-alive" CRLF
+ CRLF,
+ fileName, hostName);
+ LOG(("Request [\n%s]\n", buffer));
+
+ // Create the socket transport...
+ nsCOMPtr<nsITransport> transport;
+ rv = sts->CreateTransport(hostName, port, nullptr, -1, 0, 0, getter_AddRefs(transport));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: socket transport!");
+ return rv;
+ }
+
+ gElapsedTime = PR_Now();
+
+ nsCOMPtr<nsIRequest> writeRequest, readRequest;
+
+ rv = transport->AsyncWrite(new TestProvider(buffer), nullptr, 0, 0, 0, getter_AddRefs(writeRequest));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed calling: AsyncWrite!");
+ return rv;
+ }
+ rv = transport->AsyncRead(new TestListener(), nullptr, 0, 0, 0, getter_AddRefs(readRequest));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed calling: AsyncWrite!");
+ return rv;
+ }
+
+ // Enter the message pump
+ while ( gKeepRunning ) {
+ PLEvent *gEvent;
+ gEventQ->WaitForEvent(&gEvent);
+ gEventQ->HandleEvent(gEvent);
+ }
+
+ PRTime endTime;
+ endTime = PR_Now();
+ LOG(("Elapsed time: %d\n", (int32_t)(endTime/1000UL - gElapsedTime/1000UL)));
+
+ sts->Shutdown();
+ return 0;
+}
diff --git a/netwerk/test/TestProtocols.cpp b/netwerk/test/TestProtocols.cpp
new file mode 100644
index 0000000000..7bec14f856
--- /dev/null
+++ b/netwerk/test/TestProtocols.cpp
@@ -0,0 +1,890 @@
+/* -*- 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/. */
+
+/*
+ The TestProtocols tests the basic protocols architecture and can
+ be used to test individual protocols as well. If this grows too
+ big then we should split it to individual protocols.
+
+ -Gagan Saksena 04/29/99
+*/
+
+#include "TestCommon.h"
+#include <algorithm>
+
+#include <stdio.h>
+#ifdef WIN32
+#include <windows.h>
+#endif
+#ifdef XP_UNIX
+#include <unistd.h>
+#endif
+#include "nspr.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIIOService.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIInputStream.h"
+#include "nsCRT.h"
+#include "nsIChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIURL.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDNSService.h"
+#include "nsIAuthPrompt.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPropertyBag2.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsITimedChannel.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+#include "nsIScriptSecurityManager.h"
+
+#include "nsISimpleEnumerator.h"
+#include "nsStringAPI.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "NetwerkTestLogging.h"
+
+using namespace mozilla;
+
+namespace TestProtocols {
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
+
+//static PRTime gElapsedTime; // enable when we time it...
+static int gKeepRunning = 0;
+static bool gVerbose = false;
+static bool gAskUserForInput = false;
+static bool gResume = false;
+static uint64_t gStartAt = 0;
+
+static const char* gEntityID;
+
+//-----------------------------------------------------------------------------
+// Set proxy preferences for testing
+//-----------------------------------------------------------------------------
+
+static nsresult
+SetHttpProxy(const char *proxy)
+{
+ const char *colon = strchr(proxy, ':');
+ if (!colon)
+ {
+ NS_WARNING("invalid proxy token; use host:port");
+ return NS_ERROR_UNEXPECTED;
+ }
+ int port = atoi(colon + 1);
+ if (port == 0)
+ {
+ NS_WARNING("invalid proxy port; must be an integer");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsAutoCString proxyHost;
+ proxyHost = Substring(proxy, colon);
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs)
+ {
+ prefs->SetCharPref("network.proxy.http", proxyHost.get());
+ prefs->SetIntPref("network.proxy.http_port", port);
+ prefs->SetIntPref("network.proxy.type", 1); // manual proxy config
+ }
+ LOG(("connecting via proxy=%s:%d\n", proxyHost.get(), port));
+ return NS_OK;
+}
+
+static nsresult
+SetPACFile(const char* pacURL)
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs)
+ {
+ prefs->SetCharPref("network.proxy.autoconfig_url", pacURL);
+ prefs->SetIntPref("network.proxy.type", 2); // PAC file
+ }
+ LOG(("connecting using PAC file %s\n", pacURL));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Timing information
+//-----------------------------------------------------------------------------
+
+void PrintTimingInformation(nsITimedChannel* channel) {
+#define PRINT_VALUE(property) \
+ { \
+ PRTime value; \
+ channel->Get##property(&value); \
+ if (value) { \
+ PRExplodedTime exploded; \
+ PR_ExplodeTime(value, PR_LocalTimeParameters, &exploded); \
+ char buf[256]; \
+ PR_FormatTime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &exploded); \
+ LOG((" " #property ":\t%s (%i usec)", buf, exploded.tm_usec)); \
+ } else { \
+ LOG((" " #property ":\t0")); \
+ } \
+ }
+ LOG(("Timing data:"));
+ PRINT_VALUE(ChannelCreationTime)
+ PRINT_VALUE(AsyncOpenTime)
+ PRINT_VALUE(DomainLookupStartTime)
+ PRINT_VALUE(DomainLookupEndTime)
+ PRINT_VALUE(ConnectStartTime)
+ PRINT_VALUE(ConnectEndTime)
+ PRINT_VALUE(RequestStartTime)
+ PRINT_VALUE(ResponseStartTime)
+ PRINT_VALUE(ResponseEndTime)
+ PRINT_VALUE(CacheReadStartTime)
+ PRINT_VALUE(CacheReadEndTime)
+}
+
+//-----------------------------------------------------------------------------
+// HeaderVisitor
+//-----------------------------------------------------------------------------
+
+class HeaderVisitor : public nsIHttpHeaderVisitor
+{
+ virtual ~HeaderVisitor() = default;
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPHEADERVISITOR
+
+ HeaderVisitor() { }
+};
+NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor)
+
+NS_IMETHODIMP
+HeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value)
+{
+ LOG((" %s: %s\n",
+ PromiseFlatCString(header).get(),
+ PromiseFlatCString(value).get()));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// URLLoadInfo
+//-----------------------------------------------------------------------------
+
+class URLLoadInfo : public nsISupports
+{
+ virtual ~URLLoadInfo();
+
+public:
+
+ explicit URLLoadInfo(const char* aUrl);
+
+ // ISupports interface...
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ const char* Name() { return mURLString.get(); }
+ int64_t mBytesRead;
+ PRTime mTotalTime;
+ PRTime mConnectTime;
+ nsCString mURLString;
+};
+
+URLLoadInfo::URLLoadInfo(const char *aUrl) : mURLString(aUrl)
+{
+ mBytesRead = 0;
+ mConnectTime = mTotalTime = PR_Now();
+}
+
+URLLoadInfo::~URLLoadInfo() = default;
+
+
+NS_IMPL_ISUPPORTS0(URLLoadInfo)
+
+//-----------------------------------------------------------------------------
+// TestChannelEventSink
+//-----------------------------------------------------------------------------
+
+class TestChannelEventSink : public nsIChannelEventSink
+{
+ virtual ~TestChannelEventSink();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICHANNELEVENTSINK
+
+ TestChannelEventSink();
+};
+
+TestChannelEventSink::TestChannelEventSink()
+{
+}
+
+TestChannelEventSink::~TestChannelEventSink() = default;
+
+
+NS_IMPL_ISUPPORTS(TestChannelEventSink, nsIChannelEventSink)
+
+NS_IMETHODIMP
+TestChannelEventSink::AsyncOnChannelRedirect(nsIChannel *channel,
+ nsIChannel *newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ LOG(("\n+++ TestChannelEventSink::OnChannelRedirect (with flags %x) +++\n",
+ flags));
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TestAuthPrompt
+//-----------------------------------------------------------------------------
+
+class TestAuthPrompt : public nsIAuthPrompt
+{
+ virtual ~TestAuthPrompt();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTHPROMPT
+
+ TestAuthPrompt();
+};
+
+NS_IMPL_ISUPPORTS(TestAuthPrompt, nsIAuthPrompt)
+
+TestAuthPrompt::TestAuthPrompt()
+{
+}
+
+TestAuthPrompt::~TestAuthPrompt() = default;
+
+NS_IMETHODIMP
+TestAuthPrompt::Prompt(const char16_t *dialogTitle,
+ const char16_t *text,
+ const char16_t *passwordRealm,
+ uint32_t savePassword,
+ const char16_t *defaultText,
+ char16_t **result,
+ bool *_retval)
+{
+ *_retval = false;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TestAuthPrompt::PromptUsernameAndPassword(const char16_t *dialogTitle,
+ const char16_t *dialogText,
+ const char16_t *passwordRealm,
+ uint32_t savePassword,
+ char16_t **user,
+ char16_t **pwd,
+ bool *_retval)
+{
+ NS_ConvertUTF16toUTF8 text(passwordRealm);
+ printf("* --------------------------------------------------------------------------- *\n");
+ printf("* Authentication Required [%s]\n", text.get());
+ printf("* --------------------------------------------------------------------------- *\n");
+
+ char buf[256];
+ int n;
+
+ printf("Enter username: ");
+ Unused << fgets(buf, sizeof(buf), stdin);
+ n = strlen(buf);
+ buf[n-1] = '\0'; // trim trailing newline
+ *user = NS_StringCloneData(NS_ConvertUTF8toUTF16(buf));
+
+ const char *p;
+#if defined(XP_UNIX) && !defined(ANDROID)
+ p = getpass("Enter password: ");
+#else
+ printf("Enter password: ");
+ fgets(buf, sizeof(buf), stdin);
+ n = strlen(buf);
+ buf[n-1] = '\0'; // trim trailing newline
+ p = buf;
+#endif
+ *pwd = NS_StringCloneData(NS_ConvertUTF8toUTF16(p));
+
+ // zap buf
+ memset(buf, 0, sizeof(buf));
+
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestAuthPrompt::PromptPassword(const char16_t *dialogTitle,
+ const char16_t *text,
+ const char16_t *passwordRealm,
+ uint32_t savePassword,
+ char16_t **pwd,
+ bool *_retval)
+{
+ *_retval = false;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// InputTestConsumer
+//-----------------------------------------------------------------------------
+
+class InputTestConsumer : public nsIStreamListener
+{
+ virtual ~InputTestConsumer();
+
+public:
+
+ explicit InputTestConsumer(URLLoadInfo* aURLLoadInfo);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+private:
+ URLLoadInfo* mURLLoadInfo;
+};
+
+InputTestConsumer::InputTestConsumer(URLLoadInfo* aURLLoadInfo)
+: mURLLoadInfo(aURLLoadInfo)
+{
+ NS_IF_ADDREF(mURLLoadInfo);
+}
+
+InputTestConsumer::~InputTestConsumer()
+{
+ NS_RELEASE(mURLLoadInfo);
+}
+
+NS_IMPL_ISUPPORTS(InputTestConsumer, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
+{
+ LOG(("InputTestConsumer::OnStartRequest\n"));
+
+ NS_ASSERTION(!context, "context needs to be null when calling asyncOpen2");
+
+ if (mURLLoadInfo)
+ mURLLoadInfo->mConnectTime = PR_Now() - mURLLoadInfo->mConnectTime;
+
+ if (gVerbose) {
+ LOG(("\nStarted loading: %s\n", mURLLoadInfo ? mURLLoadInfo->Name() : "UNKNOWN URL"));
+ }
+
+ nsAutoCString value;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsresult status;
+ channel->GetStatus(&status);
+ LOG(("Channel Status: %08x\n", status));
+ if (NS_SUCCEEDED(status)) {
+ LOG(("Channel Info:\n"));
+
+ channel->GetName(value);
+ LOG(("\tName: %s\n", value.get()));
+
+ channel->GetContentType(value);
+ LOG(("\tContent-Type: %s\n", value.get()));
+
+ channel->GetContentCharset(value);
+ LOG(("\tContent-Charset: %s\n", value.get()));
+
+ int64_t length = -1;
+ if (NS_SUCCEEDED(channel->GetContentLength(&length))) {
+ LOG(("\tContent-Length: %lld\n", length));
+ } else {
+ LOG(("\tContent-Length: Unknown\n"));
+ }
+ }
+
+ nsCOMPtr<nsISupports> owner;
+ channel->GetOwner(getter_AddRefs(owner));
+ LOG(("\tChannel Owner: %x\n", owner.get()));
+ }
+
+ nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(request);
+ if (props) {
+ nsCOMPtr<nsIURI> foo;
+ props->GetPropertyAsInterface(NS_LITERAL_STRING("test.foo"),
+ NS_GET_IID(nsIURI),
+ getter_AddRefs(foo));
+ if (foo) {
+ LOG(("\ttest.foo: %s\n", foo->GetSpecOrDefault().get()));
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInt(do_QueryInterface(request));
+ if (httpChannelInt) {
+ uint32_t majorVer, minorVer;
+ nsresult rv = httpChannelInt->GetResponseVersion(&majorVer, &minorVer);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("HTTP Response version: %u.%u\n", majorVer, minorVer));
+ }
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
+ if (httpChannel) {
+ auto *visitor = new HeaderVisitor();
+ if (!visitor)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(visitor);
+
+ LOG(("HTTP request headers:\n"));
+ httpChannel->VisitRequestHeaders(visitor);
+
+ LOG(("HTTP response headers:\n"));
+ httpChannel->VisitResponseHeaders(visitor);
+
+ NS_RELEASE(visitor);
+ }
+
+ nsCOMPtr<nsIResumableChannel> resChannel = do_QueryInterface(request);
+ if (resChannel) {
+ LOG(("Resumable entity identification:\n"));
+ nsAutoCString entityID;
+ nsresult rv = resChannel->GetEntityID(entityID);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("\t|%s|\n", entityID.get()));
+ }
+ else {
+ LOG(("\t<none>\n"));
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputTestConsumer::OnDataAvailable(nsIRequest *request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ NS_ASSERTION(!context, "context needs to be null when calling asyncOpen2");
+
+ char buf[1025];
+ uint32_t amt, size;
+ nsresult rv;
+
+ while (aLength) {
+ size = std::min<uint32_t>(aLength, sizeof(buf));
+
+ rv = aIStream->Read(buf, size, &amt);
+ if (NS_FAILED(rv)) {
+ NS_ASSERTION((NS_BASE_STREAM_WOULD_BLOCK != rv),
+ "The stream should never block.");
+ return rv;
+ }
+ if (gVerbose) {
+ buf[amt] = '\0';
+ puts(buf);
+ }
+ if (mURLLoadInfo) {
+ mURLLoadInfo->mBytesRead += amt;
+ }
+
+ aLength -= amt;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputTestConsumer::OnStopRequest(nsIRequest *request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("InputTestConsumer::OnStopRequest [status=%x]\n", aStatus));
+
+ if (mURLLoadInfo) {
+ uint32_t httpStatus;
+ bool bHTTPURL = false;
+
+ mURLLoadInfo->mTotalTime = PR_Now() - mURLLoadInfo->mTotalTime;
+
+ double readTime = ((mURLLoadInfo->mTotalTime-mURLLoadInfo->mConnectTime)/1000.0)/1000.0;
+
+ nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(request));
+ if (pHTTPCon) {
+ pHTTPCon->GetResponseStatus(&httpStatus);
+ bHTTPURL = true;
+ }
+
+ LOG(("\nFinished loading: %s Status Code: %x\n", mURLLoadInfo->Name(), aStatus));
+ if (bHTTPURL) {
+ LOG(("\tHTTP Status: %u\n", httpStatus));
+ }
+ if (NS_ERROR_UNKNOWN_HOST == aStatus ||
+ NS_ERROR_UNKNOWN_PROXY_HOST == aStatus) {
+ LOG(("\tDNS lookup failed.\n"));
+ }
+ LOG(("\tTime to connect: %.3f seconds\n", (mURLLoadInfo->mConnectTime/1000.0)/1000.0));
+ LOG(("\tTime to read: %.3f seconds.\n", readTime));
+ LOG(("\tRead: %lld bytes.\n", mURLLoadInfo->mBytesRead));
+ if (mURLLoadInfo->mBytesRead == int64_t(0)) {
+ } else if (readTime > 0.0) {
+ LOG(("\tThroughput: %.0f bps.\n", (double)(mURLLoadInfo->mBytesRead*int64_t(8))/readTime));
+ } else {
+ LOG(("\tThroughput: REAL FAST!!\n"));
+ }
+
+ nsCOMPtr<nsITimedChannel> timed(do_QueryInterface(request));
+ if (timed)
+ PrintTimingInformation(timed);
+ } else {
+ LOG(("\nFinished loading: UNKNOWN URL. Status Code: %x\n", aStatus));
+ }
+
+ if (--gKeepRunning == 0)
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// NotificationCallbacks
+//-----------------------------------------------------------------------------
+
+class NotificationCallbacks final : public nsIInterfaceRequestor {
+
+ ~NotificationCallbacks() = default;
+
+public:
+ NS_DECL_ISUPPORTS
+
+ NotificationCallbacks() {
+ }
+
+ NS_IMETHOD GetInterface(const nsIID& iid, void* *result) override {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ TestChannelEventSink *sink;
+
+ sink = new TestChannelEventSink();
+ if (sink == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(sink);
+ rv = sink->QueryInterface(iid, result);
+ NS_RELEASE(sink);
+ }
+
+ if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ TestAuthPrompt *prompt;
+
+ prompt = new TestAuthPrompt();
+ if (prompt == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(prompt);
+ rv = prompt->QueryInterface(iid, result);
+ NS_RELEASE(prompt);
+ }
+ return rv;
+ }
+};
+
+NS_IMPL_ISUPPORTS(NotificationCallbacks, nsIInterfaceRequestor)
+
+//-----------------------------------------------------------------------------
+// helpers...
+//-----------------------------------------------------------------------------
+
+nsresult StartLoadingURL(const char* aUrlString)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> pService(do_GetService(kIOServiceCID, &rv));
+ if (pService) {
+ nsCOMPtr<nsIURI> pURL;
+
+ rv = pService->NewURI(nsDependentCString(aUrlString), nullptr, nullptr, getter_AddRefs(pURL));
+ if (NS_FAILED(rv)) {
+ LOG(("ERROR: NewURI failed for %s [rv=%x]\n", aUrlString));
+ return rv;
+ }
+ nsCOMPtr<nsIChannel> pChannel;
+
+ auto* callbacks = new NotificationCallbacks();
+ if (!callbacks) {
+ LOG(("Failed to create a new consumer!"));
+ return NS_ERROR_OUT_OF_MEMORY;;
+ }
+ NS_ADDREF(callbacks);
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Async reading thru the calls of the event sink interface
+ rv = NS_NewChannel(getter_AddRefs(pChannel),
+ pURL,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // loadGroup
+ callbacks,
+ nsIRequest::LOAD_NORMAL,
+ pService);
+
+ NS_RELEASE(callbacks);
+ if (NS_FAILED(rv)) {
+ LOG(("ERROR: NS_NewChannel failed for %s [rv=%x]\n", aUrlString, rv));
+ return rv;
+ }
+
+ nsCOMPtr<nsITimedChannel> timed(do_QueryInterface(pChannel));
+ if (timed)
+ timed->SetTimingEnabled(true);
+
+ nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(pChannel);
+ if (props) {
+ rv = props->SetPropertyAsInterface(NS_LITERAL_STRING("test.foo"),
+ pURL);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("set prop 'test.foo'\n"));
+ }
+ }
+
+ /*
+ You may optionally add/set other headers on this
+ request object. This is done by QI for the specific
+ protocolConnection.
+ */
+ nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(pChannel));
+
+ if (pHTTPCon) {
+ // Setting a sample header.
+ rv = pHTTPCon->SetRequestHeader(NS_LITERAL_CSTRING("sample-header"),
+ NS_LITERAL_CSTRING("Sample-Value"),
+ false);
+ if (NS_FAILED(rv)) return rv;
+ }
+ auto* info = new URLLoadInfo(aUrlString);
+ if (!info) {
+ NS_ERROR("Failed to create a load info!");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ auto* listener = new InputTestConsumer(info);
+ NS_IF_ADDREF(listener);
+ if (!listener) {
+ NS_ERROR("Failed to create a new stream listener!");
+ return NS_ERROR_OUT_OF_MEMORY;;
+ }
+
+
+ if (gResume) {
+ nsCOMPtr<nsIResumableChannel> res = do_QueryInterface(pChannel);
+ if (!res) {
+ NS_ERROR("Channel is not resumable!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsAutoCString id;
+ if (gEntityID)
+ id = gEntityID;
+ LOG(("* resuming at %llu bytes, with entity id |%s|\n", gStartAt, id.get()));
+ res->ResumeAt(gStartAt, id);
+ }
+ rv = pChannel->AsyncOpen2(listener);
+
+ if (NS_SUCCEEDED(rv)) {
+ gKeepRunning++;
+ }
+ else {
+ LOG(("ERROR: AsyncOpen failed [rv=%x]\n", rv));
+ }
+ NS_RELEASE(listener);
+ }
+
+ return rv;
+}
+
+static int32_t
+FindChar(nsCString& buffer, char c)
+{
+ const char *b;
+ int32_t len = NS_CStringGetData(buffer, &b);
+
+ for (int32_t offset = 0; offset < len; ++offset) {
+ if (b[offset] == c)
+ return offset;
+ }
+
+ return -1;
+}
+
+
+static void
+StripChar(nsCString& buffer, char c)
+{
+ const char *b;
+ uint32_t len = NS_CStringGetData(buffer, &b) - 1;
+
+ for (; len > 0; --len) {
+ if (b[len] == c) {
+ buffer.Cut(len, 1);
+ NS_CStringGetData(buffer, &b);
+ }
+ }
+}
+
+nsresult LoadURLsFromFile(char *aFileName)
+{
+ nsresult rv = NS_OK;
+ int32_t len, offset;
+ PRFileDesc* fd;
+ char buffer[1024];
+ nsCString fileBuffer;
+ nsCString urlString;
+
+ fd = PR_Open(aFileName, PR_RDONLY, 777);
+ if (!fd) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Keep reading the file until EOF (or an error) is reached...
+ do {
+ len = PR_Read(fd, buffer, sizeof(buffer));
+ if (len>0) {
+ fileBuffer.Append(buffer, len);
+ // Treat each line as a URL...
+ while ((offset = FindChar(fileBuffer, '\n')) != -1) {
+ urlString = StringHead(fileBuffer, offset);
+ fileBuffer.Cut(0, offset+1);
+
+ StripChar(urlString, '\r');
+ if (urlString.Length()) {
+ LOG(("\t%s\n", urlString.get()));
+ rv = StartLoadingURL(urlString.get());
+ if (NS_FAILED(rv)) {
+ // No need to log an error -- StartLoadingURL already
+ // did that for us, probably.
+ PR_Close(fd);
+ return rv;
+ }
+ }
+ }
+ }
+ } while (len>0);
+
+ // If anything is left in the fileBuffer, treat it as a URL...
+ StripChar(fileBuffer, '\r');
+ if (fileBuffer.Length()) {
+ LOG(("\t%s\n", fileBuffer.get()));
+ StartLoadingURL(fileBuffer.get());
+ }
+
+ PR_Close(fd);
+ return NS_OK;
+}
+
+
+nsresult LoadURLFromConsole()
+{
+ char buffer[1024];
+ printf(R"(Enter URL ("q" to start): )");
+ Unused << scanf("%s", buffer);
+ if (buffer[0]=='q')
+ gAskUserForInput = false;
+ else
+ StartLoadingURL(buffer);
+ return NS_OK;
+}
+
+} // namespace TestProtocols
+
+using namespace TestProtocols;
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv= (nsresult)-1;
+ if (argc < 2) {
+ printf("usage: %s [-verbose] [-file <name>] [-resume <startoffset>"
+ "[-entityid <entityid>]] [-proxy <proxy>] [-pac <pacURL>]"
+ "[-console] <url> <url> ... \n", argv[0]);
+ return -1;
+ }
+
+ gTestLog = PR_NewLogModule("Test");
+
+ /*
+ The following code only deals with XPCOM registration stuff. and setting
+ up the event queues. Copied from TestSocketIO.cpp
+ */
+
+ rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return -1;
+
+ {
+ int i;
+ LOG(("Trying to load:\n"));
+ for (i=1; i<argc; i++) {
+ // Turn on verbose printing...
+ if (PL_strcasecmp(argv[i], "-verbose") == 0) {
+ gVerbose = true;
+ continue;
+ }
+
+ // Turn on netlib tracing...
+ if (PL_strcasecmp(argv[i], "-file") == 0) {
+ LoadURLsFromFile(argv[++i]);
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-console") == 0) {
+ gAskUserForInput = true;
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-resume") == 0) {
+ gResume = true;
+ PR_sscanf(argv[++i], "%llu", &gStartAt);
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-entityid") == 0) {
+ gEntityID = argv[++i];
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-proxy") == 0) {
+ SetHttpProxy(argv[++i]);
+ continue;
+ }
+
+ if (PL_strcasecmp(argv[i], "-pac") == 0) {
+ SetPACFile(argv[++i]);
+ continue;
+ }
+
+ LOG(("\t%s\n", argv[i]));
+ rv = StartLoadingURL(argv[i]);
+ }
+ // Enter the message pump to allow the URL load to proceed.
+ PumpEvents();
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return NS_FAILED(rv) ? -1 : 0;
+}
diff --git a/netwerk/test/TestServ.cpp b/netwerk/test/TestServ.cpp
new file mode 100644
index 0000000000..7ae41636f5
--- /dev/null
+++ b/netwerk/test/TestServ.cpp
@@ -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 "TestCommon.h"
+#include <stdlib.h>
+#include "nsIServiceManager.h"
+#include "nsIServerSocket.h"
+#include "nsISocketTransport.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsNetCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStringAPI.h"
+#include "nsCOMPtr.h"
+#include "NetwerkTestLogging.h"
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+class MySocketListener : public nsIServerSocketListener
+{
+protected:
+ virtual ~MySocketListener() = default;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ MySocketListener() {}
+};
+
+NS_IMPL_ISUPPORTS(MySocketListener, nsIServerSocketListener)
+
+NS_IMETHODIMP
+MySocketListener::OnSocketAccepted(nsIServerSocket *serv,
+ nsISocketTransport *trans)
+{
+ LOG(("MySocketListener::OnSocketAccepted [serv=%p trans=%p]\n", serv, trans));
+
+ nsAutoCString host;
+ int32_t port;
+
+ trans->GetHost(host);
+ trans->GetPort(&port);
+
+ LOG((" -> %s:%d\n", host.get(), port));
+
+ nsCOMPtr<nsIInputStream> input;
+ nsCOMPtr<nsIOutputStream> output;
+ nsresult rv;
+
+ rv = trans->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(input));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = trans->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(output));
+ if (NS_FAILED(rv))
+ return rv;
+
+ char buf[256];
+ uint32_t n;
+
+ rv = input->Read(buf, sizeof(buf), &n);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const char response[] = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nFooooopy!!\r\n";
+ rv = output->Write(response, sizeof(response) - 1, &n);
+ if (NS_FAILED(rv))
+ return rv;
+
+ input->Close();
+ output->Close();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MySocketListener::OnStopListening(nsIServerSocket *serv, nsresult status)
+{
+ LOG(("MySocketListener::OnStopListening [serv=%p status=%x]\n", serv, status));
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+static nsresult
+MakeServer(int32_t port)
+{
+ nsresult rv;
+ nsCOMPtr<nsIServerSocket> serv = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = serv->Init(port, true, 5);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = serv->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+ LOG((" listening on port %d\n", port));
+
+ rv = serv->AsyncListen(new MySocketListener());
+ return rv;
+}
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv= (nsresult)-1;
+ if (argc < 2) {
+ printf("usage: %s <port>\n", argv[0]);
+ return -1;
+ }
+
+ gTestLog = PR_NewLogModule("Test");
+
+ /*
+ * The following code only deals with XPCOM registration stuff. and setting
+ * up the event queues. Copied from TestSocketIO.cpp
+ */
+
+ rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return -1;
+
+ {
+ rv = MakeServer(atoi(argv[1]));
+ if (NS_FAILED(rv)) {
+ LOG(("MakeServer failed [rv=%x]\n", rv));
+ return -1;
+ }
+
+ // Enter the message pump to allow the URL load to proceed.
+ PumpEvents();
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestServ.js b/netwerk/test/TestServ.js
new file mode 100644
index 0000000000..7716ac5040
--- /dev/null
+++ b/netwerk/test/TestServ.js
@@ -0,0 +1,164 @@
+/* vim:set ts=2 sw=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/. */
+
+/*
+ * To use try out this JS server socket implementation, just copy this file
+ * into the "components" directory of a Mozilla build. Then load the URL
+ * http://localhost:4444/ in the browser. You should see a page get loaded
+ * that was served up by this component :-)
+ *
+ * This code requires Mozilla 1.6 or better.
+ */
+
+const kTESTSERV_CONTRACTID = "@mozilla.org/network/test-serv;1";
+const kTESTSERV_CID = Components.ID("{a741fcd5-9695-42e8-a7f7-14f9a29f8200}");
+const nsISupports = Components.interfaces.nsISupports;
+const nsIObserver = Components.interfaces.nsIObserver;
+const nsIServerSocket = Components.interfaces.nsIServerSocket;
+const nsIServerSocketListener = Components.interfaces.nsIServerSocketListener;
+const nsITransport = Components.interfaces.nsITransport;
+const nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+
+/** we'll listen on this port for HTTP requests **/
+const kPORT = 4444;
+
+function nsTestServ() { dump(">>> creating nsTestServ instance\n"); };
+
+nsTestServ.prototype =
+{
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(nsIObserver) ||
+ iid.equals(nsIServerSocketListener) ||
+ iid.equals(nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data)
+ {
+ dump(">>> observe [" + topic + "]\n");
+ this.startListening();
+ },
+
+ /* this function is called when we receive a new connection */
+ onSocketAccepted: function(serverSocket, clientSocket)
+ {
+ dump(">>> accepted connection on "+clientSocket.host+":"+clientSocket.port+"\n");
+
+ var input = clientSocket.openInputStream(nsITransport.OPEN_BLOCKING, 0, 0);
+ var output = clientSocket.openOutputStream(nsITransport.OPEN_BLOCKING, 0, 0);
+
+ this.consumeInput(input);
+
+ const fixedResponse =
+ "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nFooooopy!!\r\n";
+ var response = fixedResponse + "\r\n" + new Date().toString() + "\r\n";
+ var n = output.write(response, response.length);
+ dump(">>> wrote "+n+" bytes\n");
+
+ input.close();
+ output.close();
+ },
+
+ onStopListening: function(serverSocket, status)
+ {
+ dump(">>> shutting down server socket\n");
+ },
+
+ startListening: function()
+ {
+ const SERVERSOCKET_CONTRACTID = "@mozilla.org/network/server-socket;1";
+ var socket = Components.classes[SERVERSOCKET_CONTRACTID].createInstance(nsIServerSocket);
+ socket.init(kPORT, true /* loopback only */, 5);
+ dump(">>> listening on port "+socket.port+"\n");
+ socket.asyncListen(this);
+ },
+
+ consumeInput: function(input)
+ {
+ /* use nsIScriptableInputStream to consume all of the data on the stream */
+
+ var sin = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(nsIScriptableInputStream);
+ sin.init(input);
+
+ /* discard all data */
+ while (sin.available() > 0)
+ sin.read(512);
+ }
+}
+
+/**
+ * JS XPCOM component registration goop:
+ *
+ * We set ourselves up to observe the xpcom-startup category. This provides
+ * us with a starting point.
+ */
+
+var servModule = new Object();
+
+servModule.registerSelf =
+function (compMgr, fileSpec, location, type)
+{
+ compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ compMgr.registerFactoryLocation(kTESTSERV_CID,
+ "nsTestServ",
+ kTESTSERV_CONTRACTID,
+ fileSpec,
+ location,
+ type);
+
+ const CATMAN_CONTRACTID = "@mozilla.org/categorymanager;1";
+ const nsICategoryManager = Components.interfaces.nsICategoryManager;
+ var catman = Components.classes[CATMAN_CONTRACTID].getService(nsICategoryManager);
+ catman.addCategoryEntry("xpcom-startup",
+ "TestServ",
+ kTESTSERV_CONTRACTID,
+ true,
+ true);
+}
+
+servModule.getClassObject =
+function (compMgr, cid, iid)
+{
+ if (!cid.equals(kTESTSERV_CID))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ if (!iid.equals(Components.interfaces.nsIFactory))
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+
+ return servFactory;
+}
+
+servModule.canUnload =
+function (compMgr)
+{
+ dump(">>> unloading test serv.\n");
+ return true;
+}
+
+var servFactory = new Object();
+
+servFactory.createInstance =
+function (outer, iid)
+{
+ if (outer != null)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+
+ if (!iid.equals(nsIObserver) &&
+ !iid.equals(nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return TestServ;
+}
+
+function NSGetModule(compMgr, fileSpec)
+{
+ return servModule;
+}
+
+var TestServ = new nsTestServ();
diff --git a/netwerk/test/TestSocketIO.cpp b/netwerk/test/TestSocketIO.cpp
new file mode 100644
index 0000000000..319e8e1a4b
--- /dev/null
+++ b/netwerk/test/TestSocketIO.cpp
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+#include <signal.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "nspr.h"
+#include "nscore.h"
+#include "nsISocketTransportService.h"
+#include "nsIEventQueueService.h"
+#include "nsIServiceManager.h"
+#include "nsITransport.h"
+#include "nsIRequest.h"
+#include "nsIStreamProvider.h"
+#include "nsIStreamListener.h"
+#include "nsIPipe.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsIByteArrayInputStream.h"
+
+static PRLogModuleInfo *gTestSocketIOLog;
+#define LOG(args) MOZ_LOG(gTestSocketIOLog, mozilla::LogLevel::Debug, args)
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
+
+static PRTime gElapsedTime;
+static int gKeepRunning = 1;
+static nsIEventQueue* gEventQ = nullptr;
+
+//
+//----------------------------------------------------------------------------
+// Test Listener
+//----------------------------------------------------------------------------
+//
+
+class TestListener : public nsIStreamListener
+{
+public:
+ TestListener() {}
+ virtual ~TestListener() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+};
+
+NS_IMPL_ISUPPORTS(TestListener,
+ nsIRequestObserver,
+ nsIStreamListener);
+
+NS_IMETHODIMP
+TestListener::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ LOG(("TestListener::OnStartRequest\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestListener::OnDataAvailable(nsIRequest* request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint32_t aSourceOffset,
+ uint32_t aLength)
+{
+ LOG(("TestListener::OnDataAvailable [offset=%u length=%u]\n",
+ aSourceOffset, aLength));
+ char buf[1025];
+ uint32_t amt;
+ while (1) {
+ aIStream->Read(buf, 1024, &amt);
+ if (amt == 0)
+ break;
+ buf[amt] = '\0';
+ puts(buf);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestListener::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("TestListener::OnStopRequest [aStatus=%x]\n", aStatus));
+ gKeepRunning = 0;
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Test Provider
+//----------------------------------------------------------------------------
+//
+
+class TestProvider : public nsIStreamProvider
+{
+public:
+ TestProvider(char *data);
+ virtual ~TestProvider();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMPROVIDER
+
+protected:
+ nsCOMPtr<nsIByteArrayInputStream> mData;
+};
+
+NS_IMPL_ISUPPORTS(TestProvider,
+ nsIStreamProvider,
+ nsIRequestObserver)
+
+TestProvider::TestProvider(char *data)
+{
+ NS_NewByteArrayInputStream(getter_AddRefs(mData), data, strlen(data));
+ LOG(("Constructing TestProvider [this=%p]\n", this));
+}
+
+TestProvider::~TestProvider()
+{
+ LOG(("Destroying TestProvider [this=%p]\n", this));
+}
+
+NS_IMETHODIMP
+TestProvider::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ LOG(("TestProvider::OnStartRequest [this=%p]\n", this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestProvider::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("TestProvider::OnStopRequest [status=%x]\n", aStatus));
+
+ nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(new TestListener());
+
+ if (NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsITransportRequest> treq = do_QueryInterface(request);
+ nsCOMPtr<nsITransport> transport;
+ treq->GetTransport(getter_AddRefs(transport));
+ if (transport) {
+ nsCOMPtr<nsIRequest> readRequest;
+ transport->AsyncRead(listener, nullptr, 0, 0, 0, getter_AddRefs(readRequest));
+ }
+ } else
+ gKeepRunning = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestProvider::OnDataWritable(nsIRequest *request, nsISupports *context,
+ nsIOutputStream *output, uint32_t offset, uint32_t count)
+{
+ LOG(("TestProvider::OnDataWritable [offset=%u, count=%u]\n", offset, count));
+ uint32_t writeCount;
+ nsresult rv = output->WriteFrom(mData, count, &writeCount);
+ // Zero bytes written on success indicates EOF
+ if (NS_SUCCEEDED(rv) && (writeCount == 0))
+ return NS_BASE_STREAM_CLOSED;
+ return rv;
+}
+
+//
+//----------------------------------------------------------------------------
+// Synchronous IO
+//----------------------------------------------------------------------------
+//
+nsresult
+WriteRequest(nsIOutputStream *os, const char *request)
+{
+ LOG(("WriteRequest [request=%s]\n", request));
+ uint32_t n;
+ return os->Write(request, strlen(request), &n);
+}
+
+nsresult
+ReadResponse(nsIInputStream *is)
+{
+ uint32_t bytesRead;
+ char buf[2048];
+ do {
+ is->Read(buf, sizeof(buf), &bytesRead);
+ if (bytesRead > 0)
+ fwrite(buf, 1, bytesRead, stdout);
+ } while (bytesRead > 0);
+ return NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// Startup...
+//----------------------------------------------------------------------------
+//
+
+void
+sighandler(int sig)
+{
+ LOG(("got signal: %d\n", sig));
+ NS_BREAK();
+}
+
+void
+usage(char **argv)
+{
+ printf("usage: %s [-sync] <host> <path>\n", argv[0]);
+ exit(1);
+}
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+
+ signal(SIGSEGV, sighandler);
+
+ gTestSocketIOLog = PR_NewLogModule("TestSocketIO");
+
+ if (argc < 3)
+ usage(argv);
+
+ int i=0;
+ bool sync = false;
+ if (nsCRT::strcasecmp(argv[1], "-sync") == 0) {
+ if (argc < 4)
+ usage(argv);
+ sync = true;
+ i = 1;
+ }
+
+ char *hostName = argv[1+i];
+ char *fileName = argv[2+i];
+ int port = 80;
+
+ // Create the Event Queue for this thread...
+ nsCOMPtr<nsIEventQueueService> eventQService =
+ do_GetService(kEventQueueServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: event queue service!");
+ return rv;
+ }
+
+ rv = eventQService->CreateMonitoredThreadEventQueue();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: thread event queue!");
+ return rv;
+ }
+
+ eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, &gEventQ);
+
+ // Create the Socket transport service...
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: socket transport service!");
+ return rv;
+ }
+
+ char *buffer = PR_smprintf("GET %s HTTP/1.1" CRLF
+ "host: %s" CRLF
+ "user-agent: Mozilla/5.0 (X11; N; Linux 2.2.16-22smp i686; en-US; m18) Gecko/20001220" CRLF
+ "accept: */*" CRLF
+ "accept-language: en" CRLF
+ "accept-encoding: gzip,deflate,compress,identity" CRLF
+ "keep-alive: 300" CRLF
+ "connection: keep-alive" CRLF
+ CRLF,
+ fileName, hostName);
+ LOG(("Request [\n%s]\n", buffer));
+
+ // Create the socket transport...
+ nsCOMPtr<nsITransport> transport;
+ rv = sts->CreateTransport(hostName, port, nullptr, 0, 0, getter_AddRefs(transport));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create: socket transport!");
+ return rv;
+ }
+
+ gElapsedTime = PR_Now();
+
+ if (!sync) {
+ nsCOMPtr<nsIRequest> request;
+ rv = transport->AsyncWrite(new TestProvider(buffer), nullptr, 0, 0, 0, getter_AddRefs(request));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed calling: AsyncWrite!");
+ return rv;
+ }
+
+ // Enter the message pump to allow the URL load to proceed.
+ while ( gKeepRunning ) {
+ PLEvent *gEvent;
+ gEventQ->WaitForEvent(&gEvent);
+ gEventQ->HandleEvent(gEvent);
+ }
+ }
+ else {
+ // synchronous write
+ {
+ nsCOMPtr<nsIOutputStream> os;
+ rv = transport->OpenOutputStream(0, 0, 0, getter_AddRefs(os));
+ if (NS_FAILED(rv)) {
+ LOG(("OpenOutputStream failed [rv=%x]\n", rv));
+ return rv;
+ }
+ rv = WriteRequest(os, buffer);
+ if (NS_FAILED(rv)) {
+ LOG(("WriteRequest failed [rv=%x]\n", rv));
+ return rv;
+ }
+ }
+ // synchronous read
+ {
+ nsCOMPtr<nsIInputStream> is;
+ rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(is));
+ if (NS_FAILED(rv)) {
+ LOG(("OpenInputStream failed [rv=%x]\n", rv));
+ return rv;
+ }
+ rv = ReadResponse(is);
+ if (NS_FAILED(rv)) {
+ LOG(("ReadResponse failed [rv=%x]\n", rv));
+ return rv;
+ }
+ }
+ }
+
+ PRTime endTime;
+ endTime = PR_Now();
+ LOG(("Elapsed time: %d\n", (int32_t)(endTime/1000UL - gElapsedTime/1000UL)));
+
+ sts->Shutdown();
+ return 0;
+}
+
diff --git a/netwerk/test/TestSocketInput.cpp b/netwerk/test/TestSocketInput.cpp
new file mode 100644
index 0000000000..c3e58bd1eb
--- /dev/null
+++ b/netwerk/test/TestSocketInput.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsISocketTransportService.h"
+#include "nsIEventQueueService.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsITransport.h"
+#include "nsIRequest.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
+
+static int gKeepRunning = 1;
+
+class InputTestConsumer : public nsIStreamListener
+{
+public:
+
+ InputTestConsumer();
+ virtual ~InputTestConsumer();
+
+ // ISupports interface...
+ NS_DECL_ISUPPORTS
+
+ // IStreamListener interface...
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+};
+
+
+InputTestConsumer::InputTestConsumer()
+{
+}
+
+InputTestConsumer::~InputTestConsumer()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(InputTestConsumer, nsIRequestObserver, nsIStreamListener)
+
+
+NS_IMETHODIMP
+InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
+{
+ printf("+++ OnStartRequest +++\n");
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InputTestConsumer::OnDataAvailable(nsIRequest *request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ char buf[1025];
+ while (aLength > 0) {
+ uint32_t amt;
+ aIStream->Read(buf, 1024, &amt);
+ if (amt == 0) break;
+ buf[amt] = '\0';
+ printf(buf);
+ aLength -= amt;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InputTestConsumer::OnStopRequest(nsIRequest *request, nsISupports* context,
+ nsresult aStatus)
+{
+ gKeepRunning = 0;
+ printf("+++ OnStopRequest status %x +++\n", aStatus);
+ return NS_OK;
+}
+
+
+int
+main(int argc, char* argv[])
+{
+ nsresult rv;
+
+ if (argc < 2) {
+ printf("usage: %s <host>\n", argv[0]);
+ return -1;
+ }
+
+ int port;
+ char* hostName = argv[1];
+//nsString portString(argv[2]);
+
+//port = portString.ToInteger(&rv);
+ port = 13;
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ // Create the Event Queue for this thread...
+ nsCOMPtr<nsIEventQueueService> eventQService =
+ do_GetService(kEventQueueServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIEventQueue> eventQ;
+ rv = eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(eventQ));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsITransport* transport;
+
+ rv = sts->CreateTransport(hostName, port, nullptr, 0, 0, &transport);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRequest> request;
+ transport->AsyncRead(new InputTestConsumer, nullptr, 0, -1, 0, getter_AddRefs(request));
+
+ NS_RELEASE(transport);
+ }
+
+ // Enter the message pump to allow the URL load to proceed.
+ while ( gKeepRunning ) {
+ PLEvent *gEvent;
+ eventQ->WaitForEvent(&gEvent);
+ eventQ->HandleEvent(gEvent);
+ }
+
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return 0;
+}
+
diff --git a/netwerk/test/TestSocketTransport.cpp b/netwerk/test/TestSocketTransport.cpp
new file mode 100644
index 0000000000..58422a38c2
--- /dev/null
+++ b/netwerk/test/TestSocketTransport.cpp
@@ -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/. */
+
+#include "TestCommon.h"
+#include "nsIComponentRegistrar.h"
+#include "nsPISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIRequest.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsStringAPI.h"
+#include "nsIDNSService.h"
+#include "nsIFileStreams.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsAutoLock.h"
+#include "mozilla/Logging.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class MyHandler : public nsIOutputStreamCallback
+ , public nsIInputStreamCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ MyHandler(const char *path,
+ nsIAsyncInputStream *in,
+ nsIAsyncOutputStream *out)
+ : mInput(in)
+ , mOutput(out)
+ , mWriteOffset(0)
+ {
+ mBuf.AssignLiteral("GET ");
+ mBuf.Append(path);
+ mBuf.AppendLiteral(" HTTP/1.0\r\n\r\n");
+ }
+ virtual ~MyHandler() {}
+
+ // called on any thread
+ NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream *out)
+ {
+ LOG(("OnOutputStreamReady\n"));
+
+ nsresult rv;
+ uint32_t n, count = mBuf.Length() - mWriteOffset;
+
+ rv = out->Write(mBuf.get() + mWriteOffset, count, &n);
+
+ LOG((" write returned [rv=%x count=%u]\n", rv, n));
+
+ if (NS_FAILED(rv) || (n == 0)) {
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG((" done writing; starting to read\n"));
+ mInput->AsyncWait(this, 0, 0, nullptr);
+ return NS_OK;
+ }
+ }
+
+ mWriteOffset += n;
+
+ return out->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ // called on any thread
+ NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream *in)
+ {
+ LOG(("OnInputStreamReady\n"));
+
+ nsresult rv;
+ uint32_t n;
+ char buf[500];
+
+ rv = in->Read(buf, sizeof(buf), &n);
+
+ LOG((" read returned [rv=%x count=%u]\n", rv, n));
+
+ if (NS_FAILED(rv) || (n == 0)) {
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ QuitPumpingEvents();
+ return NS_OK;
+ }
+ }
+
+ return in->AsyncWait(this, 0, 0, nullptr);
+ }
+
+private:
+ nsCOMPtr<nsIAsyncInputStream> mInput;
+ nsCOMPtr<nsIAsyncOutputStream> mOutput;
+ nsCString mBuf;
+ uint32_t mWriteOffset;
+};
+
+NS_IMPL_ISUPPORTS(MyHandler,
+ nsIOutputStreamCallback,
+ nsIInputStreamCallback)
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * create transport, open streams, and close
+ */
+static nsresult
+RunCloseTest(nsISocketTransportService *sts,
+ const char *host, int port,
+ uint32_t inFlags, uint32_t outFlags)
+{
+ nsresult rv;
+
+ LOG(("RunCloseTest\n"));
+
+ nsCOMPtr<nsISocketTransport> transport;
+ rv = sts->CreateTransport(nullptr, 0,
+ nsDependentCString(host), port, nullptr,
+ getter_AddRefs(transport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> in;
+ rv = transport->OpenInputStream(inFlags, 0, 0, getter_AddRefs(in));
+ nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(in, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = transport->OpenOutputStream(outFlags, 0, 0, getter_AddRefs(out));
+ nsCOMPtr<nsIAsyncOutputStream> asyncOut = do_QueryInterface(out, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("waiting 1 second before closing transport and streams...\n"));
+ PR_Sleep(PR_SecondsToInterval(1));
+
+ // let nsCOMPtr destructors close everything...
+ return NS_OK;
+}
+
+
+/**
+ * asynchronously read socket stream
+ */
+static nsresult
+RunTest(nsISocketTransportService *sts,
+ const char *host, int port, const char *path,
+ uint32_t inFlags, uint32_t outFlags)
+{
+ nsresult rv;
+
+ LOG(("RunTest\n"));
+
+ nsCOMPtr<nsISocketTransport> transport;
+ rv = sts->CreateTransport(nullptr, 0,
+ nsDependentCString(host), port, nullptr,
+ getter_AddRefs(transport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> in;
+ rv = transport->OpenInputStream(inFlags, 0, 0, getter_AddRefs(in));
+ nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(in, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = transport->OpenOutputStream(outFlags, 0, 0, getter_AddRefs(out));
+ nsCOMPtr<nsIAsyncOutputStream> asyncOut = do_QueryInterface(out, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ MyHandler *handler = new MyHandler(path, asyncIn, asyncOut);
+ if (handler == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(handler);
+
+ rv = asyncOut->AsyncWait(handler, 0, 0, nullptr);
+
+ if (NS_SUCCEEDED(rv))
+ PumpEvents();
+
+ NS_RELEASE(handler);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 4) {
+ printf("usage: TestSocketTransport <host> <port> <path>\n");
+ return -1;
+ }
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ gTestLog = PR_NewLogModule("Test");
+
+ // Make sure the DNS service is initialized on the main thread
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsPISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("phase 1 tests...\n"));
+
+ LOG(("flags = { OPEN_UNBUFFERED, OPEN_UNBUFFERED }\n"));
+ rv = RunCloseTest(sts, argv[1], atoi(argv[2]),
+ nsITransport::OPEN_UNBUFFERED,
+ nsITransport::OPEN_UNBUFFERED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunCloseTest failed");
+
+ LOG(("flags = { OPEN_BUFFERED, OPEN_UNBUFFERED }\n"));
+ rv = RunCloseTest(sts, argv[1], atoi(argv[2]),
+ 0 /* nsITransport::OPEN_BUFFERED */,
+ nsITransport::OPEN_UNBUFFERED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunCloseTest failed");
+
+ LOG(("flags = { OPEN_UNBUFFERED, OPEN_BUFFERED }\n"));
+ rv = RunCloseTest(sts, argv[1], atoi(argv[2]),
+ nsITransport::OPEN_UNBUFFERED,
+ 0 /*nsITransport::OPEN_BUFFERED */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunCloseTest failed");
+
+ LOG(("flags = { OPEN_BUFFERED, OPEN_BUFFERED }\n"));
+ rv = RunCloseTest(sts, argv[1], atoi(argv[2]),
+ 0 /*nsITransport::OPEN_BUFFERED */,
+ 0 /*nsITransport::OPEN_BUFFERED */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunCloseTest failed");
+
+ LOG(("calling Shutdown on socket transport service:\n"));
+ sts->Shutdown();
+
+ LOG(("calling Init on socket transport service:\n"));
+ sts->Init();
+
+ LOG(("phase 2 tests...\n"));
+
+ LOG(("flags = { OPEN_UNBUFFERED, OPEN_UNBUFFERED }\n"));
+ rv = RunTest(sts, argv[1], atoi(argv[2]), argv[3],
+ nsITransport::OPEN_UNBUFFERED,
+ nsITransport::OPEN_UNBUFFERED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ LOG(("flags = { OPEN_BUFFERED, OPEN_UNBUFFERED }\n"));
+ rv = RunTest(sts, argv[1], atoi(argv[2]), argv[3],
+ 0 /* nsITransport::OPEN_BUFFERED */,
+ nsITransport::OPEN_UNBUFFERED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ LOG(("flags = { OPEN_UNBUFFERED, OPEN_BUFFERED }\n"));
+ rv = RunTest(sts, argv[1], atoi(argv[2]), argv[3],
+ nsITransport::OPEN_UNBUFFERED,
+ 0 /*nsITransport::OPEN_BUFFERED */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ LOG(("flags = { OPEN_BUFFERED, OPEN_BUFFERED }\n"));
+ rv = RunTest(sts, argv[1], atoi(argv[2]), argv[3],
+ 0 /*nsITransport::OPEN_BUFFERED */,
+ 0 /*nsITransport::OPEN_BUFFERED */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ LOG(("waiting 1 second before calling Shutdown...\n"));
+ PR_Sleep(PR_SecondsToInterval(1));
+
+ LOG(("calling Shutdown on socket transport service:\n"));
+ sts->Shutdown();
+
+ // give background threads a chance to finish whatever work they may
+ // be doing.
+ LOG(("waiting 1 second before exiting...\n"));
+ PR_Sleep(PR_SecondsToInterval(1));
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return 0;
+}
diff --git a/netwerk/test/TestStreamLoader.cpp b/netwerk/test/TestStreamLoader.cpp
new file mode 100644
index 0000000000..c01148f40d
--- /dev/null
+++ b/netwerk/test/TestStreamLoader.cpp
@@ -0,0 +1,101 @@
+#include <stdio.h>
+#include "TestCommon.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "NetwerkTestLogging.h"
+#include "mozilla/Attributes.h"
+#include "nsIScriptSecurityManager.h"
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+class MyStreamLoaderObserver final : public nsIStreamLoaderObserver
+{
+ ~MyStreamLoaderObserver() = default;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+};
+
+NS_IMPL_ISUPPORTS(MyStreamLoaderObserver, nsIStreamLoaderObserver)
+
+NS_IMETHODIMP
+MyStreamLoaderObserver::OnStreamComplete(nsIStreamLoader *loader,
+ nsISupports *ctxt,
+ nsresult status,
+ uint32_t resultLen,
+ const uint8_t *result)
+{
+ LOG(("OnStreamComplete [status=%x resultLen=%u]\n", status, resultLen));
+
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ LOG((" request=%p\n", request.get()));
+
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+int main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ if (argc < 2) {
+ printf("usage: %s <url>\n", argv[0]);
+ return -1;
+ }
+
+ gTestLog = PR_NewLogModule("Test");
+
+ nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv))
+ return -1;
+
+ {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(argv[1]));
+ if (NS_FAILED(rv))
+ return -1;
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = NS_NewChannel(getter_AddRefs(chan),
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv))
+ return -1;
+
+ nsCOMPtr<nsIStreamLoaderObserver> observer = new MyStreamLoaderObserver();
+ if (!observer)
+ return -1;
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), observer);
+ if (NS_FAILED(rv))
+ return -1;
+
+ rv = chan->AsyncOpen2(loader);
+ if (NS_FAILED(rv))
+ return -1;
+
+ PumpEvents();
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ NS_ShutdownXPCOM(nullptr);
+ return 0;
+}
diff --git a/netwerk/test/TestStreamPump.cpp b/netwerk/test/TestStreamPump.cpp
new file mode 100644
index 0000000000..376b9deb7c
--- /dev/null
+++ b/netwerk/test/TestStreamPump.cpp
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIStreamTransportService.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIRequest.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsStringAPI.h"
+#include "nsIFileStreams.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsAutoLock.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include <algorithm>
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class MyListener : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ MyListener() {}
+ virtual ~MyListener() {}
+
+ NS_IMETHOD OnStartRequest(nsIRequest *req, nsISupports *ctx)
+ {
+ LOG(("MyListener::OnStartRequest\n"));
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnDataAvailable(nsIRequest *req, nsISupports *ctx,
+ nsIInputStream *stream,
+ uint64_t offset, uint32_t count)
+ {
+ LOG(("MyListener::OnDataAvailable [offset=%llu count=%u]\n", offset, count));
+
+ char buf[500];
+ nsresult rv;
+
+ while (count) {
+ uint32_t n, amt = std::min<uint32_t>(count, sizeof(buf));
+
+ rv = stream->Read(buf, amt, &n);
+ if (NS_FAILED(rv)) {
+ LOG((" read returned 0x%08x\n", rv));
+ return rv;
+ }
+
+ fwrite(buf, n, 1, stdout);
+ printf("\n");
+
+ LOG((" read %u bytes\n", n));
+ count -= n;
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest *req, nsISupports *ctx, nsresult status)
+ {
+ LOG(("MyListener::OnStopRequest [status=%x]\n", status));
+ QuitPumpingEvents();
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(MyListener,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * asynchronously copy file.
+ */
+static nsresult
+RunTest(nsIFile *file, int64_t offset, int64_t length)
+{
+ nsresult rv;
+
+ LOG(("RunTest\n"));
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream, offset, length);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = pump->AsyncRead(new MyListener(), nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ PumpEvents();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 4) {
+ printf("usage: %s <file-to-read> <start-offset> <read-length>\n", argv[0]);
+ return -1;
+ }
+ char* fileName = argv[1];
+ int64_t offset, length;
+ int err = PR_sscanf(argv[2], "%lld", &offset);
+ if (err == -1) {
+ printf("Start offset must be an integer!\n");
+ return 1;
+ }
+ err = PR_sscanf(argv[3], "%lld", &length);
+ if (err == -1) {
+ printf("Length must be an integer!\n");
+ return 1;
+ }
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ gTestLog = PR_NewLogModule("Test");
+
+ nsCOMPtr<nsIFile> file;
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileName), false, getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = RunTest(file, offset, length);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ // give background threads a chance to finish whatever work they may
+ // be doing.
+ PR_Sleep(PR_SecondsToInterval(1));
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return NS_OK;
+}
diff --git a/netwerk/test/TestStreamTransport.cpp b/netwerk/test/TestStreamTransport.cpp
new file mode 100644
index 0000000000..840c9578d6
--- /dev/null
+++ b/netwerk/test/TestStreamTransport.cpp
@@ -0,0 +1,319 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestCommon.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIStreamTransportService.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIRequest.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsStringAPI.h"
+#include "nsIFileStreams.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsAutoLock.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define CHUNK_SIZE 500
+
+class MyCopier : public nsIInputStreamCallback
+ , public nsIOutputStreamCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ MyCopier()
+ : mLock(nullptr)
+ , mInputCondition(NS_OK)
+ {
+ }
+
+ virtual ~MyCopier()
+ {
+ if (mLock)
+ nsAutoLock::DestroyLock(mLock);
+ if (mInput)
+ mInput->Close();
+ if (mOutput)
+ mOutput->Close();
+ }
+
+ // called on any thread
+ NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream *inStr)
+ {
+ LOG(("OnInputStreamReady\n"));
+ nsAutoLock lock(mLock);
+ NS_ASSERTION(inStr == mInput, "unexpected stream");
+ Process_Locked();
+ return NS_OK;
+ }
+
+ // called on any thread
+ NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream *outStr)
+ {
+ LOG(("OnOutputStreamReady\n"));
+ nsAutoLock lock(mLock);
+ NS_ASSERTION(outStr == mOutput, "unexpected stream");
+ Process_Locked();
+ return NS_OK;
+ }
+
+ void Close_Locked()
+ {
+ LOG(("Close_Locked\n"));
+
+ mOutput->Close();
+ mOutput = 0;
+ mInput->Close();
+ mInput = 0;
+
+ // post done copying event
+ QuitPumpingEvents();
+ }
+
+ void Process_Locked()
+ {
+ while (1) {
+ mInputCondition = NS_OK; // reset
+
+ uint32_t n;
+ nsresult rv = mOutput->WriteSegments(FillOutputBuffer, this, CHUNK_SIZE, &n);
+ if (NS_FAILED(rv) || (n == 0)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ mOutput->AsyncWait(this, 0, 0, nullptr);
+ else if (mInputCondition == NS_BASE_STREAM_WOULD_BLOCK)
+ mInput->AsyncWait(this, 0, 0, nullptr);
+ else
+ Close_Locked();
+ break;
+ }
+ }
+ }
+
+ nsresult AsyncCopy(nsITransport *srcTrans, nsITransport *destTrans)
+ {
+ mLock = nsAutoLock::NewLock("MyCopier::mLock");
+ if (!mLock)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStream> inStr;
+ rv = srcTrans->OpenInputStream(0, 0, 0, getter_AddRefs(inStr));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> outStr;
+ rv = destTrans->OpenOutputStream(0, 0, 0, getter_AddRefs(outStr));
+ if (NS_FAILED(rv)) return rv;
+
+ mInput = do_QueryInterface(inStr);
+ mOutput = do_QueryInterface(outStr);
+
+ return mInput->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ static nsresult FillOutputBuffer(nsIOutputStream *outStr,
+ void *closure,
+ char *buffer,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+ {
+ MyCopier *self = (MyCopier *) closure;
+
+ nsresult rv = self->mInput->Read(buffer, count, countRead);
+ if (NS_FAILED(rv))
+ self->mInputCondition = rv;
+ else if (*countRead == 0)
+ self->mInputCondition = NS_BASE_STREAM_CLOSED;
+
+ return self->mInputCondition;
+ }
+
+protected:
+ PRLock *mLock;
+ nsCOMPtr<nsIAsyncInputStream> mInput;
+ nsCOMPtr<nsIAsyncOutputStream> mOutput;
+ nsresult mInputCondition;
+};
+
+NS_IMPL_ISUPPORTS(MyCopier,
+ nsIInputStreamCallback,
+ nsIOutputStreamCallback)
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * asynchronously copy file.
+ */
+static nsresult
+RunTest(nsIFile *srcFile, nsIFile *destFile)
+{
+ nsresult rv;
+
+ LOG(("RunTest\n"));
+
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> srcStr;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(srcStr), srcFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> destStr;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(destStr), destFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransport> srcTransport;
+ rv = sts->CreateInputTransport(srcStr, int64_t(-1), int64_t(-1), true,
+ getter_AddRefs(srcTransport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransport> destTransport;
+ rv = sts->CreateOutputTransport(destStr, int64_t(-1), int64_t(-1), true,
+ getter_AddRefs(destTransport));
+ if (NS_FAILED(rv)) return rv;
+
+ MyCopier *copier = new MyCopier();
+ if (copier == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(copier);
+
+ rv = copier->AsyncCopy(srcTransport, destTransport);
+ if (NS_FAILED(rv)) return rv;
+
+ PumpEvents();
+
+ NS_RELEASE(copier);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static nsresult
+RunBlockingTest(nsIFile *srcFile, nsIFile *destFile)
+{
+ nsresult rv;
+
+ LOG(("RunBlockingTest\n"));
+
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> srcIn;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(srcIn), srcFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> fileOut;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOut), destFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransport> destTransport;
+ rv = sts->CreateOutputTransport(fileOut, int64_t(-1), int64_t(-1),
+ true, getter_AddRefs(destTransport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> destOut;
+ rv = destTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 100, 10, getter_AddRefs(destOut));
+ if (NS_FAILED(rv)) return rv;
+
+ char buf[120];
+ uint32_t n;
+ for (;;) {
+ rv = srcIn->Read(buf, sizeof(buf), &n);
+ if (NS_FAILED(rv) || (n == 0)) return rv;
+
+ rv = destOut->Write(buf, n, &n);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 2) {
+ printf("usage: %s <file-to-read>\n", argv[0]);
+ return -1;
+ }
+ char* fileName = argv[1];
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ NS_ASSERTION(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ gTestLog = PR_NewLogModule("Test");
+
+ nsCOMPtr<nsIFile> srcFile;
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileName), false, getter_AddRefs(srcFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> destFile;
+ rv = srcFile->Clone(getter_AddRefs(destFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString leafName;
+ rv = destFile->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString newName(leafName);
+ newName.AppendLiteral(".1");
+ rv = destFile->SetNativeLeafName(newName);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = RunTest(srcFile, destFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunTest failed");
+
+ newName = leafName;
+ newName.AppendLiteral(".2");
+ rv = destFile->SetNativeLeafName(newName);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = RunBlockingTest(srcFile, destFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "RunBlockingTest failed");
+
+ // give background threads a chance to finish whatever work they may
+ // be doing.
+ PR_Sleep(PR_SecondsToInterval(1));
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+ return NS_OK;
+}
diff --git a/netwerk/test/TestUDPSocket.cpp b/netwerk/test/TestUDPSocket.cpp
new file mode 100644
index 0000000000..9236d2ea3a
--- /dev/null
+++ b/netwerk/test/TestUDPSocket.cpp
@@ -0,0 +1,473 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "TestHarness.h"
+#include "nsIUDPSocket.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsINetAddr.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsITimer.h"
+#include "mozilla/net/DNS.h"
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#endif
+#include "prerror.h"
+
+#define REQUEST 0x68656c6f
+#define RESPONSE 0x6f6c6568
+#define MULTICAST_TIMEOUT 2000
+
+#define EXPECT_SUCCESS(rv, ...) \
+ PR_BEGIN_MACRO \
+ if (NS_FAILED(rv)) { \
+ fail(__VA_ARGS__); \
+ return false; \
+ } \
+ PR_END_MACRO
+
+
+#define EXPECT_FAILURE(rv, ...) \
+ PR_BEGIN_MACRO \
+ if (NS_SUCCEEDED(rv)) { \
+ fail(__VA_ARGS__); \
+ return false; \
+ } \
+ PR_END_MACRO
+
+#define REQUIRE_EQUAL(a, b, ...) \
+ PR_BEGIN_MACRO \
+ if (a != b) { \
+ fail(__VA_ARGS__); \
+ return false; \
+ } \
+ PR_END_MACRO
+
+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) {
+ fail("Raw data length(%d) do not matches String data length(%d).", rawLen, len);
+ return false;
+ }
+
+ for (uint32_t i = 0; i < len; i++) {
+ if (buffer[i] != rawData[i]) {
+ fail("Raw data(%s) do not matches String data(%s)", rawData.Elements() ,buffer);
+ return false;
+ }
+ }
+
+ uint32_t input = 0;
+ for (uint32_t i = 0; i < len; i++) {
+ input += buffer[i] << (8 * i);
+ }
+
+ if (len != sizeof(uint32_t) || input != aExpectedContent)
+ {
+ fail("Request 0x%x received, expected 0x%x", input, aExpectedContent);
+ return false;
+ } else {
+ passed("Request 0x%x received as expected", input);
+ return true;
+ }
+}
+
+/*
+ * UDPClientListener: listens for incomming UDP packets
+ */
+class UDPClientListener : public nsIUDPSocketListener
+{
+protected:
+ virtual ~UDPClientListener();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+ nsresult mResult;
+};
+
+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);
+ passed("Packet received on client from %s:%d", ip.get(), port);
+
+ if (TEST_SEND_API == phase && CheckMessageContent(message, REQUEST)) {
+ uint32_t count;
+ const uint32_t data = RESPONSE;
+ printf("*** Attempting to write response 0x%x to server by SendWithAddr...\n", RESPONSE);
+ mResult = socket->SendWithAddr(fromAddr, (const uint8_t*)&data,
+ sizeof(uint32_t), &count);
+ if (mResult == NS_OK && count == sizeof(uint32_t)) {
+ passed("Response written");
+ } else {
+ fail("Response written");
+ }
+ return NS_OK;
+ } else if (TEST_OUTPUT_STREAM != phase || !CheckMessageContent(message, RESPONSE)) {
+ mResult = NS_ERROR_FAILURE;
+ }
+
+ // Notify thread
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPClientListener::OnStopListening(nsIUDPSocket*, nsresult)
+{
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+/*
+ * UDPServerListener: listens for incomming UDP packets
+ */
+class UDPServerListener : public nsIUDPSocketListener
+{
+protected:
+ virtual ~UDPServerListener();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ nsresult mResult;
+};
+
+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);
+ passed("Packet received on server from %s:%d", ip.get(), port);
+
+ if (TEST_OUTPUT_STREAM == phase && CheckMessageContent(message, REQUEST))
+ {
+ nsCOMPtr<nsIOutputStream> outstream;
+ message->GetOutputStream(getter_AddRefs(outstream));
+
+ uint32_t count;
+ const uint32_t data = RESPONSE;
+ printf("*** Attempting to write response 0x%x to client by OutputStream...\n", RESPONSE);
+ mResult = outstream->Write((const char*)&data, sizeof(uint32_t), &count);
+
+ if (mResult == NS_OK && count == sizeof(uint32_t)) {
+ passed("Response written");
+ } else {
+ fail("Response written");
+ }
+ 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
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPServerListener::OnStopListening(nsIUDPSocket*, nsresult)
+{
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+/**
+ * Multicast timer callback: detects delivery failure
+ */
+class MulticastTimerCallback : public nsITimerCallback
+{
+protected:
+ virtual ~MulticastTimerCallback();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ nsresult mResult;
+};
+
+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;
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+/**** Main ****/
+int
+main(int32_t argc, char *argv[])
+{
+ nsresult rv;
+ ScopedXPCOM xpcom("UDP ServerSocket");
+ if (xpcom.failed())
+ return -1;
+
+ // Create UDPSocket
+ nsCOMPtr<nsIUDPSocket> server, client;
+ server = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+ client = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ // Create UDPServerListener to process UDP packets
+ RefPtr<UDPServerListener> serverListener = new UDPServerListener();
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ // Bind server socket to 0.0.0.0
+ rv = server->Init(0, false, systemPrincipal, true, 0);
+ NS_ENSURE_SUCCESS(rv, -1);
+ int32_t serverPort;
+ server->GetPort(&serverPort);
+ server->AsyncListen(serverListener);
+
+ // Bind clinet on arbitrary port
+ RefPtr<UDPClientListener> clientListener = new UDPClientListener();
+ client->Init(0, false, systemPrincipal, true, 0);
+ client->AsyncListen(clientListener);
+
+ // Write data to server
+ uint32_t count;
+ const uint32_t data = REQUEST;
+
+ phase = TEST_OUTPUT_STREAM;
+ rv = client->Send(NS_LITERAL_CSTRING("127.0.0.1"), serverPort, (uint8_t*)&data, sizeof(uint32_t), &count);
+ NS_ENSURE_SUCCESS(rv, -1);
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Request written by Send");
+
+ // Wait for server
+ PumpEvents();
+ NS_ENSURE_SUCCESS(serverListener->mResult, -1);
+
+ // Read response from server
+ NS_ENSURE_SUCCESS(clientListener->mResult, -1);
+
+ mozilla::net::NetAddr clientAddr;
+ rv = client->GetAddress(&clientAddr);
+ NS_ENSURE_SUCCESS(rv, -1);
+ // 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, (uint8_t*)&data, sizeof(uint32_t), &count);
+ NS_ENSURE_SUCCESS(rv, -1);
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Request written by SendWithAddress");
+
+ // Wait for server
+ PumpEvents();
+ NS_ENSURE_SUCCESS(serverListener->mResult, -1);
+
+ // Read response from server
+ NS_ENSURE_SUCCESS(clientListener->mResult, -1);
+
+ // Setup timer to detect multicast failure
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
+ if (NS_WARN_IF(!timer)) {
+ return -1;
+ }
+ RefPtr<MulticastTimerCallback> timerCb = new MulticastTimerCallback();
+
+ // The following multicast tests using multiple sockets require a firewall
+ // exception on Windows XP (the earliest version of Windows we now support)
+ // before they pass. For now, we'll skip them here. Later versions of Windows
+ // (Win2003 and onward) don't seem to have this issue.
+#ifdef XP_WIN
+ if (!mozilla::IsWin2003OrLater()) { // i.e. if it is WinXP
+ goto close;
+ }
+#endif
+
+ // 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);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, (uint8_t*)&data, sizeof(uint32_t), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Multicast ping written by SendWithAddress");
+
+ // Wait for server to receive successfully
+ PumpEvents();
+ if (NS_WARN_IF(NS_FAILED(serverListener->mResult))) {
+ return -1;
+ }
+ if (NS_WARN_IF(NS_FAILED(timerCb->mResult))) {
+ return -1;
+ }
+ timer->Cancel();
+ passed("Server received ping successfully");
+
+ // 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, (uint8_t*)&data, sizeof(uint32_t), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Multicast ping written by SendWithAddress");
+
+ // Wait for server to fail to receive
+ PumpEvents();
+ if (NS_WARN_IF(NS_SUCCEEDED(timerCb->mResult))) {
+ return -1;
+ }
+ timer->Cancel();
+ passed("Server failed to receive ping correctly");
+
+ // Reset state
+ client->SetMulticastLoopback(true);
+ server->SetMulticastLoopback(true);
+
+ // Change multicast interface
+ printf("Changing multicast interface\n");
+ 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, (uint8_t*)&data, sizeof(uint32_t), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Multicast ping written by SendWithAddress");
+
+ // Wait for server to fail to receive
+ PumpEvents();
+ if (NS_WARN_IF(NS_SUCCEEDED(timerCb->mResult))) {
+ return -1;
+ }
+ timer->Cancel();
+ passed("Server failed to receive ping correctly");
+
+ // Reset state
+ mozilla::net::NetAddr anyAddr;
+ anyAddr.inet.family = AF_INET;
+ anyAddr.inet.ip = PR_htonl(INADDR_ANY);
+ client->SetMulticastInterfaceAddr(anyAddr);
+
+ // Leave multicast group
+ printf("Leave multicast group\n");
+ rv = server->LeaveMulticastAddr(multicastAddr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, (uint8_t*)&data, sizeof(uint32_t), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ REQUIRE_EQUAL(count, sizeof(uint32_t), "Error");
+ passed("Multicast ping written by SendWithAddress");
+
+ // Wait for server to fail to receive
+ PumpEvents();
+ if (NS_WARN_IF(NS_SUCCEEDED(timerCb->mResult))) {
+ return -1;
+ }
+ timer->Cancel();
+ passed("Server failed to receive ping correctly");
+ goto close;
+
+close:
+ // Close server
+ printf("*** Attempting to close server ...\n");
+ server->Close();
+ client->Close();
+ PumpEvents();
+ passed("Server closed");
+
+ return 0; // failure is a non-zero return
+}
diff --git a/netwerk/test/TestUDPSocketProvider.cpp b/netwerk/test/TestUDPSocketProvider.cpp
new file mode 100644
index 0000000000..921e610ab6
--- /dev/null
+++ b/netwerk/test/TestUDPSocketProvider.cpp
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "stdio.h"
+#include "TestCommon.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nspr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+
+#define UDP_PORT 9050
+
+#define UDP_ASSERT(condition, message) \
+ PR_BEGIN_MACRO \
+ NS_ASSERTION(condition, message); \
+ if (!(condition)) { \
+ returnCode = -1; \
+ break; \
+ } \
+ PR_END_MACRO
+
+#define UDP_ASSERT_PRSTATUS(message) \
+ PR_BEGIN_MACRO \
+ NS_ASSERTION(status == PR_SUCCESS, message); \
+ if (status != PR_SUCCESS) { \
+ PRErrorCode err = PR_GetError(); \
+ fprintf(stderr, \
+ "FAIL nspr: %s: (%08x) %s\n", \
+ message, \
+ err, \
+ PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT)); \
+ returnCode = -1; \
+ break; \
+ } \
+ PR_END_MACRO
+
+#define UDP_ASSERT_NSRESULT(message) \
+ PR_BEGIN_MACRO \
+ NS_ASSERTION(NS_SUCCEEDED(rv), message); \
+ if (NS_FAILED(rv)) { \
+ fprintf(stderr, "FAIL UDPSocket: %s: %08x\n", \
+ message, rv); \
+ returnCode = -1; \
+ break; \
+ } \
+ PR_END_MACRO
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ int returnCode = 0;
+ nsresult rv = NS_OK;
+ PRFileDesc *serverFD = nullptr;
+
+ do { // act both as a scope for nsCOMPtrs to be released before XPCOM
+ // shutdown, as well as a easy way to abort the test
+ PRStatus status = PR_SUCCESS;
+
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan);
+ UDP_ASSERT(registrar, "Null nsIComponentRegistrar");
+ if (registrar)
+ registrar->AutoRegister(nullptr);
+
+ // listen for a incoming UDP connection on localhost
+ serverFD = PR_OpenUDPSocket(PR_AF_INET);
+ UDP_ASSERT(serverFD, "Cannot open UDP socket for listening");
+
+ PRSocketOptionData socketOptions;
+ socketOptions.option = PR_SockOpt_Nonblocking;
+ socketOptions.value.non_blocking = false;
+ status = PR_SetSocketOption(serverFD, &socketOptions);
+ UDP_ASSERT_PRSTATUS("Failed to set server socket as blocking");
+
+ PRNetAddr addr;
+ status = PR_InitializeNetAddr(PR_IpAddrLoopback, UDP_PORT, &addr);
+ UDP_ASSERT_PRSTATUS("Failed to initialize loopback address");
+
+ status = PR_Bind(serverFD, &addr);
+ UDP_ASSERT_PRSTATUS("Failed to bind server socket");
+
+ // dummy IOService to get around bug 379890
+ nsCOMPtr<nsISupports> ios =
+ do_GetService("@mozilla.org/network/io-service;1");
+
+ // and have a matching UDP connection for the client
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
+ UDP_ASSERT_NSRESULT("Cannot get socket transport service");
+
+ nsCOMPtr<nsISocketTransport> transport;
+ const char *protocol = "udp";
+ rv = sts->CreateTransport(&protocol, 1, NS_LITERAL_CSTRING("localhost"),
+ UDP_PORT, nullptr, getter_AddRefs(transport));
+ UDP_ASSERT_NSRESULT("Cannot create transport");
+
+ uint32_t count, read;
+ const uint32_t data = 0xFF0056A9;
+
+ // write to the output stream
+ nsCOMPtr<nsIOutputStream> outstream;
+ rv = transport->OpenOutputStream(nsITransport::OPEN_BLOCKING,
+ 0, 0, getter_AddRefs(outstream));
+ UDP_ASSERT_NSRESULT("Cannot open output stream");
+
+ rv = outstream->Write((const char*)&data, sizeof(uint32_t), &count);
+ UDP_ASSERT_NSRESULT("Cannot write to output stream");
+ UDP_ASSERT(count == sizeof(uint32_t),
+ "Did not write enough bytes to output stream");
+
+ // read from NSPR to check it's the same
+ count = PR_RecvFrom(serverFD, &read, sizeof(uint32_t), 0, &addr, 1);
+ UDP_ASSERT(count == sizeof(uint32_t),
+ "Did not read enough bytes from NSPR");
+ status = (read == data ? PR_SUCCESS : PR_FAILURE);
+ UDP_ASSERT_PRSTATUS("Did not read expected data from NSPR");
+
+ // write to NSPR
+ count = PR_SendTo(serverFD, &data, sizeof(uint32_t), 0, &addr, 1);
+ status = (count == sizeof(uint32_t) ? PR_SUCCESS : PR_FAILURE);
+ UDP_ASSERT_PRSTATUS("Did not write enough bytes to NSPR");
+
+ // read from stream
+ nsCOMPtr<nsIInputStream> instream;
+ rv = transport->OpenInputStream(nsITransport::OPEN_BLOCKING,
+ 0, 0, getter_AddRefs(instream));
+ UDP_ASSERT_NSRESULT("Cannot open input stream");
+
+ rv = instream->Read((char*)&read, sizeof(uint32_t), &count);
+ UDP_ASSERT_NSRESULT("Cannot read from input stream");
+ UDP_ASSERT(count == sizeof(uint32_t),
+ "Did not read enough bytes from input stream");
+ UDP_ASSERT(read == data, "Did not read expected data from stream");
+
+ } while (false); // release all XPCOM things
+ if (serverFD) {
+ PRStatus status = PR_Close(serverFD);
+ if (status != PR_SUCCESS) {
+ PRErrorCode err = PR_GetError();
+ fprintf(stderr, "FAIL: Cannot close server: (%08x) %s\n",
+ err, PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT));
+ }
+ }
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+
+ return returnCode;
+}
+
diff --git a/netwerk/test/TestURLManipulation.html b/netwerk/test/TestURLManipulation.html
new file mode 100644
index 0000000000..fd58652b7b
--- /dev/null
+++ b/netwerk/test/TestURLManipulation.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <title>URL manipulation</title>
+ <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+
+ <script type="text/javascript">
+ var gIOService = null;
+ function getIOService()
+ {
+ if (gIOService)
+ return gIOService;
+
+ try {
+ gIOService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ } catch(e) { dump("problem creating nsIURL for: "+inURLString+"\n"); }
+
+ return gIOService;
+ }
+
+ function getnsIURL(inURLString)
+ {
+ var URL = null;
+ var ioserv = getIOService();
+ try {
+ var URI = ioserv.newURI(inURLString, "", null);
+ URL = URI.QueryInterface(Components.interfaces.nsIURL);
+ } catch(e) { dump("problem creating nsIURL for: "+inURLString+"\n"); }
+ return URL;
+ }
+
+ function getCommonSpec()
+ {
+ var URL1 = getnsIURL(document.foo.baseEdit.value);
+ var URL2 = getnsIURL(document.foo.compareEdit.value);
+ var result = "";
+ try {
+ result = URL1.getCommonBaseSpec(URL2);
+ } catch(e) { dump("problem with getCommonSpec ("+e+")\n"); }
+ document.foo.resultEdit.value = result;
+ }
+
+ function getRelativeSpec()
+ {
+ var URL1 = getnsIURL(document.foo.baseEdit.value);
+ var URL2 = getnsIURL(document.foo.compareEdit.value);
+ var result = "";
+ try {
+ result = URL1.getRelativeSpec(URL2);
+ } catch(e) { dump("problem with getRelativeSpec ("+e+")\n"); }
+ document.foo.resultEdit.value = result;
+ }
+
+ function doResolve()
+ {
+ var URL = getnsIURL(document.foo.baseEdit.value);
+ var result = "";
+ try {
+ result = URL.resolve(document.foo.resultEdit.value);
+ } catch(e) { dump("problem with getRelativeSpec ("+e+")\n"); }
+ document.foo.compareEdit.value = result;
+ }
+ </script>
+</head>
+<body>
+<h1>testing of URL manipulation:</h1>
+<p>
+ <form name="foo">
+ <p>
+ <label for="baseEdit">base url (absolute)</label><br>
+ <input type="input" name="baseEdit" size="80" value="http://www.mozilla.org/">
+
+ <p>
+ <label for="compareEdit">comparison uri (absolute)</label><br>
+ <input type="input" name="compareEdit" size="80">
+
+ <p>
+ <label for="resultEdit">resolved url</label><br>
+ <input type="input" name="resultEdit" size="80">
+
+ <p>
+ <input type="button" onclick="getCommonSpec();" value="Get Common Spec">
+ <input type="button" onclick="getRelativeSpec();" value="Get Relative Spec">
+ <input type="button" onclick="doResolve();" value="Resolve">
+ <h5> note: results from "resolve" are placed in "comparison uri" edit field</h5>
+ </form>
+<p>
+<br>
+
+<h3>notes for testing</h3>
+different types of uris:<br>
+<ul>
+ <li>about:</li>
+ <li>about:blank</li>
+ <li>mailbox://nsmail-2.mcom.com/xxx</li>
+ <li>mailto:brade@netscape.com)</li>
+ <li>junk</li>
+ <li>http://foo/</li>
+ <li>http://foo.com/</li>
+ <li>https://foo.com/</li>
+ <li>ftp://ftp.mozilla.org/</li>
+ <li>http://foo.com:8080/</li>
+ <li>http://brade@foo.com/</li>
+ <li>http://brade:password@foo.com/</li>
+ <li>http://brade:@foo.com:8080/</li>
+ <li>file:///</li>
+ <li>file:///Quest/Desktop%20Folder/test.html</li>
+</ul>
+other variations:<br>
+<ul>
+ <li>sub-directories on above list</li>
+ <li>files on above list</li>
+ <li>sub-directories and files on above list<br>
+ </li>
+ <li>directories which don't end in a '/'</li>
+ <li>files with queries</li>
+ <li>files with no extension</li>
+ <li>files with references</li>
+ <li>files with params</li>
+ <li>other schemes (chrome, ldap, news, finger, etc.)<br>
+ </li>
+</ul>
+<br>
+This should be true:<br>
+&nbsp; resultString = baseURL.getRelativeSpec(URL);<br>
+&lt;==&gt;<br>
+&nbsp; baseURL.resolve(resultString) == URL.spec;<br>
+</body>
+</html>
diff --git a/netwerk/test/TestURLParser.cpp b/netwerk/test/TestURLParser.cpp
new file mode 100644
index 0000000000..43f126e727
--- /dev/null
+++ b/netwerk/test/TestURLParser.cpp
@@ -0,0 +1,135 @@
+#include "TestCommon.h"
+#include <stdio.h>
+#include "nsIURLParser.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+static void
+print_field(const char *label, char *str, int32_t len)
+{
+ char c = str[len];
+ str[len] = '\0';
+ printf("[%s=%s]\n", label, str);
+ str[len] = c;
+}
+
+#define PRINT_FIELD(x) \
+ print_field(# x, x, x ## Len)
+
+#define PRINT_SUBFIELD(base, x) \
+ PR_BEGIN_MACRO \
+ if (x ## Len != -1) \
+ print_field(# x, base + x ## Pos, x ## Len); \
+ PR_END_MACRO
+
+static void
+parse_authority(nsIURLParser *urlParser, char *auth, int32_t authLen)
+{
+ PRINT_FIELD(auth);
+
+ uint32_t usernamePos, passwordPos;
+ int32_t usernameLen, passwordLen;
+ uint32_t hostnamePos;
+ int32_t hostnameLen, port;
+
+ urlParser->ParseAuthority(auth, authLen,
+ &usernamePos, &usernameLen,
+ &passwordPos, &passwordLen,
+ &hostnamePos, &hostnameLen,
+ &port);
+
+ PRINT_SUBFIELD(auth, username);
+ PRINT_SUBFIELD(auth, password);
+ PRINT_SUBFIELD(auth, hostname);
+ if (port != -1)
+ printf("[port=%d]\n", port);
+}
+
+static void
+parse_file_path(nsIURLParser *urlParser, char *filepath, int32_t filepathLen)
+{
+ PRINT_FIELD(filepath);
+
+ uint32_t dirPos, basePos, extPos;
+ int32_t dirLen, baseLen, extLen;
+
+ urlParser->ParseFilePath(filepath, filepathLen,
+ &dirPos, &dirLen,
+ &basePos, &baseLen,
+ &extPos, &extLen);
+
+ PRINT_SUBFIELD(filepath, dir);
+ PRINT_SUBFIELD(filepath, base);
+ PRINT_SUBFIELD(filepath, ext);
+}
+
+static void
+parse_path(nsIURLParser *urlParser, char *path, int32_t pathLen)
+{
+ PRINT_FIELD(path);
+
+ uint32_t filePos, queryPos, refPos;
+ int32_t fileLen, queryLen, refLen;
+
+ urlParser->ParsePath(path, pathLen,
+ &filePos, &fileLen,
+ &queryPos, &queryLen,
+ &refPos, &refLen);
+
+ if (fileLen != -1)
+ parse_file_path(urlParser, path + filePos, fileLen);
+ PRINT_SUBFIELD(path, query);
+ PRINT_SUBFIELD(path, ref);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ if (argc < 2) {
+ printf("usage: TestURLParser [-std|-noauth|-auth] <url>\n");
+ return -1;
+ }
+ nsCOMPtr<nsIURLParser> urlParser;
+ if (strcmp(argv[1], "-noauth") == 0) {
+ urlParser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID);
+ argv[1] = argv[2];
+ }
+ else if (strcmp(argv[1], "-auth") == 0) {
+ urlParser = do_GetService(NS_AUTHURLPARSER_CONTRACTID);
+ argv[1] = argv[2];
+ }
+ else {
+ urlParser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ if (strcmp(argv[1], "-std") == 0)
+ argv[1] = argv[2];
+ else
+ printf("assuming -std\n");
+ }
+ if (urlParser) {
+ printf("have urlParser @%p\n", static_cast<void*>(urlParser.get()));
+
+ char *spec = argv[1];
+ uint32_t schemePos, authPos, pathPos;
+ int32_t schemeLen, authLen, pathLen;
+
+ urlParser->ParseURL(spec, -1,
+ &schemePos, &schemeLen,
+ &authPos, &authLen,
+ &pathPos, &pathLen);
+
+ if (schemeLen != -1)
+ PRINT_SUBFIELD(spec, scheme);
+ if (authLen != -1)
+ parse_authority(urlParser, spec + authPos, authLen);
+ if (pathLen != -1)
+ parse_path(urlParser, spec + pathPos, pathLen);
+ }
+ else
+ printf("no urlParser\n");
+ return 0;
+}
diff --git a/netwerk/test/TestUpload.cpp b/netwerk/test/TestUpload.cpp
new file mode 100644
index 0000000000..5818a5ccb0
--- /dev/null
+++ b/netwerk/test/TestUpload.cpp
@@ -0,0 +1,169 @@
+/* -*- 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 "TestCommon.h"
+#include <algorithm>
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "nsIComponentRegistrar.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIServiceManager.h"
+#include "nsNetUtil.h"
+
+#include "nsIUploadChannel.h"
+
+#include "NetwerkTestLogging.h"
+//
+// set NSPR_LOG_MODULES=Test:5
+//
+static PRLogModuleInfo *gTestLog = nullptr;
+#define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// InputTestConsumer
+//-----------------------------------------------------------------------------
+
+class InputTestConsumer : public nsIStreamListener
+{
+ virtual ~InputTestConsumer();
+
+public:
+
+ InputTestConsumer();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+};
+
+InputTestConsumer::InputTestConsumer()
+{
+}
+
+InputTestConsumer::~InputTestConsumer() = default;
+
+NS_IMPL_ISUPPORTS(InputTestConsumer,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
+{
+ LOG(("InputTestConsumer::OnStartRequest\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputTestConsumer::OnDataAvailable(nsIRequest *request,
+ nsISupports* context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ char buf[1025];
+ uint32_t amt, size;
+ nsresult rv;
+
+ while (aLength) {
+ size = std::min<uint32_t>(aLength, sizeof(buf));
+ rv = aIStream->Read(buf, size, &amt);
+ if (NS_FAILED(rv)) {
+ NS_ASSERTION((NS_BASE_STREAM_WOULD_BLOCK != rv),
+ "The stream should never block.");
+ return rv;
+ }
+ aLength -= amt;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InputTestConsumer::OnStopRequest(nsIRequest *request, nsISupports* context,
+ nsresult aStatus)
+{
+ LOG(("InputTestConsumer::OnStopRequest [status=%x]\n", aStatus));
+ QuitPumpingEvents();
+ return NS_OK;
+}
+
+
+int
+main(int argc, char* argv[])
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ nsresult rv;
+
+ if (argc < 2) {
+ printf("usage: %s <url> <file-to-upload>\n", argv[0]);
+ return -1;
+ }
+ char* uriSpec = argv[1];
+ char* fileName = argv[2];
+
+ gTestLog = PR_NewLogModule("Test");
+
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ // first thing to do is create ourselves a stream that
+ // is to be uploaded.
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv = NS_NewPostDataStream(getter_AddRefs(uploadStream),
+ true,
+ nsDependentCString(fileName)); // XXX UTF-8
+ if (NS_FAILED(rv)) return -1;
+
+ // create our url.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_FAILED(rv)) return -1;
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return -1;
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ if (NS_FAILED(rv)) return -1;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) return -1;
+
+ // QI and set the upload stream
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(channel));
+ uploadChannel->SetUploadStream(uploadStream, EmptyCString(), -1);
+
+ // create a dummy listener
+ InputTestConsumer* listener;
+
+ listener = new InputTestConsumer;
+ if (!listener) {
+ NS_ERROR("Failed to create a new stream listener!");
+ return -1;
+ }
+ NS_ADDREF(listener);
+
+ channel->AsyncOpen2(listener);
+
+ PumpEvents();
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ rv = NS_ShutdownXPCOM(nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+
+ return 0;
+}
+
diff --git a/netwerk/test/TestWriteSpeed.cpp b/netwerk/test/TestWriteSpeed.cpp
new file mode 100644
index 0000000000..0b0260fdd5
--- /dev/null
+++ b/netwerk/test/TestWriteSpeed.cpp
@@ -0,0 +1,116 @@
+/* -*- 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 "prio.h"
+#include "prinrval.h"
+#include "prmem.h"
+#include <stdio.h>
+#include <math.h>
+
+void
+NS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues,
+ double *meanResult, double *stdDevResult)
+{
+ double mean = 0.0, var = 0.0, stdDev = 0.0;
+ if (n > 0.0 && sumOfValues >= 0) {
+ mean = sumOfValues / n;
+ double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues);
+ if (temp < 0.0 || n <= 1)
+ var = 0.0;
+ else
+ var = temp / (n * (n - 1));
+ // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
+ stdDev = var != 0.0 ? sqrt(var) : 0.0;
+ }
+ *meanResult = mean;
+ *stdDevResult = stdDev;
+}
+
+int
+Test(const char* filename, int32_t minSize, int32_t maxSize,
+ int32_t sizeIncrement, int32_t iterations)
+{
+ fprintf(stdout, " size write: mean stddev iters total: mean stddev iters\n");
+ for (int32_t size = minSize; size <= maxSize; size += sizeIncrement) {
+ // create a buffer of stuff to write
+ char* buf = (char*)PR_Malloc(size);
+ if (buf == nullptr)
+ return -1;
+
+ // initialize it with a pattern
+ int32_t i;
+ char hex[] = "0123456789ABCDEF";
+ for (i = 0; i < size; i++) {
+ buf[i] = hex[i & 0xF];
+ }
+
+ double writeCount = 0, writeRate = 0, writeRateSquared = 0;
+ double totalCount = 0, totalRate = 0, totalRateSquared = 0;
+ for (i = 0; i < iterations; i++) {
+ PRIntervalTime start = PR_IntervalNow();
+
+ char name[1024];
+ sprintf(name, "%s_%d", filename, i);
+ PRFileDesc* fd = PR_Open(name, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0664);
+ if (fd == nullptr)
+ return -1;
+
+ PRIntervalTime writeStart = PR_IntervalNow();
+ int32_t rv = PR_Write(fd, buf, size);
+ if (rv < 0) return rv;
+ if (rv != size) return -1;
+ PRIntervalTime writeStop = PR_IntervalNow();
+
+ PRStatus st = PR_Close(fd);
+ if (st == PR_FAILURE) return -1;
+
+ PRIntervalTime stop = PR_IntervalNow();
+
+ PRIntervalTime writeTime = PR_IntervalToMilliseconds(writeStop - writeStart);
+ if (writeTime > 0) {
+ double wr = size / writeTime;
+ writeRate += wr;
+ writeRateSquared += wr * wr;
+ writeCount++;
+ }
+
+ PRIntervalTime totalTime = PR_IntervalToMilliseconds(stop - start);
+ if (totalTime > 0) {
+ double t = size / totalTime;
+ totalRate += t;
+ totalRateSquared += t * t;
+ totalCount++;
+ }
+ }
+
+ PR_Free(buf);
+
+ double writeMean, writeStddev;
+ double totalMean, totalStddev;
+ NS_MeanAndStdDev(writeCount, writeRate, writeRateSquared,
+ &writeMean, &writeStddev);
+ NS_MeanAndStdDev(totalCount, totalRate, totalRateSquared,
+ &totalMean, &totalStddev);
+ fprintf(stdout, "%10d %10.2f %10.2f %10d %10.2f %10.2f %10d\n",
+ size, writeMean, writeStddev, (int32_t)writeCount,
+ totalMean, totalStddev, (int32_t)totalCount);
+ }
+ return 0;
+}
+
+int
+main(int argc, char* argv[])
+{
+ if (argc != 5) {
+ printf("usage: %s <min buf size (K)> <max buf size (K)> <size increment (K)> <iterations>\n", argv[0]);
+ return -1;
+ }
+ Test("y:\\foo",
+ atoi(argv[1]) * 1024,
+ atoi(argv[2]) * 1024,
+ atoi(argv[3]) * 1024,
+ atoi(argv[4]));
+ return 0;
+}
diff --git a/netwerk/test/browser/browser.ini b/netwerk/test/browser/browser.ini
new file mode 100644
index 0000000000..8611891fd7
--- /dev/null
+++ b/netwerk/test/browser/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ dummy.html
+
+[browser_about_cache.js]
+[browser_NetUtil.js]
+[browser_child_resource.js]
+skip-if = e10s && debug && os == "linux" && bits == 64
+[browser_post_file.js]
+[browser_nsIFormPOSTActionChannel.js]
+skip-if = e10s # protocol handler and channel does not work in content process
diff --git a/netwerk/test/browser/browser_NetUtil.js b/netwerk/test/browser/browser_NetUtil.js
new file mode 100644
index 0000000000..a6c4f2bcd6
--- /dev/null
+++ b/netwerk/test/browser/browser_NetUtil.js
@@ -0,0 +1,92 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // We overload this test to include verifying that httpd.js is
+ // importable as a testing-only JS module.
+ Components.utils.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: XPCOMUtils.generateQI([Ci.nsIProgressEventSink,
+ Ci.nsIInterfaceRequestor]),
+ getInterface: function (aIID) { return this.QueryInterface(aIID); },
+ onProgress: function () {},
+ onStatus: function () {}
+ };
+ 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: function(aXULWindow) {
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ var self = this;
+ domwindow.addEventListener("load", function() {
+ domwindow.removeEventListener("load", arguments.callee, false);
+
+ if (domwindow.document.location.href != self.url)
+ return;
+
+ // Allow other window load listeners to execute before passing to callback
+ executeSoon(function() {
+ self.callback(domwindow);
+ });
+ }, false);
+ },
+ onCloseWindow: function(aXULWindow) {},
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {}
+}
diff --git a/netwerk/test/browser/browser_about_cache.js b/netwerk/test/browser/browser_about_cache.js
new file mode 100644
index 0000000000..38cfa3d028
--- /dev/null
+++ b/netwerk/test/browser/browser_about_cache.js
@@ -0,0 +1,71 @@
+"use strict";
+
+/**
+ * Open a dummy page, then open about:cache and verify the opened page shows up in the cache.
+ */
+add_task(function*() {
+ 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 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kTestPage, true);
+ yield BrowserTestUtils.removeTab(tab);
+
+ tab = yield 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);
+ yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache should not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ let channel = content.document.docShell.currentDocumentChannel;
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ is(principalURI && principalURI.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();
+ });
+ yield 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);
+ yield ContentTask.spawn(tab.linkedBrowser, kTestPage, function(kTestPage) {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache with query params should still not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+ let channel = content.document.docShell.currentDocumentChannel;
+ principalURI = channel.loadInfo.triggeringPrincipal.URI;
+ is(principalURI && principalURI.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();
+ });
+ yield entryLoaded;
+ info("about:cache entry loaded");
+
+
+ yield ContentTask.spawn(tab.linkedBrowser, triggeringURISpec, function(triggeringURISpec) {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache-entry should also not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+ let channel = content.document.docShell.currentDocumentChannel;
+ principalURI = channel.loadInfo.triggeringPrincipal.URI;
+ is(principalURI && principalURI.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.");
+ });
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/test/browser/browser_child_resource.js b/netwerk/test/browser/browser_child_resource.js
new file mode 100644
index 0000000000..098e6bd844
--- /dev/null
+++ b/netwerk/test/browser/browser_child_resource.js
@@ -0,0 +1,256 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+// 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" : "";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+const resProtocol = Cc["@mozilla.org/network/protocol;1?name=resource"]
+ .getService(Ci.nsIResProtocolHandler);
+
+function getMinidumpDirectory() {
+ var dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ dir.append("minidumps");
+ return dir;
+}
+
+// This observer is needed so we can clean up all evidence of the crash so
+// the testrunner thinks things are peachy.
+var CrashObserver = {
+ observe: function(subject, topic, data) {
+ is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+ ok(subject instanceof Ci.nsIPropertyBag2,
+ 'Subject implements nsIPropertyBag2.');
+ // we might see this called as the process terminates due to previous tests.
+ // We are only looking for "abnormal" exits...
+ if (!subject.hasKey("abnormal")) {
+ info("This is a normal termination and isn't the one we are looking for...");
+ return;
+ }
+
+ var dumpID;
+ if ('nsICrashReporter' in Ci) {
+ dumpID = subject.getPropertyAsAString('dumpID');
+ ok(dumpID, "dumpID is present and not an empty string");
+ }
+
+ if (dumpID) {
+ var minidumpDirectory = getMinidumpDirectory();
+ let file = minidumpDirectory.clone();
+ file.append(dumpID + '.dmp');
+ file.remove(true);
+ file = minidumpDirectory.clone();
+ file.append(dumpID + '.extra');
+ file.remove(true);
+ }
+ }
+}
+Services.obs.addObserver(CrashObserver, 'ipc:content-shutdown', false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(CrashObserver, 'ipc:content-shutdown');
+});
+
+function frameScript() {
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ let resProtocol = Components.classes["@mozilla.org/network/protocol;1?name=resource"]
+ .getService(Components.interfaces.nsIResProtocolHandler);
+
+ addMessageListener("Test:ResolveURI", function({ data: uri }) {
+ uri = Services.io.newURI(uri, null, null);
+ try {
+ let resolved = resProtocol.resolveURI(uri);
+ sendAsyncMessage("Test:ResolvedURI", resolved);
+ }
+ catch (e) {
+ sendAsyncMessage("Test:ResolvedURI", null);
+ }
+ });
+
+ addMessageListener("Test:Crash", function() {
+ dump("Crashing\n");
+ privateNoteIntentionalCrash();
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+ });
+}
+
+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, null, null);
+ try {
+ return resProtocol.resolveURI(uri);
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+function remoteResolveURI(uri) {
+ return new Promise((resolve) => {
+ let manager = gBrowser.selectedBrowser.messageManager;
+
+ function listener({ data: resolved }) {
+ manager.removeMessageListener("Test:ResolvedURI", listener);
+ resolve(resolved);
+ }
+
+ manager.addMessageListener("Test:ResolvedURI", listener);
+ manager.sendAsyncMessage("Test:ResolveURI", uri);
+ });
+}
+
+var loadTestTab = Task.async(function*() {
+ gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ return browser;
+});
+
+// Restarts the child process by crashing it then reloading the tab
+var restart = Task.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;
+
+ browser.messageManager.sendAsyncMessage("Test:Crash");
+ yield waitForEvent(browser, "AboutTabCrashedLoad", false, true);
+
+ browser.reload();
+
+ yield BrowserTestUtils.browserLoaded(browser);
+ is(browser.getAttribute("remote"), expectedRemote, "Browser should be in the right process");
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ return browser;
+});
+
+// Sanity check that this test is going to be useful
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ // This must be loaded in the remote process for this test to be useful
+ is(browser.getAttribute("remote"), expectedRemote, "Browser should be in the right process");
+
+ let local = resolveURI("resource://gre/modules/Services.jsm");
+ let remote = yield 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(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ let local = resolveURI("resource://testing/test.js");
+ let remote = yield 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", null, null));
+ local = resolveURI("resource://testing/test.js");
+ remote = yield 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 = yield 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(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ let local = resolveURI("resource://testing/test.js");
+ let remote = yield 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");
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield 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", null, null));
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield 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);
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield 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(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ resProtocol.setSubstitution("testing2", Services.io.newURI("resource://testing", null, null));
+ let local = resolveURI("resource://testing2/test.js");
+ let remote = yield 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 = yield 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 = yield 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_nsIFormPOSTActionChannel.js b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
new file mode 100644
index 0000000000..150c4feca9
--- /dev/null
+++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
@@ -0,0 +1,284 @@
+/*
+ * Tests for bug 1241377: A channel with nsIFormPOSTActionChannel interface
+ * should be able to accept form POST.
+ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+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;
+ },
+ newURI: function(aSpec, aOriginCharset, aBaseURI) {
+ var uri = Cc["@mozilla.org/network/standard-url;1"].
+ createInstance(Ci.nsIURI);
+ uri.spec = aSpec;
+ return uri;
+ },
+ newChannel2: function(aURI, aLoadInfo) {
+ return new CustomChannel(aURI, aLoadInfo);
+ },
+ newChannel: function(aURI) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ allowPort: function(port, scheme) {
+ return port != -1;
+ },
+
+ /** nsIFactory */
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ lockFactory: function() {},
+
+ /** nsISupports */
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler,
+ Ci.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 = XPCOMUtils.generateQI(interfaces);
+}
+CustomChannel.prototype = {
+ /** nsIUploadChannel */
+ get uploadStream() {
+ return this._uploadStream;
+ },
+ set uploadStream(val) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ setUploadStream: function(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 Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ open: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ open2: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ asyncOpen: function(aListener, aContext) {
+ 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 = "";
+ if (this._uploadStream) {
+ var bstream = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ bstream.setInputStream(this._uploadStream);
+ postData = bstream.readBytes(bstream.available());
+ }
+ data += `
+<input id="upload_stream" value="${this._uploadStream ? "yes" : "no"}">
+<input id="post_data" value="${btoa(postData)}">
+`;
+ }
+
+ 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, aContext);
+ } catch(e) {}
+ try {
+ aListener.onDataAvailable(this, aContext, stream, 0, stream.available());
+ } catch(e) {}
+ try {
+ aListener.onStopRequest(this, aContext, Cr.NS_OK);
+ } catch(e) {}
+ }
+ };
+ Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ },
+ asyncOpen2: function(aListener) {
+ this.asyncOpen(aListener, null);
+ },
+
+ /** nsIRequest */
+ get name() {
+ return this.uri.spec;
+ },
+ isPending: function () {
+ return false;
+ },
+ get status() {
+ return Cr.NS_OK;
+ },
+ cancel: function(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");
+ if (upload_stream && post_data) {
+ sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value]);
+ return;
+ }
+ }
+ }
+
+ setTimeout(check, 100);
+ };
+
+ check();
+ });
+}
+
+function loadTestTab(uri) {
+ gBrowser.selectedTab = gBrowser.addTab(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] }) {
+ manager.removeMessageListener("Test:IFrameLoaded", listener);
+ resolve([hasUploadStream, atob(postData)]);
+ }
+
+ manager.addMessageListener("Test:IFrameLoaded", listener);
+ manager.sendAsyncMessage("Test:WaitForIFrame");
+ });
+}
+
+add_task(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(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(NORMAL_FORM_URI);
+ is(hasUploadStream, "no", "normal action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(UPLOAD_FORM_URI);
+ is(hasUploadStream, "no", "upload action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(POST_FORM_URI);
+ is(hasUploadStream, "yes", "post action should have uploadStream");
+ is(postData,
+ "Content-Type: text/plain\r\n" +
+ "Content-Length: 9\r\n" +
+ "\r\n" +
+ "foo=bar\r\n", "POST data is received correctly");
+
+ 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..6c9fd7a2ff
--- /dev/null
+++ b/netwerk/test/browser/browser_post_file.js
@@ -0,0 +1,101 @@
+/*
+ * Tests for bug 1241100: Post to local file should not overwrite the file.
+ */
+
+Components.utils.import("resource://gre/modules/osfile.jsm");
+
+function* createTestFile(filename, content) {
+ let path = OS.Path.join(OS.Constants.Path.tmpDir, filename);
+ yield OS.File.writeAtomic(path, content);
+ return path;
+}
+
+function* readFile(path) {
+ var array = yield OS.File.read(path);
+ var decoder = new TextDecoder();
+ return decoder.decode(array);
+}
+
+function frameScript() {
+ addMessageListener("Test:WaitForIFrame", function() {
+ var check = function() {
+ if (content) {
+ var frame = content.document.getElementById("frame");
+ if (frame) {
+ var okBox = frame.contentDocument.getElementById("action_file_ok");
+ if (okBox) {
+ sendAsyncMessage("Test:IFrameLoaded");
+ return;
+ }
+ }
+ }
+
+ setTimeout(check, 100);
+ };
+
+ check();
+ });
+}
+
+add_task(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 = yield* createTestFile(postFilename, postFileContent);
+ var actionPath = yield* createTestFile(actionFilename, actionFileContent);
+
+ var postURI = OS.Path.toFileURI(postPath);
+
+ gBrowser.selectedTab = gBrowser.addTab(postURI);
+ let browser = gBrowser.selectedBrowser;
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ yield new Promise(resolve => {
+ let manager = browser.messageManager;
+
+ function listener() {
+ manager.removeMessageListener("Test:IFrameLoaded", listener);
+ resolve();
+ }
+
+ manager.addMessageListener("Test:IFrameLoaded", listener);
+ manager.sendAsyncMessage("Test:WaitForIFrame");
+ });
+
+ var actionFileContentAfter = yield* readFile(actionPath);
+ is(actionFileContentAfter, actionFileContent, "action file is not modified");
+
+ yield OS.File.remove(postPath);
+ yield OS.File.remove(actionPath);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/dummy.html b/netwerk/test/browser/dummy.html
new file mode 100644
index 0000000000..6b28a248fb
--- /dev/null
+++ b/netwerk/test/browser/dummy.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
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/785753-1.html b/netwerk/test/crashtests/785753-1.html
new file mode 100644
index 0000000000..7ccb823555
--- /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.parentNode.removeChild(test3)}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; -moz-column-span:483; }",styleSheet0.cssRules.length);}catch(e){}
+try{test0.parentNode.removeChild(test0)}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.parentNode.removeChild(test3)}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.parentNode.removeChild(test3)}catch(e){};
+},1);
+
+setTimeout(function(){
+try{test1.parentNode.removeChild(test1)}catch(e){};
+try{test0.innerHtml=test2.innerHtml;}catch(e){}
+try{test1.parentNode.removeChild(test1)}catch(e){};
+try{test0.parentNode.removeChild(test0)}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; -moz-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.parentNode.removeChild(test0)}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.parentNode.removeChild(test1)}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.parentNode.removeChild(test3)}catch(e){};
+},3);
+
+setTimeout(function(){
+try{test1.parentNode.removeChild(test1)}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.parentNode.removeChild(test2)}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.parentNode.removeChild(test2)}catch(e){};
+document.execCommand("Copy", true);
+try{test3.style['letter-spacing']='102px';}catch(e){}
+try{test2.parentNode.removeChild(test2)}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..564df22b9d
--- /dev/null
+++ b/netwerk/test/crashtests/crashtests.list
@@ -0,0 +1,4 @@
+load 785753-1.html
+load 785753-2.html
+load 1274044-1.html
+skip-if(Android) pref(privacy.firstparty.isolate,true) load 1334468-1.html
diff --git a/netwerk/test/gtest/TestProtocolProxyService.cpp b/netwerk/test/gtest/TestProtocolProxyService.cpp
new file mode 100644
index 0000000000..a49e9f9617
--- /dev/null
+++ b/netwerk/test/gtest/TestProtocolProxyService.cpp
@@ -0,0 +1,128 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsIURL.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "../../base/nsProtocolProxyService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Preferences.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<nsIURL> url( do_CreateInstance(NS_STANDARDURL_CONTRACTID) );
+ ASSERT_TRUE(url) << "couldn't create URL";
+
+ nsAutoCString spec;
+
+ auto CheckLoopbackURLs = [&](bool expected)
+ {
+ // loopback IPs are always filtered
+ spec = "http://127.0.0.1";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ spec = "http://[::1]";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckURLs = [&](bool expected)
+ {
+ spec = "http://example.com";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "https://10.2.3.4";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 443), expected);
+
+ spec = "http://1.2.3.4";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://1.2.3.4:8080";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://[2001::1]";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://2.3.4.5:7777";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://[abcd::2]:123";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+ spec = "http://bla.test.com";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckPortDomain = [&](bool expected)
+ {
+ spec = "http://blabla.com:10";
+ ASSERT_EQ(url->SetSpec(spec), NS_OK);
+ ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+ };
+
+ auto CheckLocalDomain = [&](bool expected)
+ {
+ spec = "http://test";
+ ASSERT_EQ(url->SetSpec(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(true); // only time when loopbacks can be proxied. bug?
+ 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);
+ // Check URLs can no longer use filtered proxy
+ CheckURLs(false);
+ CheckLoopbackURLs(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);
+ CheckURLs(true);
+ CheckLoopbackURLs(false);
+ 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);
+}
+
+} // namespace net
+} // namespace mozila
diff --git a/netwerk/test/gtest/TestStandardURL.cpp b/netwerk/test/gtest/TestStandardURL.cpp
new file mode 100644
index 0000000000..ccab556a96
--- /dev/null
+++ b/netwerk/test/gtest/TestStandardURL.cpp
@@ -0,0 +1,69 @@
+#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsIURL.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+
+TEST(TestStandardURL, Simple) {
+ nsCOMPtr<nsIURL> url( do_CreateInstance(NS_STANDARDURL_CONTRACTID) );
+ ASSERT_TRUE(url);
+ ASSERT_EQ(url->SetSpec(NS_LITERAL_CSTRING("http://example.com")), NS_OK);
+
+ nsAutoCString out;
+
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("http://example.com/"));
+
+ ASSERT_EQ(url->Resolve(NS_LITERAL_CSTRING("foo.html?q=45"), out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("http://example.com/foo.html?q=45"));
+
+ ASSERT_EQ(url->SetScheme(NS_LITERAL_CSTRING("foo")), NS_OK);
+
+ ASSERT_EQ(url->GetScheme(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("foo"));
+
+ ASSERT_EQ(url->GetHost(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("example.com"));
+ ASSERT_EQ(url->SetHost(NS_LITERAL_CSTRING("www.yahoo.com")), NS_OK);
+ ASSERT_EQ(url->GetHost(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("www.yahoo.com"));
+
+ ASSERT_EQ(url->SetPath(NS_LITERAL_CSTRING("/some-path/one-the-net/about.html?with-a-query#for-you")), NS_OK);
+ ASSERT_EQ(url->GetPath(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("/some-path/one-the-net/about.html?with-a-query#for-you"));
+
+ ASSERT_EQ(url->SetQuery(NS_LITERAL_CSTRING("a=b&d=c&what-ever-you-want-to-be-called=45")), NS_OK);
+ ASSERT_EQ(url->GetQuery(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("a=b&d=c&what-ever-you-want-to-be-called=45"));
+
+ ASSERT_EQ(url->SetRef(NS_LITERAL_CSTRING("#some-book-mark")), NS_OK);
+ ASSERT_EQ(url->GetRef(out), NS_OK);
+ ASSERT_TRUE(out == NS_LITERAL_CSTRING("some-book-mark"));
+}
+
+#define COUNT 10000
+
+MOZ_GTEST_BENCH(TestStandardURL, Perf, [] {
+ nsCOMPtr<nsIURL> url( do_CreateInstance(NS_STANDARDURL_CONTRACTID) );
+ ASSERT_TRUE(url);
+ nsAutoCString out;
+
+ for (int i = COUNT; i; --i) {
+ ASSERT_EQ(url->SetSpec(NS_LITERAL_CSTRING("http://example.com")), NS_OK);
+ ASSERT_EQ(url->GetSpec(out), NS_OK);
+ url->Resolve(NS_LITERAL_CSTRING("foo.html?q=45"), out);
+ url->SetScheme(NS_LITERAL_CSTRING("foo"));
+ url->GetScheme(out);
+ url->SetHost(NS_LITERAL_CSTRING("www.yahoo.com"));
+ url->GetHost(out);
+ url->SetPath(NS_LITERAL_CSTRING("/some-path/one-the-net/about.html?with-a-query#for-you"));
+ url->GetPath(out);
+ url->SetQuery(NS_LITERAL_CSTRING("a=b&d=c&what-ever-you-want-to-be-called=45"));
+ url->GetQuery(out);
+ url->SetRef(NS_LITERAL_CSTRING("#some-book-mark"));
+ url->GetRef(out);
+ }
+});
diff --git a/netwerk/test/gtest/moz.build b/netwerk/test/gtest/moz.build
new file mode 100644
index 0000000000..6e6c801521
--- /dev/null
+++ b/netwerk/test/gtest/moz.build
@@ -0,0 +1,14 @@
+# -*- 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 += [
+ 'TestProtocolProxyService.cpp',
+ 'TestStandardURL.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/netwerk/test/httpserver/README b/netwerk/test/httpserver/README
new file mode 100644
index 0000000000..e253c6d471
--- /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, create an instance of the server using the following command:
+
+ var server = Components.classes["@mozilla.org/server/jshttp;1"]
+ .createInstance(Components.interfaces.nsIHttpServer);
+
+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..5542adfc2e
--- /dev/null
+++ b/netwerk/test/httpserver/httpd.js
@@ -0,0 +1,5376 @@
+/* -*- 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.
+ */
+
+this.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",
+];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+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 = this;
+
+/**
+ * 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 Cr.NS_ERROR_ABORT;
+ }
+}
+
+/** Constructs an HTTP error object. */
+this.HttpError = function HttpError(code, description)
+{
+ this.code = code;
+ this.description = description;
+}
+HttpError.prototype =
+{
+ toString: function()
+ {
+ return this.code + " " + this.description;
+ }
+};
+
+/**
+ * Errors thrown to trigger specific HTTP server responses.
+ */
+this.HTTP_400 = new HttpError(400, "Bad Request");
+this.HTTP_401 = new HttpError(401, "Unauthorized");
+this.HTTP_402 = new HttpError(402, "Payment Required");
+this.HTTP_403 = new HttpError(403, "Forbidden");
+this.HTTP_404 = new HttpError(404, "Not Found");
+this.HTTP_405 = new HttpError(405, "Method Not Allowed");
+this.HTTP_406 = new HttpError(406, "Not Acceptable");
+this.HTTP_407 = new HttpError(407, "Proxy Authentication Required");
+this.HTTP_408 = new HttpError(408, "Request Timeout");
+this.HTTP_409 = new HttpError(409, "Conflict");
+this.HTTP_410 = new HttpError(410, "Gone");
+this.HTTP_411 = new HttpError(411, "Length Required");
+this.HTTP_412 = new HttpError(412, "Precondition Failed");
+this.HTTP_413 = new HttpError(413, "Request Entity Too Large");
+this.HTTP_414 = new HttpError(414, "Request-URI Too Long");
+this.HTTP_415 = new HttpError(415, "Unsupported Media Type");
+this.HTTP_417 = new HttpError(417, "Expectation Failed");
+
+this.HTTP_500 = new HttpError(500, "Internal Server Error");
+this.HTTP_501 = new HttpError(501, "Not Implemented");
+this.HTTP_502 = new HttpError(502, "Bad Gateway");
+this.HTTP_503 = new HttpError(503, "Service Unavailable");
+this.HTTP_504 = new HttpError(504, "Gateway Timeout");
+this.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;
+
+/** The XPCOM prefs service. */
+var gRootPrefBranch = null;
+function getRootPrefBranch()
+{
+ if (!gRootPrefBranch)
+ {
+ gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ }
+ return gRootPrefBranch;
+}
+
+/**
+ * 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 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 =
+{
+ classID: Components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"),
+
+ // 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: function(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);
+ 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: function(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: function()
+ {
+ dumpn("*** _notifyStopped async callback");
+ self._notifyStopped();
+ }
+ };
+ gThreadManager.currentThread
+ .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+
+ // NSIHTTPSERVER
+
+ //
+ // see nsIHttpServer.start
+ //
+ start: function(port)
+ {
+ this._start(port, "localhost")
+ },
+
+ _start: function(port, host)
+ {
+ if (this._socket)
+ throw 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 prefs = getRootPrefBranch();
+ var maxConnections = 5 + Math.max(
+ prefs.getIntPref("network.http.max-persistent-connections-per-server"),
+ prefs.getIntPref("network.http.max-persistent-connections-per-proxy"));
+
+ try
+ {
+ var loopback = true;
+ if (this._host != "127.0.0.1" && this._host != "localhost") {
+ var 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 ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ var socket;
+ for (var i = 100; i; i--)
+ {
+ var temp = new ServerSocket(this._port,
+ loopback, // true = localhost, false = everybody
+ maxConnections);
+
+ var allowed = ios.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?");
+ }
+
+ dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
+ " pending connections");
+ socket.asyncListen(this);
+ this._port = socket.port;
+ this._identity._initialize(socket.port, host, true);
+ this._socket = socket;
+ }
+ catch (e)
+ {
+ dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ }
+ },
+
+ //
+ // see nsIHttpServer.stop
+ //
+ stop: function(callback)
+ {
+ if (!callback)
+ throw Cr.NS_ERROR_NULL_POINTER;
+ if (!this._socket)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ 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
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile: function(path, file)
+ {
+ if (file && (!file.exists() || file.isDirectory()))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handler.registerFile(path, file);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory: function(path, directory)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/" ||
+ path.charAt(path.length - 1) != "/" ||
+ (directory &&
+ (!directory.exists() || !directory.isDirectory())))
+ throw 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: function(path, handler)
+ {
+ this._handler.registerPathHandler(path, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerPrefixHandler
+ //
+ registerPrefixHandler: function(prefix, handler)
+ {
+ this._handler.registerPrefixHandler(prefix, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler: function(code, handler)
+ {
+ this._handler.registerErrorHandler(code, handler);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler: function(handler)
+ {
+ this._handler.setIndexHandler(handler);
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType: function(ext, type)
+ {
+ this._handler.registerContentType(ext, type);
+ },
+
+ //
+ // see nsIHttpServer.serverIdentity
+ //
+ get identity()
+ {
+ return this._identity;
+ },
+
+ //
+ // see nsIHttpServer.getState
+ //
+ getState: function(path, k)
+ {
+ return this._handler._getState(path, k);
+ },
+
+ //
+ // see nsIHttpServer.setState
+ //
+ setState: function(path, k, v)
+ {
+ return this._handler._setState(path, k, v);
+ },
+
+ //
+ // see nsIHttpServer.getSharedState
+ //
+ getSharedState: function(k)
+ {
+ return this._handler._getSharedState(k);
+ },
+
+ //
+ // see nsIHttpServer.setSharedState
+ //
+ setSharedState: function(k, v)
+ {
+ return this._handler._setSharedState(k, v);
+ },
+
+ //
+ // see nsIHttpServer.getObjectState
+ //
+ getObjectState: function(k)
+ {
+ return this._handler._getObjectState(k);
+ },
+
+ //
+ // see nsIHttpServer.setObjectState
+ //
+ setObjectState: function(k, v)
+ {
+ return this._handler._setObjectState(k, v);
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpServer) ||
+ iid.equals(Ci.nsIServerSocketListener) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // 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: function()
+ {
+ return this._socketClosed && !this._hasOpenConnections();
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /** True if this server has any open connections to it, false otherwise. */
+ _hasOpenConnections: function()
+ {
+ //
+ // 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: function()
+ {
+ 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: function(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();
+ // Bug 508125: Add a GC here else we'll use gigabytes of memory running
+ // mochitests. We can't rely on xpcshell doing an automated GC, as that
+ // would interfere with testing GC stuff...
+ Components.utils.forceGC();
+ },
+
+ /**
+ * Requests that the server be shut down when possible.
+ */
+ _requestQuit: function()
+ {
+ dumpn(">>> requesting a quit");
+ dumpStack();
+ this._doQuit = true;
+ }
+};
+
+this.HttpServer = nsHttpServer;
+
+//
+// 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 Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryScheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryHost
+ //
+ get primaryHost()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryHost;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryPort
+ //
+ get primaryPort()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryPort;
+ },
+
+ //
+ // see nsIHttpServerIdentity.add
+ //
+ add: function(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: function(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: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ return "x" + host in this._locations &&
+ scheme === this._locations["x" + host][port];
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ getScheme: function(host, port)
+ {
+ this._validate("http", host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ return "";
+
+ return entry[port] || "";
+ },
+
+ //
+ // see nsIHttpServerIdentity.setPrimary
+ //
+ setPrimary: function(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: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+ * Initializes the primary name for the corresponding server, based on the
+ * provided port number.
+ */
+ _initialize: function(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")
+ 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: function()
+ {
+ 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: function(scheme, host, port)
+ {
+ if (scheme !== "http" && scheme !== "https")
+ {
+ dumpn("*** server only supports http/https schemes: '" + scheme + "'");
+ dumpStack();
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (!HOST_REGEX.test(host))
+ {
+ dumpn("*** unexpected host: '" + host + "'");
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (port < 0 || port > 65535)
+ {
+ dumpn("*** unexpected port: '" + port + "'");
+ throw 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)
+{
+ 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;
+
+ /**
+ * 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: function()
+ {
+ 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: function(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: function(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: function()
+ {
+ return "<Connection(" + this.number +
+ (this.request ? ", " + this.request.path : "") +"): " +
+ (this._closed ? "closed" : "open") + ">";
+ },
+
+ requestStarted: function()
+ {
+ 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: function(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: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIInputStreamCallback) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE API
+
+ /**
+ * Processes unprocessed, downloaded data as a request line.
+ *
+ * @returns boolean
+ * true iff the request line has been fully processed
+ */
+ _processRequestLine: function()
+ {
+ 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: function()
+ {
+ 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: function()
+ {
+ 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));
+
+ var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
+ bos.writeByteArray(data, count);
+ 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: function()
+ {
+ 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.indexOf(":");
+ if (colon < 0)
+ {
+ host = hostPort;
+ port = "";
+ }
+ else
+ {
+ host = hostPort.substring(0, colon);
+ port = hostPort.substring(colon + 1);
+ }
+
+ // NB: We allow an empty port here because, oddly, a colon may be
+ // present even without a port number, e.g. "example.com:"; in this
+ // case the default port applies.
+ if (!HOST_REGEX.test(host) || !/^\d*$/.test(port))
+ {
+ dumpn("*** malformed hostname (" + hostPort + ") in Host " +
+ "header, 400 time");
+ throw HTTP_400;
+ }
+
+ // If we're not given a port, we're stuck, because we don't know what
+ // scheme to use to look up the correct port here, in general. Since
+ // the HTTPS case requires a tunnel/proxy and thus requires that the
+ // requested URI be absolute (and thus contain the necessary
+ // information), let's assume HTTP will prevail and use that.
+ port = +port || 80;
+
+ var scheme = identity.getScheme(host, port);
+ if (!scheme)
+ {
+ dumpn("*** unrecognized hostname (" + hostPort + ") in Host " +
+ "header, 400 time");
+ throw HTTP_400;
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ }
+ }
+ else
+ {
+ NS_ASSERT(metadata._host === undefined,
+ "HTTP/1.0 doesn't allow absolute paths in the request line!");
+
+ metadata._scheme = identity.primaryScheme;
+ metadata._host = identity.primaryHost;
+ metadata._port = identity.primaryPort;
+ }
+
+ NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port),
+ "must have a location we recognize by now!");
+ },
+
+ /**
+ * Handles responses in case of error, either in the server or in the request.
+ *
+ * @param e
+ * the specific error encountered, which is an HttpError in the case where
+ * the request is in some way invalid or cannot be fulfilled; if this isn't
+ * an HttpError we're going to be paranoid and shut down, because that
+ * shouldn't happen, ever
+ */
+ _handleError: function(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: function()
+ {
+ 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: function(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 "unsupported HTTP version";
+ }
+ catch (e)
+ {
+ // we support HTTP/1.0 and HTTP/1.1 only
+ throw HTTP_501;
+ }
+
+
+ var fullPath = request[1];
+ 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 = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(fullPath, null, null);
+ fullPath = uri.path;
+ 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: function()
+ {
+ 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: function(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: function(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: function()
+ {
+ 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.getNext().QueryInterface(Ci.nsIFile);
+ var 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
+ {
+ var 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->nsILocalFile 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: function(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: function(path, file)
+ {
+ 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;
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ self._writeFileResponse(request, file, response, 0, file.fileSize);
+ };
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler: function(path, handler)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handlerToField(handler, this._overridePaths, path);
+ },
+
+ //
+ // see nsIHttpServer.registerPrefixHandler
+ //
+ registerPrefixHandler: function(path, handler)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handlerToField(handler, this._overridePrefixes, path);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory: function(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 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: function(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: function(handler)
+ {
+ if (!handler)
+ handler = defaultIndexHandler;
+ else if (typeof(handler) != "function")
+ handler = createHandlerFunc(handler);
+
+ this._indexHandler = handler;
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType: function(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: function(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: function(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 : nsILocalFile
+ * 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: function(metadata, file, response, offset, count)
+ {
+ const PR_RDONLY = 0x01;
+
+ var type = this._getTypeFromFile(file);
+ if (type === SJS_TYPE)
+ {
+ var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ try
+ {
+ var sis = new ScriptableInputStream(fis);
+ 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;
+ Cu.evalInSandbox(sis.read(file.fileSize), s, "latest");
+ }
+ 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);
+
+ var 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: function()
+ {
+ 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, data.length);
+ 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: function(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: function(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: function(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: function(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: function(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: function(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: function(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/uriloader/external-helper-app-service;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(file);
+ }
+ catch (e)
+ {
+ return "application/octet-stream";
+ }
+ },
+
+ /**
+ * Returns the nsILocalFile 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 nsILocalFile
+ * the file to be sent as the response to a request for the path
+ */
+ _getFileForPath: function(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: function(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: function(errorCode, metadata, response)
+ {
+ if (!metadata)
+ throw 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: function(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: function(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: function(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: function(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: function(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: function(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: function(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 nsILocalFile 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 : nsILocalFile
+ * the file to map to key, or null to remove a mapping
+ */
+ put: function(key, value)
+ {
+ if (value)
+ this._map[key] = value.clone();
+ else
+ delete this._map[key];
+ },
+
+ /**
+ * Returns a clone of the nsILocalFile mapped to key, or null if no such
+ * mapping exists.
+ *
+ * @param key : string
+ * key to which the returned file maps
+ * @returns nsILocalFile
+ * a clone of the mapped file, or null if no mapping exists
+ */
+ get: function(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 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: function(data)
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ var dataAsString = String(data);
+ this.bodyOutputStream.write(dataAsString, dataAsString.length);
+ },
+
+ //
+ // see nsIHttpResponse.setStatusLine
+ //
+ setStatusLine: function(httpVersion, code, description)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ if (!(code >= 0 && code < 1000))
+ throw 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 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 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: function(name, value, merge)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ this._headers.setHeader(name, value, merge);
+ },
+
+ setHeaderNoCheck: function(name, value)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ this._headers.setHeaderNoCheck(name, value);
+ },
+
+ //
+ // see nsIHttpResponse.processAsync
+ //
+ processAsync: function()
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ throw 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: function()
+ {
+ if (this._processAsync)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ if (this._finished)
+ throw 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: function()
+ {
+ if (!this._processAsync && !this._powerSeized)
+ throw 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: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // 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: function(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: function()
+ {
+ 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: function()
+ {
+ 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
+ */
+ abort: function(e)
+ {
+ dumpn("*** abort(<" + e + ">)");
+
+ // 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: function()
+ {
+ 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: function()
+ {
+ 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: function()
+ {
+ 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: function()
+ {
+ 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: function(request, cx)
+ {
+ dumpn("*** preamble copying started");
+ },
+
+ onStopRequest: function(request, cx, 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: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ var headerCopier = 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: function()
+ {
+ 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: function(request, context)
+ {
+ dumpn("*** onStartRequest");
+ },
+
+ onStopRequest: function(request, cx, 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: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ 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: function()
+ {
+ 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 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 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, context);
+ 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: function(iid)
+ {
+ if (iid.equals(Ci.nsIInputStreamCallback) ||
+ iid.equals(Ci.nsIOutputStreamCallback) ||
+ iid.equals(Ci.nsIRequest) ||
+ iid.equals(Ci.nsISupports))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // 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: function(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 Cr.NS_BASE_STREAM_CLOSED;
+ }
+ catch (e)
+ {
+ if (streamClosed(e))
+ {
+ dumpn("*** input stream closed");
+ e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
+ }
+ else
+ {
+ dumpn("!!! unexpected error reading from input, canceling: " + e);
+ e = Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ this._doneReadingSource(e);
+ 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: function(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: function()
+ {
+ 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: function(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: function(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: function(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: function(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: function()
+ {
+ dumpn("*** onStopRequest async callback");
+
+ self._completed = true;
+ try
+ {
+ self._observer.onStopRequest(self, self._context, 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: function()
+ {
+ 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: function()
+ {
+ 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: function()
+ {
+ 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: function(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: function(fieldName)
+ {
+ if (fieldName == "")
+ {
+ dumpn("*** Empty fieldName");
+ throw 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 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: function(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 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 "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 "Not a valid HTTP version!";
+}
+nsHttpVersion.prototype =
+{
+ /**
+ * Returns the standard string representation of the HTTP version represented
+ * by this (e.g., "1.1").
+ */
+ toString: function ()
+ {
+ 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: function (otherVersion)
+ {
+ return this.major == otherVersion.major &&
+ this.minor == otherVersion.minor;
+ },
+
+ /** True if this >= otherVersion, false otherwise. */
+ atLeast: function(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: function(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 <http://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: function(fieldName, fieldValue)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ var value = headerUtils.normalizeFieldValue(fieldValue);
+ if (name in this._headers) {
+ this._headers[name].push(fieldValue);
+ } else {
+ this._headers[name] = [fieldValue];
+ }
+ },
+
+ /**
+ * 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: function(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: function(fieldName)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+
+ if (name in this._headers)
+ return this._headers[name];
+ else
+ throw 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: function(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: function()
+ {
+ return this._nextIndex < this._items.length;
+ },
+ getNext: function()
+ {
+ if (!this.hasMoreElements())
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ return this._items[this._nextIndex++];
+ },
+ QueryInterface: function(aIID)
+ {
+ if (Ci.nsISimpleEnumerator.equals(aIID) ||
+ Ci.nsISupports.equals(aIID))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+
+/**
+ * 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: function(name)
+ {
+ return this._headers.getHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.hasHeader
+ //
+ hasHeader: function(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: function(name)
+ {
+ this._ensurePropertyBag();
+ return this._bag.getProperty(name);
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /** Ensures a property bag has been created for ad-hoc behaviors. */
+ _ensurePropertyBag: function()
+ {
+ if (!this._bag)
+ this._bag = new WritablePropertyBag();
+ }
+};
+
+
+// XPCOM trappings
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
+
+/**
+ * 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.nsILocalFile);
+ 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/httpd.manifest b/netwerk/test/httpserver/httpd.manifest
new file mode 100644
index 0000000000..745e5d3672
--- /dev/null
+++ b/netwerk/test/httpserver/httpd.manifest
@@ -0,0 +1,3 @@
+component {54ef6f81-30af-4b1d-ac55-8ba811293e41} httpd.js
+contract @mozilla.org/server/jshttp;1 {54ef6f81-30af-4b1d-ac55-8ba811293e41}
+interfaces test_necko.xpt
diff --git a/netwerk/test/httpserver/moz.build b/netwerk/test/httpserver/moz.build
new file mode 100644
index 0000000000..f8199d18ec
--- /dev/null
+++ b/netwerk/test/httpserver/moz.build
@@ -0,0 +1,25 @@
+# -*- 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'
+
+# Don't add our test-only .xpt files to the normal manifests
+XPIDL_NO_MANIFEST = True
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
+
+EXTRA_COMPONENTS += [
+ 'httpd.js',
+ 'httpd.manifest',
+]
+
+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..97192a2d60
--- /dev/null
+++ b/netwerk/test/httpserver/nsIHttpServer.idl
@@ -0,0 +1,620 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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
+ */
+ void registerFile(in string path, in nsIFile file);
+
+ /**
+ * Registers a custom path handler.
+ *
+ * @param path
+ * the path on the server (beginning with a "/") which is to be handled by
+ * handler; this path must not include a query string or hash component; it
+ * also should usually be canonicalized, since most browsers will do so
+ * before sending otherwise-matching requests
+ * @param handler
+ * an object which will handle any requests for the given path, or null to
+ * remove any existing handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500 response
+ * will be returned
+ * @throws NS_ERROR_INVALID_ARG
+ * if path does not begin with a "/"
+ */
+ void registerPathHandler(in string path, in nsIHttpRequestHandler handler);
+
+ /**
+ * Registers a custom prefix handler.
+ *
+ * @param prefix
+ * the path on the server (beginning and ending with "/") which is to be
+ * handled by handler; this path must not include a query string or hash
+ * component. All requests that start with this prefix will be directed to
+ * the given handler.
+ * @param handler
+ * an object which will handle any requests for the given path, or null to
+ * remove any existing handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500 response
+ * will be returned
+ * @throws NS_ERROR_INVALID_ARG
+ * if path does not begin with a "/" or does not end with a "/"
+ */
+ void registerPrefixHandler(in string prefix, in nsIHttpRequestHandler handler);
+
+ /**
+ * Registers a custom error page handler.
+ *
+ * @param code
+ * the error code which is to be handled by handler
+ * @param handler
+ * an object which will handle any requests which generate the given status
+ * code, or null to remove any existing handler. If the handler throws an
+ * exception during server operation, fallback is to the genericized error
+ * handler (the x00 version), then to 500, using a user-defined error
+ * handler if one exists or the server default handler otherwise. Fallback
+ * will never occur from a user-provided handler that throws to the same
+ * handler as provided by the server, e.g. a throwing user 404 falls back to
+ * 400, not a server-provided 404 that might not throw.
+ * @note
+ * If the error handler handles HTTP 500 and throws, behavior is undefined.
+ */
+ void registerErrorHandler(in unsigned long code, in nsIHttpRequestHandler handler);
+
+ /**
+ * Maps all requests to paths beneath path to the corresponding file beneath
+ * dir.
+ *
+ * @param path
+ * the absolute path on the server against which requests will be served
+ * from dir (e.g., "/", "/foo/", etc.); must begin and end with a forward
+ * slash
+ * @param dir
+ * the directory to be used to serve all requests for paths underneath path
+ * (except those further overridden by another, deeper path registered with
+ * another directory); if null, any current mapping for the given path is
+ * removed
+ * @throws NS_ERROR_INVALID_ARG
+ * if dir is non-null and does not exist or is not a directory, or if path
+ * does not begin with and end with a forward slash
+ */
+ void registerDirectory(in string path, in nsIFile dir);
+
+ /**
+ * Associates files with the given extension with the given Content-Type when
+ * served by this server, in the absence of any file-specific information
+ * about the desired Content-Type. If type is empty, removes any extant
+ * mapping, if one is present.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if the given type is not a valid header field value, i.e. if it doesn't
+ * match the field-value production in RFC 2616
+ * @note
+ * No syntax checking is done of the given type, beyond ensuring that it is
+ * a valid header field value. Behavior when not given a string matching
+ * the media-type production in RFC 2616 section 3.7 is undefined.
+ * Implementations may choose to define specific behavior for types which do
+ * not match the production, such as for CGI functionality.
+ * @note
+ * Implementations MAY treat type as a trusted argument; users who fail to
+ * generate this string from trusted data risk security vulnerabilities.
+ */
+ void registerContentType(in string extension, in string type);
+
+ /**
+ * Sets the handler used to display the contents of a directory if
+ * the directory contains no index page.
+ *
+ * @param handler
+ * an object which will handle any requests for directories which
+ * do not contain index pages, or null to reset to the default
+ * index handler; if while the server is running the handler
+ * throws an exception while responding to a request, an HTTP 500
+ * response will be returned. An nsIFile corresponding to the
+ * directory is available from the metadata object passed to the
+ * handler, under the key "directory".
+ */
+ void setIndexHandler(in nsIHttpRequestHandler handler);
+
+ /** Represents the locations at which this server is reachable. */
+ readonly attribute nsIHttpServerIdentity identity;
+
+ /**
+ * Retrieves the string associated with the given key in this, for the given
+ * path's saved state. All keys are initially associated with the empty
+ * string.
+ */
+ AString getState(in AString path, in AString key);
+
+ /**
+ * Sets the string associated with the given key in this, for the given path's
+ * saved state.
+ */
+ void setState(in AString path, in AString key, in AString value);
+
+ /**
+ * Retrieves the string associated with the given key in this, in
+ * entire-server saved state. All keys are initially associated with the
+ * empty string.
+ */
+ AString getSharedState(in AString key);
+
+ /**
+ * Sets the string associated with the given key in this, in entire-server
+ * saved state.
+ */
+ void setSharedState(in AString key, in AString value);
+
+ /**
+ * Retrieves the object associated with the given key in this in
+ * object-valued saved state. All keys are initially associated with null.
+ */
+ nsISupports getObjectState(in AString key);
+
+ /**
+ * Sets the object associated with the given key in this in object-valued
+ * saved state. The value may be null.
+ */
+ void setObjectState(in AString key, in nsISupports value);
+};
+
+/**
+ * An interface through which a notification of the complete stopping (socket
+ * closure, in-flight requests all fully served and responded to) of an HTTP
+ * server may be received.
+ */
+[scriptable, function, uuid(925a6d33-9937-4c63-abe1-a1c56a986455)]
+interface nsIHttpServerStoppedCallback : nsISupports
+{
+ /** Called when the corresponding server has been fully stopped. */
+ void onStopped();
+};
+
+/**
+ * Represents a set of names for a server, one of which is the primary name for
+ * the server and the rest of which are secondary. By default every server will
+ * contain ("http", "localhost", port) and ("http", "127.0.0.1", port) as names,
+ * where port is what was provided to the corresponding server when started;
+ * however, except for their being removed when the corresponding server stops
+ * they have no special importance.
+ */
+[scriptable, uuid(a89de175-ae8e-4c46-91a5-0dba99bbd284)]
+interface nsIHttpServerIdentity : nsISupports
+{
+ /**
+ * The primary scheme at which the corresponding server is located, defaulting
+ * to 'http'. This name will be the value of nsIHttpRequest.scheme for
+ * HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute string primaryScheme;
+
+ /**
+ * The primary name by which the corresponding server is known, defaulting to
+ * 'localhost'. This name will be the value of nsIHttpRequest.host for
+ * HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute string primaryHost;
+
+ /**
+ * The primary port on which the corresponding server runs, defaulting to the
+ * associated server's port. This name will be the value of
+ * nsIHttpRequest.port for HTTP/1.0 requests.
+ *
+ * This value is always set when the corresponding server is running. If the
+ * server is not running, this value is set only if it has been set to a
+ * non-default name using setPrimary. In this case reading this value will
+ * throw NS_ERROR_NOT_INITIALIZED.
+ */
+ readonly attribute long primaryPort;
+
+ /**
+ * Adds a location at which this server may be accessed.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ void add(in string scheme, in string host, in long port);
+
+ /**
+ * Removes this name from the list of names by which the corresponding server
+ * is known. If name is also the primary name for the server, the primary
+ * name reverts to 'http://127.0.0.1' with the associated server's port.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ * @returns
+ * true if the given name was a name for this server, false otherwise
+ */
+ boolean remove(in string scheme, in string host, in long port);
+
+ /**
+ * Returns true if the given name is in this, false otherwise.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ boolean has(in string scheme, in string host, in long port);
+
+ /**
+ * Returns the scheme for the name with the given host and port, if one is
+ * present; otherwise returns the empty string.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if host does not match the host production imported into RFC 2616 from
+ * RFC 2396, or if port is not a valid port number
+ */
+ string getScheme(in string host, in long port);
+
+ /**
+ * Designates the given name as the primary name in this and adds it to this
+ * if it is not already present.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE
+ * if scheme or host do not match the scheme or host productions imported
+ * into RFC 2616 from RFC 2396, or if port is not a valid port number
+ */
+ void setPrimary(in string scheme, in string host, in long port);
+};
+
+/**
+ * A representation of a handler for HTTP requests. The handler is used by
+ * calling its .handle method with data for an incoming request; it is the
+ * handler's job to use that data as it sees fit to make the desired response.
+ *
+ * @note
+ * This interface uses the [function] attribute, so you can pass a
+ * script-defined function with the functionality of handle() to any
+ * method which has a nsIHttpRequestHandler parameter, instead of wrapping
+ * it in an otherwise empty object.
+ */
+[scriptable, function, uuid(2bbb4db7-d285-42b3-a3ce-142b8cc7e139)]
+interface nsIHttpRequestHandler : nsISupports
+{
+ /**
+ * Processes an HTTP request and initializes the passed-in response to reflect
+ * the correct HTTP response.
+ *
+ * If this method throws an exception, externally observable behavior depends
+ * upon whether is being processed asynchronously. If such is the case, the
+ * output is some prefix (perhaps all, perhaps none, perhaps only some) of the
+ * data which would have been sent if, instead, the response had been finished
+ * at that point. If no data has been written, the response has not had
+ * seizePower() called on it, and it is not being asynchronously created, an
+ * error handler will be invoked (usually 500 unless otherwise specified).
+ *
+ * Some uses of nsIHttpRequestHandler may require this method to never throw
+ * an exception; in the general case, however, this method may throw an
+ * exception (causing an HTTP 500 response to occur, if the above conditions
+ * are met).
+ *
+ * @param request
+ * data representing an HTTP request
+ * @param response
+ * an initially-empty response which must be modified to reflect the data
+ * which should be sent as the response to the request described by metadata
+ */
+ void handle(in nsIHttpRequest request, in nsIHttpResponse response);
+};
+
+
+/**
+ * A representation of the data included in an HTTP request.
+ */
+[scriptable, uuid(978cf30e-ad73-42ee-8f22-fe0aaf1bf5d2)]
+interface nsIHttpRequest : nsISupports
+{
+ /**
+ * The request type for this request (see RFC 2616, section 5.1.1).
+ */
+ readonly attribute string method;
+
+ /**
+ * The scheme of the requested path, usually 'http' but might possibly be
+ * 'https' if some form of SSL tunneling is in use. Note that this value
+ * cannot be accurately determined unless the incoming request used the
+ * absolute-path form of the request line; it defaults to 'http', so only
+ * if it is something else can you be entirely certain it's correct.
+ */
+ readonly attribute string scheme;
+
+ /**
+ * The host of the data being requested (e.g. "localhost" for the
+ * http://localhost:8080/file resource). Note that the relevant port on the
+ * host is specified in this.port. This value is in the ASCII character
+ * encoding.
+ */
+ readonly attribute string host;
+
+ /**
+ * The port on the server on which the request was received.
+ */
+ readonly attribute unsigned long port;
+
+ /**
+ * The requested path, without any query string (e.g. "/dir/file.txt"). It is
+ * guaranteed to begin with a "/". The individual components in this string
+ * are URL-encoded.
+ */
+ readonly attribute string path;
+
+ /**
+ * The URL-encoded query string associated with this request, not including
+ * the initial "?", or "" if no query string was present.
+ */
+ readonly attribute string queryString;
+
+ /**
+ * A string containing the HTTP version of the request (i.e., "1.1"). Leading
+ * zeros for either component of the version will be omitted. (In other
+ * words, if the request contains the version "1.01", this attribute will be
+ * "1.1"; see RFC 2616, section 3.1.)
+ */
+ readonly attribute string httpVersion;
+
+ /**
+ * Returns the value for the header in this request specified by fieldName.
+ *
+ * @param fieldName
+ * the name of the field whose value is to be gotten; note that since HTTP
+ * header field names are case-insensitive, this method produces equivalent
+ * results for "HeAdER" and "hEADer" as fieldName
+ * @returns
+ * The result is a string containing the individual values of the header,
+ * usually separated with a comma. The headers WWW-Authenticate,
+ * Proxy-Authenticate, and Set-Cookie violate the HTTP specification,
+ * however, and for these headers only the separator string is '\n'.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if the given header does not exist in this
+ */
+ string getHeader(in string fieldName);
+
+ /**
+ * Returns true if a header with the given field name exists in this, false
+ * otherwise.
+ *
+ * @param fieldName
+ * the field name whose existence is to be determined in this; note that
+ * since HTTP header field names are case-insensitive, this method produces
+ * equivalent results for "HeAdER" and "hEADer" as fieldName
+ * @throws NS_ERROR_INVALID_ARG
+ * if fieldName does not constitute a valid header field name
+ */
+ boolean hasHeader(in string fieldName);
+
+ /**
+ * An nsISimpleEnumerator of nsISupportsStrings over the names of the headers
+ * in this request. The header field names in the enumerator may not
+ * necessarily have the same case as they do in the request itself.
+ */
+ readonly attribute nsISimpleEnumerator headers;
+
+ /**
+ * A stream from which data appearing in the body of this request can be read.
+ */
+ readonly attribute nsIInputStream bodyInputStream;
+};
+
+
+/**
+ * Represents an HTTP response, as described in RFC 2616, section 6.
+ */
+[scriptable, uuid(1acd16c2-dc59-42fa-9160-4f26c43c1c21)]
+interface nsIHttpResponse : nsISupports
+{
+ /**
+ * Sets the status line for this. If this method is never called on this, the
+ * status line defaults to "HTTP/", followed by the server's default HTTP
+ * version (e.g. "1.1"), followed by " 200 OK".
+ *
+ * @param httpVersion
+ * the HTTP version of this, as a string (e.g. "1.1"); if null, the server
+ * default is used
+ * @param code
+ * the numeric HTTP status code for this
+ * @param description
+ * a human-readable description of code; may be null if no description is
+ * desired
+ * @throws NS_ERROR_INVALID_ARG
+ * if httpVersion is not a valid HTTP version string, statusCode is greater
+ * than 999, or description contains invalid characters
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if this response is being processed asynchronously and data has been
+ * written to this response's body, or if seizePower() has been called on
+ * this
+ */
+ void setStatusLine(in string httpVersion,
+ in unsigned short statusCode,
+ in string description);
+
+ /**
+ * Sets the specified header in this.
+ *
+ * @param name
+ * the name of the header; must match the field-name production per RFC 2616
+ * @param value
+ * the value of the header; must match the field-value production per RFC
+ * 2616
+ * @param merge
+ * when true, if the given header already exists in this, the values passed
+ * to this function will be merged into the existing header, per RFC 2616
+ * header semantics (except for the Set-Cookie, WWW-Authenticate, and
+ * Proxy-Authenticate headers, which will treat each such merged header as
+ * an additional instance of the header, for real-world compatibility
+ * reasons); when false, replaces any existing header of the given name (if
+ * any exists) with a new header with the specified value
+ * @throws NS_ERROR_INVALID_ARG
+ * if name or value is not a valid header component
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if this response is being processed asynchronously and data has been
+ * written to this response's body, or if seizePower() has been called on
+ * this
+ */
+ void setHeader(in string name, in string value, in boolean merge);
+
+ /**
+ * This is used for testing our header handling, so header will be sent out
+ * without transformation. There can be multiple headers.
+ */
+ void setHeaderNoCheck(in string name, in string value);
+
+ /**
+ * A stream to which data appearing in the body of this response (or in the
+ * totality of the response if seizePower() is called) should be written.
+ * After this response has been designated as being processed asynchronously,
+ * or after seizePower() has been called on this, subsequent writes will no
+ * longer be buffered and will be written to the underlying transport without
+ * delaying until the entire response is constructed. Write-through may or
+ * may not be synchronous in the implementation, and in any case particular
+ * behavior may not be observable to the HTTP client as intermediate buffers
+ * both in the server socket and in the client may delay written data; be
+ * prepared for delays at any time.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if accessed after this response is fully constructed
+ */
+ readonly attribute nsIOutputStream bodyOutputStream;
+
+ /**
+ * Writes a string to the response's output stream. This method is merely a
+ * convenient shorthand for writing the same data to bodyOutputStream
+ * directly.
+ *
+ * @note
+ * This method is only guaranteed to work with ASCII data.
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if called after this response has been fully constructed
+ */
+ void write(in string data);
+
+ /**
+ * Signals that this response is being constructed asynchronously. Requests
+ * are typically completely constructed during nsIHttpRequestHandler.handle;
+ * however, responses which require significant resources (time, memory,
+ * processing) to construct can be created and sent incrementally by calling
+ * this method during the call to nsIHttpRequestHandler.handle. This method
+ * only has this effect when called during nsIHttpRequestHandler.handle;
+ * behavior is undefined if it is called at a later time. It may be called
+ * multiple times with no ill effect, so long as each call occurs before
+ * finish() is called.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * if not initially called within a nsIHttpRequestHandler.handle call or if
+ * called after this response has been finished
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if seizePower() has been called on this
+ */
+ void processAsync();
+
+ /**
+ * Seizes complete control of this response (and its connection) from the
+ * server, allowing raw and unfettered access to data being sent in the HTTP
+ * response. Once this method has been called the only property which may be
+ * accessed without an exception being thrown is bodyOutputStream, and the
+ * only methods which may be accessed without an exception being thrown are
+ * write(), finish(), and seizePower() (which may be called multiple times
+ * without ill effect so long as all calls are otherwise allowed).
+ *
+ * After a successful call, all data subsequently written to the body of this
+ * response is written directly to the corresponding connection. (Previously-
+ * written data is silently discarded.) No status line or headers are sent
+ * before doing so; if the response handler wishes to write such data, it must
+ * do so manually. Data generation completes only when finish() is called; it
+ * is not enough to simply call close() on bodyOutputStream.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * if processAsync() has been called on this
+ * @throws NS_ERROR_UNEXPECTED
+ * if finish() has been called on this
+ */
+ void seizePower();
+
+ /**
+ * Signals that construction of this response is complete and that it may be
+ * sent over the network to the client, or if seizePower() has been called
+ * signals that all data has been written and that the underlying connection
+ * may be closed. This method may only be called after processAsync() or
+ * seizePower() has been called. This method is idempotent.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * if processAsync() or seizePower() has not already been properly called
+ */
+ void finish();
+};
diff --git a/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^
new file mode 100644
index 0000000000..b005a65fd2
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^
@@ -0,0 +1 @@
+If this has goofy headers on it, it's a success.
diff --git a/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^
new file mode 100644
index 0000000000..66e1522317
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/caret_test.txt^^headers^
@@ -0,0 +1,3 @@
+HTTP 500 This Isn't A Server Error
+Foo-RFC: 3092
+Shaving-Cream-Atom: Illudium Phosdex
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_both.html b/netwerk/test/httpserver/test/data/cern_meta/test_both.html
new file mode 100644
index 0000000000..db18ea5d7a
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html
@@ -0,0 +1,2 @@
+This page is a text file served with status 501. (That's really a lie, tho,
+because this is definitely Implemented.)
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^
new file mode 100644
index 0000000000..bb3c16a2e2
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_both.html^headers^
@@ -0,0 +1,2 @@
+HTTP 501 Unimplemented
+Content-Type: text/plain
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt
new file mode 100644
index 0000000000..7235fa32a5
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>This is really HTML, not text</title>
+</head>
+<body>
+<p>This file is really HTML; the test_ctype_override.txt^headers^ file sets a
+ new header that overwrites the default text/plain header.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^
new file mode 100644
index 0000000000..156209f9c8
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_ctype_override.txt^headers^
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html
new file mode 100644
index 0000000000..fd243c640e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>This is a 404 page</title>
+</head>
+<body>
+<p>This page has a 404 HTTP status associated with it, via
+ <code>test_status_override.html^headers^</code>.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^
new file mode 100644
index 0000000000..f438a05746
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override.html^headers^
@@ -0,0 +1 @@
+HTTP 404 Can't Find This
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt
new file mode 100644
index 0000000000..4718ec282f
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt
@@ -0,0 +1 @@
+This page has an HTTP status override without a description (it defaults to "").
diff --git a/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^
new file mode 100644
index 0000000000..32da7632f9
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/cern_meta/test_status_override_nodesc.txt^headers^
@@ -0,0 +1 @@
+HTTP 732
diff --git a/netwerk/test/httpserver/test/data/name-scheme/bar.html^^ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^
new file mode 100644
index 0000000000..bed1f34c9f
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <title>Welcome to bar.html^</title>
+</head>
+<body>
+<p>This file is named with two trailing carets, so the last is stripped
+ away, producing bar.html^ as the final name.</p>
+</body>
+</html>
+
diff --git a/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^
new file mode 100644
index 0000000000..04fbaa08fe
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/bar.html^^headers^
@@ -0,0 +1,2 @@
+HTTP 200 OK
+Content-Type: text/html
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^
new file mode 100644
index 0000000000..dccee48e34
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/ERROR_IF_SEE_THIS.txt^
@@ -0,0 +1 @@
+This file shouldn't be shown in directory listings.
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^
new file mode 100644
index 0000000000..a8ee35a3b6
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/SHOULD_SEE_THIS.txt^^
@@ -0,0 +1 @@
+This file should show up in directory listings as SHOULD_SEE_THIS.txt^.
diff --git a/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt b/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt
new file mode 100644
index 0000000000..2ceca8ca9e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/folder^^/file.txt
@@ -0,0 +1,2 @@
+File in a directory named with a trailing caret (in the virtual FS; on disk it
+actually ends with two carets).
diff --git a/netwerk/test/httpserver/test/data/name-scheme/foo.html^ b/netwerk/test/httpserver/test/data/name-scheme/foo.html^
new file mode 100644
index 0000000000..a3efe8b5c3
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/foo.html^
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>ERROR</title>
+</head>
+<body>
+<p>This file should never be served by the web server because its name ends
+ with a caret not followed by another caret.</p>
+</body>
+</html>
diff --git a/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt b/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt
new file mode 100644
index 0000000000..ab71eabaf0
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/name-scheme/normal-file.txt
@@ -0,0 +1 @@
+This should be seen.
diff --git a/netwerk/test/httpserver/test/data/ranges/empty.txt b/netwerk/test/httpserver/test/data/ranges/empty.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/empty.txt
diff --git a/netwerk/test/httpserver/test/data/ranges/headers.txt b/netwerk/test/httpserver/test/data/ranges/headers.txt
new file mode 100644
index 0000000000..6cf83528c8
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/headers.txt
@@ -0,0 +1 @@
+Hello Kitty
diff --git a/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^ b/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^
new file mode 100644
index 0000000000..d0a633f042
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/headers.txt^headers^
@@ -0,0 +1 @@
+X-SJS-Header: customized
diff --git a/netwerk/test/httpserver/test/data/ranges/range.txt b/netwerk/test/httpserver/test/data/ranges/range.txt
new file mode 100644
index 0000000000..ab71eabaf0
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/ranges/range.txt
@@ -0,0 +1 @@
+This should be seen.
diff --git a/netwerk/test/httpserver/test/data/sjs/cgi.sjs b/netwerk/test/httpserver/test/data/sjs/cgi.sjs
new file mode 100644
index 0000000000..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..89c7089b5f
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/qi.sjs
@@ -0,0 +1,48 @@
+const Ci = Components.interfaces;
+
+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..21f6151174
--- /dev/null
+++ b/netwerk/test/httpserver/test/head_utils.js
@@ -0,0 +1,600 @@
+/* -*- 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/. */
+
+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
+DEBUG = true;
+
+Cu.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 : nsILocalFile
+ * 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 start = 0, 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 string
+ * an error message if iter doesn't agree with expectedLines
+ */
+function expectLines(iter, expectedLines)
+{
+ var index = 0;
+ for (var line in iter)
+ {
+ if (expectedLines.length == index)
+ throw "Error: got more than " + expectedLines.length + " expected lines!";
+
+ var expected = expectedLines[index++];
+ if (expected !== line)
+ throw "Error on line " + index + "!\n" +
+ " actual: '" + line + "',\n" +
+ " expect: '" + expected + "'";
+ }
+
+ if (expectedLines.length !== index)
+ {
+ throw "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();
+ while (line !== "")
+ line = iter.next();
+}
+
+/**
+ * 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 (e)
+ {
+ /* swallow and let tests continue */
+ }
+ }
+
+ listener._channel = ch;
+ ch.asyncOpen2(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: function(request, cx)
+ {
+ do_check_true(request === this._channel);
+ var ch = request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ this._data.length = 0;
+ try
+ {
+ try
+ {
+ testArray[testIndex].onStartRequest(ch, cx);
+ }
+ catch (e)
+ {
+ do_report_unexpected_exception(e, "testArray[" + testIndex + "].onStartRequest");
+ }
+ }
+ catch (e)
+ {
+ do_note_exception(e, "!!! swallowing onStartRequest exception so onStopRequest is " +
+ "called...");
+ }
+ },
+ onDataAvailable: function(request, cx, 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: function(request, cx, 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, cx, status, this._data);
+ }
+ finally
+ {
+ try
+ {
+ performNextTest();
+ }
+ finally
+ {
+ do_test_finished();
+ }
+ }
+ },
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIStreamListener) ||
+ aIID.equals(Ci.nsIRequestObserver) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ 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 "bad port";
+ if (!(data instanceof Array))
+ data = [data];
+ if (data.length <= 0)
+ throw "bad data length";
+ if (!data.every(function(v) { return /^[\x00-\xff]*$/.test(v); }))
+ throw "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)
+ */
+function runRawTests(testArray, done)
+{
+ 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;
+ }
+
+
+ var rawTest = testArray[testIndex];
+
+ var transport =
+ sts.createTransport(null, 0, 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: function(stream)
+ {
+ do_check_true(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: function(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..84ec74daf4
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_async_response_sending.js
@@ -0,0 +1,1683 @@
+/* -*- 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("******************\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 sum = 0;
+ for (var i = 0, sz = arr.length; i < sz; i++)
+ sum += arr[i];
+ return sum;
+}
+
+/**
+ * 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: function(streamReadyInterceptCreator)
+ {
+ dumpn("*** [" + this.name + "].interceptStreamReadyCallbacks");
+
+ do_check_true(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: function()
+ {
+ dumpn("*** [" + this.name + "].removeStreamReadyInterceptor()");
+
+ do_check_true(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");
+
+ do_check_true(callback && typeof callback !== "function");
+
+ var closureOnly =
+ (flags & Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY) !== 0;
+
+ do_check_true(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: closureOnly,
+ requestedCount: 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)
+ {
+ 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;
+ }
+
+ do_check_true(this._readable <= self._data.length ||
+ this._readable === Infinity,
+ "consistency check");
+
+ if (this._readable < count || self._data.length < count)
+ throw 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 + ")");
+
+ do_check_true(Components.isSuccessCode(self._status), "errant call");
+ do_check_true(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()");
+
+ do_check_true(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;
+ do_check_true(waiter !== null, "no waiter?");
+
+ if (this._event === null)
+ {
+ var event = this._event =
+ {
+ run: function run()
+ {
+ input._waiter = null;
+ input._event = null;
+ try
+ {
+ do_check_true(!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: function QueryInterface(iid)
+ {
+ if (iid.equals(Ci.nsIAsyncInputStream) ||
+ iid.equals(Ci.nsIInputStream) ||
+ iid.equals(Ci.nsISupports))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ /** 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: function(streamReadyInterceptCreator)
+ {
+ dumpn("*** [" + this.name + "].interceptStreamReadyCallbacks");
+
+ do_check_true(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: function()
+ {
+ dumpn("*** [" + this.name + "].removeStreamReadyInterceptor()");
+
+ do_check_true(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");
+
+ do_check_true(callback && typeof callback !== "function");
+
+ var closureOnly =
+ (flags & Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY) !== 0;
+
+ do_check_true(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: closureOnly,
+ requestedCount: 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, length)
+ {
+ dumpn("*** [" + this.name + "].writeByteArray" +
+ "([" + bytes + "], " + length + ")");
+
+ do_check_eq(bytes.length, length, "sanity");
+ if (!Components.isSuccessCode(self._status))
+ throw self._status;
+
+ do_check_eq(this._writableAmounts.length, 0,
+ "writeByteArray can't support specified-length writes");
+
+ if (this._writable < length)
+ throw Cr.NS_BASE_STREAM_WOULD_BLOCK;
+
+ self._data.push.apply(self._data, bytes);
+ this._writable -= length;
+
+ if (input._readable === Infinity && input._waiter &&
+ !input._waiter.closureOnly)
+ {
+ input._notify();
+ }
+ },
+
+ //
+ // see nsIOutputStream.write
+ //
+ write: function write(str, length)
+ {
+ dumpn("*** [" + this.name + "].write");
+
+ do_check_eq(str.length, length, "sanity");
+ if (!Components.isSuccessCode(self._status))
+ throw self._status;
+ if (this._writable === 0)
+ throw Cr.NS_BASE_STREAM_WOULD_BLOCK;
+
+ var actualWritten;
+ if (this._writableAmounts.length === 0)
+ {
+ actualWritten = Math.min(this._writable, length);
+ }
+ else
+ {
+ do_check_true(this._writable >= this._writableAmounts[0],
+ "writable amounts value greater than writable data?");
+ do_check_eq(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 + ")");
+
+ do_check_true(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(", ") + "])");
+
+ do_check_true(increments.length > 0, "bad increments");
+ do_check_true(increments.every(function(v) { return v > 0; }),
+ "zero increment?");
+
+ do_check_true(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;
+ do_check_true(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: function QueryInterface(iid)
+ {
+ if (iid.equals(Ci.nsIAsyncOutputStream) ||
+ iid.equals(Ci.nsIOutputStream) ||
+ iid.equals(Ci.nsISupports))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+}
+
+/**
+ * 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, bytes.length);
+ }
+ 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;
+
+ do_check_eq(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; });
+ do_check_eq(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;
+
+ do_check_eq(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 + ")");
+
+ do_check_eq(this, streamReadyCallback, "sanity");
+
+ try
+ {
+ if (quantumIndex < dataQuantums.length)
+ {
+ var quantum = dataQuantums[quantumIndex++];
+ var sz = quantum.length;
+ do_check_eq(self._lastQuantum.length, sz,
+ "different quantum lengths");
+ for (var i = 0; i < sz; i++)
+ {
+ do_check_eq(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("*** 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);
+ do_check_eq(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
+ {
+ do_check_true(this._allDataWritten, "expect all data written now!");
+ do_check_true(this._copyingFinished, "expect copying finished now!");
+
+ do_check_eq(this._actualStatus, this._expectedStatus,
+ "wrong final status");
+
+ var expected = this._expectedData, received = this._receivedData;
+ dumpn("received: [" + received + "], expected: [" + expected + "]");
+ do_check_eq(received.length, expected.length, "wrong data");
+ for (var i = 0, sz = expected.length; i < sz; i++)
+ do_check_eq(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 self = this;
+ var event =
+ {
+ run: function run()
+ {
+ try
+ {
+ task();
+ }
+ catch (e)
+ {
+ do_throw("exception thrown running task: " + e);
+ }
+ }
+ };
+ gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ /**
+ * 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 + ")");
+
+ do_check_true(_ === null);
+ do_check_eq(this._receivedData.length, 0);
+ do_check_eq(this._lastQuantum.length, 0);
+ },
+
+ //
+ // see nsIRequestObserver.onStopRequest
+ //
+ onStopRequest: function onStopRequest(self, _, status)
+ {
+ dumpn("*** CopyTest.onStopRequest (" + self.name + ", " + status + ")");
+
+ do_check_true(_ === null);
+ 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..9151bed4bc
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_basic_functionality.js
@@ -0,0 +1,176 @@
+/* -*- 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 dirServ = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+ var path = dirServ.get("CurProcD", Ci.nsILocalFile);
+ 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)
+{
+ do_check_true(ch.contentLength > -1);
+ do_check_eq(ch.getResponseHeader("connection"), "close");
+ do_check_false(ch.isNoStoreResponse());
+ do_check_false(ch.isPrivateResponse());
+}
+
+function start_objHandler(ch, cx)
+{
+ commonCheck(ch);
+
+ do_check_eq(ch.responseStatus, 200);
+ do_check_true(ch.requestSucceeded);
+ do_check_eq(ch.getResponseHeader("content-type"), "text/plain");
+ do_check_eq(ch.responseStatusText, "OK");
+
+ var reqMin = {}, reqMaj = {}, respMin = {}, respMaj = {};
+ ch.getRequestVersion(reqMaj, reqMin);
+ ch.getResponseVersion(respMaj, respMin);
+ do_check_true(reqMaj.value == respMaj.value &&
+ reqMin.value == respMin.value);
+}
+
+function start_functionHandler(ch, cx)
+{
+ commonCheck(ch);
+
+ do_check_eq(ch.responseStatus, 404);
+ do_check_false(ch.requestSucceeded);
+ do_check_eq(ch.getResponseHeader("foopy"), "quux-baz");
+ do_check_eq(ch.responseStatusText, "Page Not Found");
+
+ var reqMin = {}, reqMaj = {}, respMin = {}, respMaj = {};
+ ch.getRequestVersion(reqMaj, reqMin);
+ ch.getResponseVersion(respMaj, respMin);
+ do_check_true(reqMaj.value == 1 && reqMin.value == 1);
+ do_check_true(respMaj.value == 1 && respMin.value == 1);
+}
+
+function start_non_existent_path(ch, cx)
+{
+ commonCheck(ch);
+
+ do_check_eq(ch.responseStatus, 404);
+ do_check_false(ch.requestSucceeded);
+}
+
+function start_lots_of_headers(ch, cx)
+{
+ commonCheck(ch);
+
+ do_check_eq(ch.responseStatus, 200);
+ do_check_true(ch.requestSucceeded);
+
+ for (var i = 0; i < HEADER_COUNT; i++)
+ do_check_eq(ch.getResponseHeader("X-Header-" + i), "value " + i);
+}
+
+// PATH HANDLERS
+
+// /objHandler
+var objHandler =
+ {
+ handle: function(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;
+
+ do_check_eq(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: function(id)
+ {
+ if (id.equals(Ci.nsISupports) || id.equals(Ci.nsIHttpRequestHandler))
+ return this;
+ throw Cr.NS_ERROR_NOINTERFACE;
+ }
+ };
+
+// /functionHandler
+function functionHandler(metadata, response)
+{
+ response.setStatusLine("1.1", 404, "Page Not Found");
+ response.setHeader("foopy", "quux-baz", false);
+
+ do_check_eq(metadata.port, port);
+ do_check_eq(metadata.host, "localhost");
+ do_check_eq(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..0fd2236d73
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_body_length.js
@@ -0,0 +1,64 @@
+/* -*- 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)
+{
+ do_check_eq(request.method, "POST");
+ do_check_eq(request.getHeader("Content-Length"), "017");
+
+ var body = new ScriptableInputStream(request.bodyInputStream);
+
+ var avail;
+ var data = "";
+ while ((avail = body.available()) > 0)
+ data += body.read(avail);
+
+ do_check_eq(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..53d23e5226
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_byte_range.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/. */
+
+// 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, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "21");
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function stop_normal(ch, cx, status, data)
+{
+ do_check_eq(data.length, 21);
+ do_check_eq(data[0], 0x54);
+ do_check_eq(data[20], 0x0a);
+}
+
+function init_byterange(ch)
+{
+ ch.setRequestHeader("Range", "bytes=10-", false);
+}
+
+function start_byterange(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "11");
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+ do_check_eq(ch.getResponseHeader("Content-Range"), "bytes 10-20/21");
+}
+
+function stop_byterange(ch, cx, status, data)
+{
+ do_check_eq(data.length, 11);
+ do_check_eq(data[0], 0x64);
+ do_check_eq(data[10], 0x0a);
+}
+
+function init_byterange2(ch)
+{
+ ch.setRequestHeader("Range", "bytes=21-", false);
+}
+
+function start_byterange2(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 416);
+}
+
+function init_byterange3(ch)
+{
+ ch.setRequestHeader("Range", "bytes=10-15", false);
+}
+
+function start_byterange3(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "6");
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+ do_check_eq(ch.getResponseHeader("Content-Range"), "bytes 10-15/21");
+}
+
+function stop_byterange3(ch, cx, status, data)
+{
+ do_check_eq(data.length, 6);
+ do_check_eq(data[0], 0x64);
+ do_check_eq(data[1], 0x20);
+ do_check_eq(data[2], 0x62);
+ do_check_eq(data[3], 0x65);
+ do_check_eq(data[4], 0x20);
+ do_check_eq(data[5], 0x73);
+}
+
+function init_byterange4(ch)
+{
+ ch.setRequestHeader("Range", "xbytes=21-", false);
+}
+
+function start_byterange4(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 400);
+}
+
+function init_byterange5(ch)
+{
+ ch.setRequestHeader("Range", "bytes=-5", false);
+}
+
+function start_byterange5(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+}
+
+function stop_byterange5(ch, cx, status, data)
+{
+ do_check_eq(data.length, 5);
+ do_check_eq(data[0], 0x65);
+ do_check_eq(data[1], 0x65);
+ do_check_eq(data[2], 0x6e);
+ do_check_eq(data[3], 0x2e);
+ do_check_eq(data[4], 0x0a);
+}
+
+function init_byterange6(ch)
+{
+ ch.setRequestHeader("Range", "bytes=15-12", false);
+}
+
+function start_byterange6(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+}
+
+function stop_byterange6(ch, cx, status, data)
+{
+ do_check_eq(data.length, 21);
+ do_check_eq(data[0], 0x54);
+ do_check_eq(data[20], 0x0a);
+}
+
+function init_byterange7(ch)
+{
+ ch.setRequestHeader("Range", "bytes=0-5", false);
+}
+
+function start_byterange7(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "6");
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+ do_check_eq(ch.getResponseHeader("Content-Range"), "bytes 0-5/21");
+}
+
+function stop_byterange7(ch, cx, status, data)
+{
+ do_check_eq(data.length, 6);
+ do_check_eq(data[0], 0x54);
+ do_check_eq(data[1], 0x68);
+ do_check_eq(data[2], 0x69);
+ do_check_eq(data[3], 0x73);
+ do_check_eq(data[4], 0x20);
+ do_check_eq(data[5], 0x73);
+}
+
+function init_byterange8(ch)
+{
+ ch.setRequestHeader("Range", "bytes=20-21", false);
+}
+
+function start_byterange8(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+ do_check_eq(ch.getResponseHeader("Content-Range"), "bytes 20-20/21");
+}
+
+function stop_byterange8(ch, cx, status, data)
+{
+ do_check_eq(data.length, 1);
+ do_check_eq(data[0], 0x0a);
+}
+
+function init_byterange9(ch)
+{
+ ch.setRequestHeader("Range", "bytes=020-021", false);
+}
+
+function start_byterange9(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+}
+
+function stop_byterange9(ch, cx, status, data)
+{
+ do_check_eq(data.length, 1);
+ do_check_eq(data[0], 0x0a);
+}
+
+function init_byterange10(ch)
+{
+ ch.setRequestHeader("Range", "bytes=-", false);
+}
+
+function start_byterange10(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 400);
+}
+
+function init_byterange11(ch)
+{
+ ch.setRequestHeader("Range", "bytes=-500", false);
+}
+
+function start_byterange11(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 206);
+}
+
+function stop_byterange11(ch, cx, status, data)
+{
+ do_check_eq(data.length, 21);
+ do_check_eq(data[0], 0x54);
+ do_check_eq(data[20], 0x0a);
+}
+
+function start_byterange12(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.getResponseHeader("Content-Length"), "0");
+}
+
+function stop_byterange12(ch, cx, status, data)
+{
+ do_check_eq(data.length, 0);
+}
+
+function init_byterange13(ch)
+{
+ ch.setRequestHeader("Range", "bytes=9999999-", false);
+}
+
+function start_byterange13(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 416);
+ do_check_eq(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..54062bc3ea
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_cern_meta.js
@@ -0,0 +1,76 @@
+/* -*- 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, cx)
+{
+ do_check_eq(ch.responseStatus, 501);
+ do_check_eq(ch.responseStatusText, "Unimplemented");
+
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function start_test_ctype_override_txt(ch, cx)
+{
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/html");
+}
+
+function start_test_status_override_html(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 404);
+ do_check_eq(ch.responseStatusText, "Can't Find This");
+}
+
+function start_test_status_override_nodesc_txt(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 732);
+ do_check_eq(ch.responseStatusText, "");
+}
+
+function start_caret_test_txt_(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 500);
+ do_check_eq(ch.responseStatusText, "This Isn't A Server Error");
+
+ do_check_eq(ch.getResponseHeader("Foo-RFC"), "3092");
+ do_check_eq(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..c2c1b4e73e
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_default_index_handler.js
@@ -0,0 +1,290 @@
+/* -*- 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, dirEntries;
+
+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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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);
+
+ dirEntries = [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);
+
+ dirEntries.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 = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+
+ // 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");
+ }
+
+ // See all the .QueryInterface()s and .item()s happening here? That's because
+ // xpcshell sucks and doesn't have classinfo, so no automatic interface
+ // flattening or array-style access to items in NodeLists. Suck.
+
+ var body = doc.documentElement.getElementsByTagName("body");
+ do_check_eq(body.length, 1);
+ body = body.item(0);
+
+ // header
+ var header = body.QueryInterface(Ci.nsIDOMElement)
+ .getElementsByTagName("h1");
+ do_check_eq(header.length, 1);
+
+ do_check_eq(header.item(0).QueryInterface(Ci.nsIDOMNode).textContent, path);
+
+ // files
+ var lst = body.getElementsByTagName("ol");
+ do_check_eq(lst.length, 1);
+ var items = lst.item(0).QueryInterface(Ci.nsIDOMElement)
+ .getElementsByTagName("li");
+
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ var top = ios.newURI(uri, null, null);
+
+ // 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.item(i)
+ .childNodes
+ .item(0)
+ .QueryInterface(Ci.nsIDOMNode)
+ .QueryInterface(Ci.nsIDOMElement);
+ var f = dirEntries[i];
+
+ var sep = f.isDirectory ? "/" : "";
+
+ do_check_eq(link.textContent, f.name + sep);
+
+ uri = ios.newURI(link.getAttribute("href"), null, top);
+ do_check_eq(decodeURIComponent(uri.path), 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 = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+
+ // 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");
+ }
+
+ // See all the .QueryInterface()s and .item()s happening here? That's because
+ // xpcshell sucks and doesn't have classinfo, so no automatic interface
+ // flattening or array-style access to items in NodeLists. Suck.
+
+ var body = doc.documentElement.getElementsByTagName("body");
+ do_check_eq(body.length, 1);
+ body = body.item(0);
+
+ // header
+ var header = body.QueryInterface(Ci.nsIDOMElement)
+ .getElementsByTagName("h1");
+ do_check_eq(header.length, 1);
+
+ do_check_eq(header.item(0).QueryInterface(Ci.nsIDOMNode).textContent, path);
+
+ // files
+ var lst = body.getElementsByTagName("ol");
+ do_check_eq(lst.length, 1);
+ var items = lst.item(0).QueryInterface(Ci.nsIDOMElement)
+ .getElementsByTagName("li");
+
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ var dirURI = ios.newURI(uri, null, null);
+
+ for (var i = 0; i < items.length; i++)
+ {
+ var link = items.item(i)
+ .childNodes
+ .item(0)
+ .QueryInterface(Ci.nsIDOMNode)
+ .QueryInterface(Ci.nsIDOMElement);
+ var f = dirEntries[i];
+
+ var sep = f.isDirectory ? "/" : "";
+
+ do_check_eq(link.textContent, f.name + sep);
+
+ uri = ios.newURI(link.getAttribute("href"), null, top);
+ do_check_eq(decodeURIComponent(uri.path), 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: name, isDirectory: 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)
+{
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/html;charset=utf-8");
+}
+function stopRootDirectory(ch, cx, status, data)
+{
+ dataCheck(data, BASE_URL, "/", dirEntries[0]);
+}
+
+// check non-top-level, too
+function stopFooDirectory(ch, cx, status, data)
+{
+ dataCheck(data, BASE_URL + "foo/", "/foo/", dirEntries[1]);
+}
+
+// trailing-caret leaf with hidden files
+function stopTrailingCaretDirectory(ch, cx, 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..85cc3d3453
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_empty_body.js
@@ -0,0 +1,55 @@
+/* -*- 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, cx)
+{
+ do_check_true(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..c70dd1f114
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_errorhandler_exception.js
@@ -0,0 +1,84 @@
+/* -*- 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)
+{
+ do_check_eq(channel.responseStatus, httpCode);
+ do_check_eq(channel.responseStatusText, statusText);
+
+ var respMaj = {}, respMin = {};
+ channel.getResponseVersion(respMaj, respMin);
+ do_check_eq(respMaj.value, httpMaxVer);
+ do_check_eq(respMin.value, httpMinVer);
+}
+
+function start_throws_exception(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 500, "Internal Server Error");
+}
+
+function start_nonexistent_404_fails_so_400(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 400, "Bad Request");
+}
+
+function start_multiple_exceptions_500(ch, cx)
+{
+ checkStatusLine(ch, 1, 1, 500, "Internal Server Error");
+}
+
+function succeeded(ch, cx, status, data)
+{
+ do_check_true(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 "this shouldn't cause an exit...";
+ do_throw("Not reached!");
+}
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..2933f9aa68
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_header_array.js
@@ -0,0 +1,67 @@
+/* -*- 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, cx)
+{
+ var headerValue;
+
+ headerValue = ch.getResponseHeader("Proxy-Authenticate");
+ do_check_eq(headerValue, "First line 1\nSecond line 1\nThird line 1");
+ headerValue = ch.getResponseHeader("WWW-Authenticate");
+ do_check_eq(headerValue, "First line 2\nSecond line 2\nThird line 2");
+ headerValue = ch.getResponseHeader("X-Single-Header-Merge");
+ do_check_eq(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..74314a9668
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_headers.js
@@ -0,0 +1,189 @@
+/* -*- 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 "Setting (" + fieldName + ", " +
+ fieldValue + ") as header succeeded!";
+ }
+ catch (e)
+ {
+ if (e !== 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");
+ do_check_eq(c, "text/html");
+
+ headers.setHeader("test", "FOO", false);
+ var c = headers.getHeader("test");
+ do_check_eq(c, "FOO");
+
+ try
+ {
+ headers.getHeader(":");
+ throw "Failed to throw for invalid header";
+ }
+ catch (e)
+ {
+ if (e !== Cr.NS_ERROR_INVALID_ARG)
+ do_throw("headers.getHeader(':') must throw invalid arg");
+ }
+
+ try
+ {
+ headers.getHeader("valid");
+ throw 'header doesn\'t exist';
+ }
+ catch (e)
+ {
+ if (e !== 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;
+ do_check_true(it.toLowerCase() in heads);
+ delete heads[it.toLowerCase()];
+ }
+
+ for (var i in heads)
+ do_throw("still have properties in heads!?!?");
+
+}
+
+function testHasHeader()
+{
+ var headers = new nsHttpHeaders();
+
+ headers.setHeader("foo", "bar", false);
+ do_check_true(headers.hasHeader("foo"));
+ do_check_true(headers.hasHeader("fOo"));
+ do_check_false(headers.hasHeader("not-there"));
+
+ headers.setHeader("f`'~", "bar", false);
+ do_check_true(headers.hasHeader("F`'~"));
+
+ try
+ {
+ headers.hasHeader(":");
+ throw "failed to throw";
+ }
+ catch (e)
+ {
+ if (e !== 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..503a04fef0
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_host.js
@@ -0,0 +1,666 @@
+/* -*- 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.
+ */
+
+const PORT = 4444;
+const FAKE_PORT_ONE = 8888;
+const FAKE_PORT_TWO = 8889;
+
+var srv, id;
+
+function run_test()
+{
+ dumpn("*** run_test");
+
+ 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.
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ // This should be a nop.
+ id.add("http", "localhost", FAKE_PORT_ONE);
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_true(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);
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "127.0.0.1");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_true(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);
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(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);
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, FAKE_PORT_ONE);
+ do_check_true(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(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.
+ 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();
+
+ // Our primary location is gone because it was dependent on the port on which
+ // the server was running.
+ checkPrimariesThrow(id);
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_false(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.
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost", FAKE_PORT_TWO);
+ do_check_eq(id.primaryPort, FAKE_PORT_TWO);
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_true(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_true(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);
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_true(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+
+ id.remove("http", "localhost", FAKE_PORT_TWO);
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_true(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ id.remove("http", "127.0.0.1", FAKE_PORT_TWO);
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+
+ do_test_pending();
+ srv.stop(function()
+ {
+ try
+ {
+ do_test_pending();
+ run_test_3();
+ }
+ finally
+ {
+ do_test_finished();
+ }
+ });
+}
+
+function run_test_3()
+{
+ dumpn("*** run_test_3");
+
+ do_test_finished();
+
+ // 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.
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "example.com");
+ do_check_eq(id.primaryPort, FAKE_PORT_TWO);
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(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.
+ do_check_true(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_true(id.has("http", "localhost", PORT));
+ do_check_true(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.
+ do_check_true(id.has("http", "localhost", 80));
+ do_check_eq(id.primaryScheme, "http");
+ do_check_eq(id.primaryHost, "localhost");
+ do_check_eq(id.primaryPort, PORT);
+ do_check_true(id.has("http", "localhost", PORT));
+ do_check_true(id.has("http", "127.0.0.1", PORT));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "127.0.0.1", FAKE_PORT_ONE));
+ do_check_false(id.has("http", "example.com", FAKE_PORT_TWO));
+ do_check_false(id.has("http", "localhost", FAKE_PORT_TWO));
+ do_check_false(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!
+ runRawTests(tests, testComplete(srv));
+}
+
+
+/*********************
+ * UTILITY FUNCTIONS *
+ *********************/
+
+/**
+ * Verifies that all .primary* getters on a server identity correctly throw
+ * NS_ERROR_NOT_INITIALIZED.
+ *
+ * @param id : nsIHttpServerIdentity
+ * the server identity to test
+ */
+function checkPrimariesThrow(id)
+{
+ var threw = false;
+ try
+ {
+ id.primaryScheme;
+ }
+ catch (e)
+ {
+ threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ do_check_true(threw);
+
+ threw = false;
+ try
+ {
+ id.primaryHost;
+ }
+ catch (e)
+ {
+ threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ do_check_true(threw);
+
+ threw = false;
+ try
+ {
+ id.primaryPort;
+ }
+ catch (e)
+ {
+ threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+ }
+ do_check_true(threw);
+}
+
+/**
+ * Utility function to check for a 400 response.
+ */
+function check400(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ var firstLine = iter.next();
+ do_check_eq(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(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "HTTP/1.0 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var 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
+
+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 = "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
+
+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 = "GET /http/1.1-good-host HTTP/1.1\r\n" +
+ "Host: localhost:4444\r\n" +
+ "\r\n";
+function check11goodHost(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "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: /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 = "GET /http/1.1-ip-host HTTP/1.1\r\n" +
+ "Host: 127.0.0.1:4444\r\n" +
+ "\r\n";
+function check11ipHost(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "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: /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(data)
+{
+ check11goodHost(data);
+
+ // 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(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "HTTP/1.0 200 TEST PASSED");
+
+ skipHeaders(iter);
+
+ // Okay, next line must be the data we expected to be written
+ var 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(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ do_check_eq(iter.next(), "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: /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
+
+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..49f4f82587
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_linedata.js
@@ -0,0 +1,20 @@
+/* -*- 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: "" };
+ do_check_false(data.readLine(out));
+
+ data.appendBytes([LF]);
+ do_check_true(data.readLine(out));
+ do_check_eq(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..8233833fa1
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_load_module.js
@@ -0,0 +1,16 @@
+/* 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() {
+ Components.utils.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..154f73d25d
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_name_scheme.js
@@ -0,0 +1,90 @@
+/* -*- 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, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/html");
+}
+
+function start_foo_html_(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 404);
+}
+
+function start_normal_file_txt(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.getResponseHeader("Content-Type"), "text/plain");
+}
+
+function start_folder__file_txt(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(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..21ded660d7
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_processasync.js
@@ -0,0 +1,304 @@
+/* -*- 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, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(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, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "New status line!");
+ do_check_eq(ch.getResponseHeader("X-Foo"), "new value");
+}
+
+function stop_handleAsync1(ch, cx, status, data)
+{
+ do_check_eq(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 body = "BO";
+ response.bodyOutputStream.write(body, body.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 body = "DY";
+ response.bodyOutputStream.write(body, body.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, cx)
+{
+ var now = Date.now();
+ dumpn("*** start_handleAsync2: onStartRequest time " + now + ", " +
+ (now - startTime_handleAsync2) + "ms after start time");
+ do_check_true(now >= startTime_handleAsync2 + startToHeaderDelay);
+
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "Status line");
+ do_check_eq(ch.getResponseHeader("X-Custom-Header"), "value");
+}
+
+function stop_handleAsync2(ch, cx, status, data)
+{
+ var now = Date.now();
+ dumpn("*** stop_handleAsync2: onStopRequest time " + now + ", " +
+ (now - startTime_handleAsync2) + "ms after header time");
+ do_check_true(now >= startTime_handleAsync2 + startToFinishedDelay);
+
+ do_check_eq(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: function()
+ {
+ if (count-- === 0)
+ {
+ response.finish();
+ return;
+ }
+
+ try
+ {
+ out.writeByteArray(data, data.length);
+ 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.currentThread
+ .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ step();
+ response.processAsync();
+}
+handlers["/handleAsyncOrdering"] = handleAsyncOrdering;
+
+function stop_handleAsyncOrdering(ch, cx, status, data)
+{
+ do_check_eq(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..aeac79d891
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_qi.js
@@ -0,0 +1,110 @@
+/* -*- 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/. */
+
+/*
+ * 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();
+
+ var qi;
+ try
+ {
+ qi = 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, cx)
+{
+ do_check_eq(ch.responseStatusText, "QI Tests Passed");
+ do_check_eq(ch.responseStatus, 200);
+}
+
+function start_sjs_qi(ch, cx)
+{
+ do_check_eq(ch.responseStatusText, "SJS QI Tests Passed");
+ do_check_eq(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..fbb41293e9
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerdirectory.js
@@ -0,0 +1,263 @@
+/* -*- 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)
+{
+ do_check_eq(ch.responseStatus, 404);
+ do_check_false(ch.requestSucceeded);
+}
+
+function checkOverride(ch)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "OK");
+ do_check_true(ch.requestSucceeded);
+ do_check_eq(ch.getResponseHeader("Override-Succeeded"), "yes");
+}
+
+function check200(ch)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "OK");
+}
+
+function checkFile(ch, cx, status, data)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_true(ch.requestSucceeded);
+
+ var actualFile = serverBasePath.clone();
+ actualFile.append("test_registerdirectory.js");
+ do_check_eq(ch.getResponseHeader("Content-Length"),
+ actualFile.fileSize.toString());
+ do_check_eq(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..16a1270f51
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerfile.js
@@ -0,0 +1,50 @@
+/* -*- 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, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+}
+
+function onStop(ch, cx, status, data)
+{
+ // not sufficient for equality, but not likely to be wrong!
+ do_check_eq(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 "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..fa3c3390a2
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerprefix.js
@@ -0,0 +1,127 @@
+/* -*- 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)
+{
+ do_check_eq(ch.responseStatus, 404);
+ do_check_false(ch.requestSucceeded);
+}
+
+function makeCheckOverride(magic)
+{
+ return (function checkOverride(ch)
+ {
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(ch.responseStatusText, "OK");
+ do_check_true(ch.requestSucceeded);
+ do_check_eq(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..b1a863f485
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js
@@ -0,0 +1,135 @@
+/* -*- 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, data, 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";
+data = [];
+for (var i = 0; i < str.length; i += 16384)
+ data.push(str.substr(i, 16384));
+
+function checkVeryLongRequestLine(data)
+{
+ var iter = LineIterator(data);
+
+ print("data length: " + data.length);
+ print("iter object: " + iter);
+
+ // Status-Line
+ do_check_eq(iter.next(), "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, data, checkVeryLongRequestLine),
+tests.push(test);
+
+
+function lotsOfLeadingBlankLines(request, response)
+{
+ writeDetails(request, response);
+ response.setStatusLine(request.httpVersion, 200, "TEST PASSED");
+}
+
+var blankLines = "\r\n";
+for (var 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";
+data = [];
+for (var i = 0; i < str.length; i += 100)
+ data.push(str.substr(i, 100));
+
+function checkLotsOfLeadingBlankLines(data)
+{
+ var iter = LineIterator(data);
+
+ // Status-Line
+ print("data length: " + data.length);
+ print("iter object: " + iter);
+
+ do_check_eq(iter.next(), "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, data, 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..0a37ee44b3
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_response_write.js
@@ -0,0 +1,55 @@
+/* -*- 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, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+ do_check_eq(data.map(v => String.fromCharCode(v)).join(""), "1234");
+}
+
+function check_1234(ch, cx)
+{
+ do_check_eq(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..f2d9e32c1c
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_seizepower.js
@@ -0,0 +1,182 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests that the seizePower API works correctly.
+ */
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return srv.identity.primaryPort;
+});
+
+var srv;
+
+function run_test()
+{
+ 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, 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.currentThread.dispatch({
+ run: function()
+ {
+ fun();
+ }
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+}
+
+
+/*****************
+ * 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),
+ ];
+});
+
+var data0 = "GET /raw-data HTTP/1.0\r\n" +
+ "\r\n";
+function checkRawData(data)
+{
+ do_check_eq(data, "Raw data!");
+}
+
+var data1 = "GET /called-too-late HTTP/1.0\r\n" +
+ "\r\n";
+function checkTooLate(data)
+{
+ do_check_eq(LineIterator(data).next(), "too-late passed");
+}
+
+var data2 = "GET /exceptions HTTP/1.0\r\n" +
+ "\r\n";
+function checkExceptions(data)
+{
+ do_check_eq("exceptions test passed", data);
+}
+
+var data3 = "GET /async-seizure HTTP/1.0\r\n" +
+ "\r\n";
+function checkAsyncSeizure(data)
+{
+ do_check_eq(data, "async seizure passed");
+}
+
+var data4 = "GET /seize-after-async HTTP/1.0\r\n" +
+ "\r\n";
+function checkSeizeAfterAsync(data)
+{
+ do_check_eq(LineIterator(data).next(), "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..6e733f4dbb
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_setindexhandler.js
@@ -0,0 +1,67 @@
+/* -*- 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, cx)
+{
+ do_check_eq(ch.getResponseHeader("Content-Length"), "10");
+ srv.setIndexHandler(null);
+}
+function stopCustomIndexHandler(ch, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+ do_check_eq(String.fromCharCode.apply(null, data), "directory!");
+}
+
+function startDefaultIndexHandler(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+}
+function stopDefaultIndexHandler(ch, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+}
+
+// PATH HANDLERS
+
+function myIndexHandler(metadata, response)
+{
+ var dir = metadata.getProperty("directory");
+ do_check_true(dir != null);
+ do_check_true(dir instanceof Ci.nsIFile);
+ do_check_true(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..f6f6514887
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_setstatusline.js
@@ -0,0 +1,172 @@
+/* -*- 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)
+{
+ do_check_eq(channel.responseStatus, httpCode);
+ do_check_eq(channel.responseStatusText, statusText);
+
+ var respMaj = {}, respMin = {};
+ channel.getResponseVersion(respMaj, respMin);
+ do_check_eq(respMaj.value, httpMaxVer);
+ do_check_eq(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, cx)
+{
+ checkStatusLine(ch, 1, 1, 200, "OK");
+}
+function stop(ch, cx, status, data)
+{
+ do_check_true(Components.isSuccessCode(status));
+}
+
+
+// /http1_0
+function http1_0(metadata, response)
+{
+ response.setStatusLine("1.0", 200, "OK");
+}
+function startHttp1_0(ch, cx)
+{
+ checkStatusLine(ch, 1, 0, 200, "OK");
+}
+
+
+// /http1_1
+function http1_1(metadata, response)
+{
+ response.setStatusLine("1.1", 200, "OK");
+}
+function startHttp1_1(ch, cx)
+{
+ 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, cx)
+{
+ checkStatusLine(ch, 1, 1, 200, "OK");
+ do_check_eq(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, cx)
+{
+ checkStatusLine(ch, 1, 1, 617, "Crazy");
+}
+
+
+// /nullVersion
+function nullVersion(metadata, response)
+{
+ response.setStatusLine(null, 255, "NULL");
+}
+function startNullVersion(ch, cx)
+{
+ // 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..2a9814196c
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs.js
@@ -0,0 +1,251 @@
+/* -*- 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, cx, status, bytes)
+ {
+ dumpn(channel.originalURI.spec);
+ do_check_eq(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, cx)
+ {
+ if (throwing)
+ {
+ do_check_false(ch.requestSucceeded);
+ do_check_eq(ch.responseStatus, 500);
+ }
+ else
+ {
+ do_check_eq(ch.contentType, "text/plain");
+ }
+ }
+
+ function checkContents(ch, cx, status, data)
+ {
+ if (!throwing)
+ do_check_eq("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, cx, status, data)
+ {
+ checkContents(ch, cx, 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, cx, status, data)
+{
+ do_check_neq("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, cx)
+{
+ 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);
+ }
+ do_check_eq(200, ch.responseStatus);
+ do_check_eq("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, cx)
+{
+ do_check_eq(ch.contentType, "text/plain");
+}
+
+function onStop(ch, cx, status, data)
+{
+ do_check_eq("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 "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..b0f4e546d9
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_object_state.js
@@ -0,0 +1,290 @@
+/* -*- 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, cx)
+{
+ 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, cx, status, data)
+{
+ dumpn("*** initialStop");
+
+ do_check_eq(data.map(function(v) { return String.fromCharCode(v); }).join(""),
+ "done");
+
+ do_check_eq(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, cx)
+{
+ dumpn("*** intermediateStart");
+
+ do_check_neq(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, cx, status, data)
+{
+ dumpn("*** intermediateStop");
+
+ do_check_eq(data.map(function(v) { return String.fromCharCode(v); }).join(""),
+ "intermediate");
+
+ do_check_neq(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, cx)
+{
+ 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, cx, status, data)
+{
+ dumpn("*** triggerStop");
+
+ do_check_eq(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
+ {
+ quit(1);
+ }
+ }
+
+ if (triggerStopped && initialStopped)
+ {
+ finished = true;
+ try
+ {
+ do_check_eq(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.asyncOpen2(this);
+}
+HTTPTestLoader.prototype =
+ {
+ onStartRequest: function(request, cx)
+ {
+ dumpn("*** HTTPTestLoader.onStartRequest for " + this._path);
+
+ var ch = request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ try
+ {
+ try
+ {
+ this._start(ch, cx);
+ }
+ catch (e)
+ {
+ do_throw(this._path + ": error in onStartRequest: " + e);
+ }
+ }
+ catch (e)
+ {
+ dumpn("!!! swallowing onStartRequest exception so onStopRequest is " +
+ "called...");
+ }
+ },
+ onDataAvailable: function(request, cx, inputStream, offset, count)
+ {
+ dumpn("*** HTTPTestLoader.onDataAvailable for " + this._path);
+
+ Array.prototype.push.apply(this._data,
+ makeBIS(inputStream).readByteArray(count));
+ },
+ onStopRequest: function(request, cx, status)
+ {
+ dumpn("*** HTTPTestLoader.onStopRequest for " + this._path);
+
+ var ch = request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ this._stop(ch, cx, status, this._data);
+ },
+ QueryInterface: function(aIID)
+ {
+ dumpn("*** QueryInterface: " + aIID);
+
+ if (aIID.equals(Ci.nsIStreamListener) ||
+ aIID.equals(Ci.nsIRequestObserver) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
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..ccf5c4b031
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_state.js
@@ -0,0 +1,186 @@
+/* -*- 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()
+ {
+ do_check_eq(srv.getSharedState("shared-value"), "done!");
+ do_check_eq(srv.getState("/path-handler", "private-value"),
+ "pathHandlerPrivate2");
+ do_check_eq(srv.getState("/state1.sjs", "private-value"),
+ "");
+ do_check_eq(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(URL + "/state1.sjs?" +
+ "newShared=newShared&newPrivate=newPrivate",
+ null, start_initial, null),
+ new Test(URL + "/state1.sjs?" +
+ "newShared=newShared2&newPrivate=newPrivate2",
+ null, start_overwrite, null),
+ new Test(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(URL + "/state2.sjs?" +
+ "newShared=newShared4&newPrivate=newPrivate4",
+ null, start_other_initial, null),
+ new Test(URL + "/state2.sjs?" +
+ "newShared=",
+ null, start_other_remove_ignore, null),
+ new Test(URL + "/state2.sjs?" +
+ "newShared=newShared5&newPrivate=newPrivate5",
+ null, start_other_set_new, null),
+ new Test(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);
+
+ do_check_eq(ch.responseStatus, 200);
+ do_check_eq(getHeader("X-Old-Shared-Value"), oldShared);
+ do_check_eq(getHeader("X-New-Shared-Value"), newShared);
+ do_check_eq(getHeader("X-Old-Private-Value"), oldPrivate);
+ do_check_eq(getHeader("X-New-Private-Value"), newPrivate);
+}
+
+function start_initial(ch, cx)
+{
+dumpn("XXX start_initial");
+ expectValues(ch, "", "newShared", "", "newPrivate");
+}
+
+function start_overwrite(ch, cx)
+{
+ expectValues(ch, "newShared", "newShared2", "newPrivate", "newPrivate2");
+}
+
+function start_remove(ch, cx)
+{
+ expectValues(ch, "newShared2", "", "newPrivate2", "newPrivate3");
+}
+
+function start_handler(ch, cx)
+{
+ expectValues(ch, "", "pathHandlerShared", "", "pathHandlerPrivate");
+}
+
+function start_handler_again(ch, cx)
+{
+ expectValues(ch, "pathHandlerShared", "",
+ "pathHandlerPrivate", "pathHandlerPrivate2");
+}
+
+function start_other_initial(ch, cx)
+{
+ expectValues(ch, "", "newShared4", "", "newPrivate4");
+}
+
+function start_other_remove_ignore(ch, cx)
+{
+ expectValues(ch, "newShared4", "", "newPrivate4", "");
+}
+
+function start_other_set_new(ch, cx)
+{
+ expectValues(ch, "", "newShared5", "newPrivate4", "newPrivate5");
+}
+
+function start_set_remove_original(ch, cx)
+{
+ 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..2e4855b01f
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_sjs_throwing_exceptions.js
@@ -0,0 +1,74 @@
+/* -*- 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(); });
+ do_check_eq(gStartCount, TEST_RUNS);
+ do_check_true(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, cx)
+{
+ do_check_eq(ch.responseStatus, 500);
+ do_check_false(ch.requestSucceeded);
+
+ gStartCount++;
+}
+
+function start_last(ch, cx)
+{
+ do_check_eq(ch.responseStatus, 200);
+ do_check_true(ch.requestSucceeded);
+
+ do_check_eq(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..e9f42bddd8
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_start_stop.js
@@ -0,0 +1,189 @@
+/* -*- 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);
+ }
+
+ try
+ {
+ srv.stop();
+ do_throw("missing argument to stop");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ try
+ {
+ srv.stop(null);
+ do_throw("null argument to stop");
+ }
+ catch (e)
+ {
+ isException(e, Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ 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: function()
+ {
+ 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.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_4 or serverStopped handle
+ // the final shutdown.
+ do_test_pending();
+
+ runHttpTests([new Test(PREPATH + "/handle")], run_test_4);
+}
+
+var testsComplete = false;
+
+function run_test_4()
+{
+ dumpn("*** run_test_4");
+
+ 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()
+ {
+ do_check_false(stopped);
+
+ callLater(INTERVAL, function()
+ {
+ do_check_false(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..7890917a6b
--- /dev/null
+++ b/netwerk/test/httpserver/test/xpcshell.ini
@@ -0,0 +1,37 @@
+[DEFAULT]
+head = head_utils.js
+tail =
+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]
+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]
+# Bug 1063533: frequent timeouts on Android 4.3
+skip-if = os == "android"
+[test_start_stop.js]
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_loadinfo_redirectchain.sjs b/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs
new file mode 100644
index 0000000000..88cacbdb97
--- /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].URI.spec);
+ }
+ for (var i = 0; i < redirectChainIncludingInternalRedirects.length; i++) {
+ resultOBJ.redirectChainIncludingInternalRedirects.push(redirectChainIncludingInternalRedirects[i].URI.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/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..98d406c803
--- /dev/null
+++ b/netwerk/test/mochitests/mochitest.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+support-files =
+ method.sjs
+ partial_content.sjs
+ rel_preconnect.sjs
+ user_agent.sjs
+ user_agent_update.sjs
+ web_packaged_app.sjs
+ file_loadinfo_redirectchain.sjs
+ redirect_idn.html^headers^
+ redirect_idn.html
+ empty.html
+ redirect.sjs
+
+[test_arraybufferinputstream.html]
+[test_idn_redirect.html]
+[test_loadinfo_redirectchain.html]
+[test_partially_cached_content.html]
+[test_rel_preconnect.html]
+[test_redirect_ref.html]
+[test_uri_scheme.html]
+[test_user_agent_overrides.html]
+[test_user_agent_updates.html]
+[test_user_agent_updates_reset.html]
+[test_viewsource_unlinkable.html]
+[test_xhr_method_case.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..e36f7c99ae
--- /dev/null
+++ b/netwerk/test/mochitests/redirect.sjs
@@ -0,0 +1,8 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+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/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/signed_web_packaged_app.sjs b/netwerk/test/mochitests/signed_web_packaged_app.sjs
new file mode 100644
index 0000000000..6bcac6ffd4
--- /dev/null
+++ b/netwerk/test/mochitests/signed_web_packaged_app.sjs
@@ -0,0 +1,78 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+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/test_arraybufferinputstream.html b/netwerk/test/mochitests/test_arraybufferinputstream.html
new file mode 100644
index 0000000000..eb7796ef2d
--- /dev/null
+++ b/netwerk/test/mochitests/test_arraybufferinputstream.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>ArrayBuffer stream test</title>
+ <script type="text/javascript" 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, Cr = SpecialPowers.Cr;
+ 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_idn_redirect.html b/netwerk/test/mochitests/test_idn_redirect.html
new file mode 100644
index 0000000000..b9a594cc56
--- /dev/null
+++ b/netwerk/test/mochitests/test_idn_redirect.html
@@ -0,0 +1,36 @@
+<!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 type="text/javascript" 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_loadinfo_redirectchain.html b/netwerk/test/mochitests/test_loadinfo_redirectchain.html
new file mode 100644
index 0000000000..4568db0c2b
--- /dev/null
+++ b/netwerk/test/mochitests/test_loadinfo_redirectchain.html
@@ -0,0 +1,213 @@
+<!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 type="text/javascript" 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"
+ ];
+
+ 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].URI.spec,
+ EXPECTED_REDIRECT_CHAIN[i],
+ "redirectChain at index [" + i + "] should match");
+ is(redirectChainIncludingInternalRedirects[i].URI.spec,
+ EXPECTED_REDIRECT_CHAIN[i],
+ "redirectChainIncludingInternalRedirects at index [" + i + "] 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, false);
+ 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, false);
+ 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, false);
+ 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, false);
+ 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_partially_cached_content.html b/netwerk/test/mochitests/test_partially_cached_content.html
new file mode 100644
index 0000000000..65a56dfe5b
--- /dev/null
+++ b/netwerk/test/mochitests/test_partially_cached_content.html
@@ -0,0 +1,92 @@
+<!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 type="application/javascript" 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, false);
+
+ // Reload.
+ frameElement.src="partial_content.sjs";
+}
+
+/* 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..40ea166589
--- /dev/null
+++ b/netwerk/test/mochitests/test_redirect_ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title> Bug 1234575 - Test redirect ref</title>
+ <script type="text/javascript" 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..4e94fb4cee
--- /dev/null
+++ b/netwerk/test/mochitests/test_rel_preconnect.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for link rel=preconnect</title>
+ <script type="text/javascript" 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;
+
+function doTest()
+{
+ 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", false);
+
+ // 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_uri_scheme.html b/netwerk/test/mochitests/test_uri_scheme.html
new file mode 100644
index 0000000000..1f01a4d0a1
--- /dev/null
+++ b/netwerk/test/mochitests/test_uri_scheme.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for URI Manipulation</title>
+ <script type="text/javascript" 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_user_agent_overrides.html b/netwerk/test/mochitests/test_user_agent_overrides.html
new file mode 100644
index 0000000000..7396c35e13
--- /dev/null
+++ b/netwerk/test/mochitests/test_user_agent_overrides.html
@@ -0,0 +1,240 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=782453
+-->
+<head>
+ <title>Test for User Agent Overrides</title>
+ <script type="application/javascript" 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=782453">Mozilla Bug 782453</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides";
+const PREF_OVERRIDES_BRANCH = "general.useragent.override.";
+
+const DEFAULT_UA = navigator.userAgent;
+
+const UA_WHOLE_OVERRIDE = "DummyUserAgent";
+const UA_WHOLE_EXPECTED = UA_WHOLE_OVERRIDE;
+
+const UA_PARTIAL_FROM = "\\wozilla"; // /\wozilla
+const UA_PARTIAL_SEP = "#";
+const UA_PARTIAL_TO = UA_WHOLE_OVERRIDE;
+const UA_PARTIAL_OVERRIDE = UA_PARTIAL_FROM + UA_PARTIAL_SEP + UA_PARTIAL_TO;
+const UA_PARTIAL_EXPECTED = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM, 'g'), UA_PARTIAL_TO);
+
+function testUAIFrame(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) {
+ let url = location.pathname;
+ url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+ let ifr = document.createElement('IFRAME');
+
+ ifr.src = url;
+
+ document.getElementById('content').appendChild(ifr);
+
+ window.addEventListener("message", function recv(e) {
+ ok(sameQ == (e.data.header.indexOf(expected) != -1), message);
+ if (testNavQ) {
+ ok(navSameQ == (e.data.nav.indexOf(expected) != -1), navMessage);
+ }
+ window.removeEventListener("message", recv, false);
+ callback();
+ }, false);
+
+}
+
+function testUAIFrameNoNav(host, expected, sameQ, message, callback) {
+ testUAIFrame(host, expected, sameQ, message, false, true, '', callback);
+}
+
+function testUA(options, callback) {
+ var [domain, override, test_hosts, expected] =
+ [options.domain, options.override, options.test_hosts, options.expected];
+
+ (function nextTest() {
+ let test_host = test_hosts.shift();
+
+ info("Overriding " + domain + " with " + override + " for " + test_host);
+
+ function is_subdomain(host) {
+ var [test_domain] = host.slice(host.lastIndexOf('/') + 1).split(':', 1);
+ return test_domain === domain || test_domain.endsWith('.' + domain);
+ }
+
+ var localhost = location.origin;
+ var overrideNavigator = is_subdomain(localhost);
+ var navigator_ua, test_ua;
+
+ if (overrideNavigator) {
+ navigator_ua = navigator.userAgent;
+ }
+
+ let url = location.pathname;
+ url = test_host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+ let ifr = document.createElement('IFRAME');
+ ifr.src = url;
+
+ document.getElementById('content').appendChild(ifr);
+
+ window.addEventListener("message", function recv(e) {
+ test_ua = e.data.header;
+ SpecialPowers.pushPrefEnv({
+ set: [[PREF_OVERRIDES_BRANCH + domain, override]],
+ }, function () {
+ testUAIFrame(test_host, expected, true, 'Header UA not overridden at step ' + (++step), true,
+ true, 'Navigator UA not overridden at step ' + (++step), function () {
+ // clear the override pref to undo overriding the UA
+ SpecialPowers.pushPrefEnv({
+ clear: [[PREF_OVERRIDES_BRANCH + domain]],
+ }, function () {
+ testUAIFrameNoNav(test_host, test_ua, true, 'Header UA not restored at step ' + (++step), function() {
+ test_hosts.length ? nextTest() : callback();
+ });
+ });
+ });
+ });
+ window.removeEventListener("message", recv, false);
+ }, false);
+ })();
+}
+
+var step = 0; // for logging
+var tests = [
+ // should override both header and navigator.userAgent
+ {
+ domain: location.hostname,
+ override: UA_WHOLE_OVERRIDE,
+ test_hosts: [location.origin],
+ expected: UA_WHOLE_EXPECTED
+ },
+
+ // should support partial overrides
+ {
+ domain: location.hostname,
+ override: UA_PARTIAL_OVERRIDE,
+ test_hosts: [location.origin],
+ expected: UA_PARTIAL_EXPECTED
+ },
+
+ // should match domain and subdomains
+ {
+ domain: 'example.org',
+ override: UA_WHOLE_OVERRIDE,
+ test_hosts: ['http://example.org',
+ 'http://test1.example.org',
+ 'http://sub1.test1.example.org'],
+ expected: UA_WHOLE_EXPECTED
+ },
+
+ // should not match superdomains
+ {
+ domain: 'sub1.test1.example.org',
+ override: UA_WHOLE_OVERRIDE,
+ test_hosts: ['http://example.org',
+ 'http://test1.example.org'],
+ expected: DEFAULT_UA
+ },
+
+ // should work with https
+ {
+ domain: 'example.com',
+ override: UA_WHOLE_OVERRIDE,
+ test_hosts: ['https://example.com',
+ 'https://test1.example.com',
+ 'https://sub1.test1.example.com'],
+ expected: UA_WHOLE_EXPECTED
+ },
+];
+
+// test that UA is not overridden when the 'site_specific_overrides' pref is off
+function testInactive(callback) {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_OVERRIDES_ENABLED, false],
+ [PREF_OVERRIDES_BRANCH + location.hostname, UA_WHOLE_OVERRIDE]
+ ]
+ }, function () {
+ testUAIFrame(location.origin, UA_WHOLE_OVERRIDE, false, 'Failed to disabled header UA override at step ' + (++step),
+ true, false, 'Failed to disable navigator UA override at step + ' + (++step), function () {
+ SpecialPowers.pushPrefEnv({
+ clear: [
+ [PREF_OVERRIDES_ENABLED],
+ [PREF_OVERRIDES_BRANCH + location.hostname]
+ ]
+ }, callback);
+ });
+ });
+}
+
+function testPriority(callback) {
+ // foo.bar.com override should have priority over bar.com override
+ var tests = [
+ ['example.org', 'test1.example.org', 'sub1.test1.example.org'],
+ ['example.org', 'test1.example.org', 'sub2.test1.example.org'],
+ ['example.org', 'test2.example.org', 'sub1.test2.example.org'],
+ ['example.org', 'test2.example.org', 'sub2.test2.example.org'],
+ ];
+ (function nextTest() {
+ var [level0, level1, level2] = tests.shift();
+ var host = 'http://' + level2;
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_OVERRIDES_ENABLED, true],
+ [PREF_OVERRIDES_BRANCH + level1, UA_WHOLE_OVERRIDE]
+ ]
+ }, function () {
+ // should use first override at this point
+ testUAIFrameNoNav(host, UA_WHOLE_EXPECTED, true, 'UA not overridden at step ' + (++step), function() {
+ // add a second override that should be used
+ testUA({
+ domain: level2,
+ override: UA_PARTIAL_OVERRIDE,
+ test_hosts: [host],
+ expected: UA_PARTIAL_EXPECTED
+ }, function () {
+ // add a third override that should not be used
+ testUA({
+ domain: level0,
+ override: UA_PARTIAL_OVERRIDE,
+ test_hosts: [host],
+ expected: UA_WHOLE_EXPECTED
+ }, tests.length ? nextTest : callback);
+ });
+ });
+ });
+ })();
+}
+
+function testOverrides(callback) {
+ SpecialPowers.pushPrefEnv({
+ set: [[PREF_OVERRIDES_ENABLED, true]]
+ }, function nextTest() {
+ testUA(tests.shift(), function() { tests.length ? nextTest() : callback() });
+ });
+}
+
+SpecialPowers.loadChromeScript(_ => {
+ Components.utils.import("resource://gre/modules/UserAgentOverrides.jsm");
+ UserAgentOverrides.init();
+});
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestCompleteLog();
+SimpleTest.requestLongerTimeout(5);
+
+testOverrides(function() {
+ testInactive(function() {
+ testPriority(SimpleTest.finish)
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_user_agent_updates.html b/netwerk/test/mochitests/test_user_agent_updates.html
new file mode 100644
index 0000000000..839f9e0005
--- /dev/null
+++ b/netwerk/test/mochitests/test_user_agent_updates.html
@@ -0,0 +1,369 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=897221
+-->
+<head>
+ <title>Test for User Agent Updates</title>
+ <script type="text/javascript" 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=897221">Mozilla Bug 897221</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
+const PREF_UPDATES = "general.useragent.updates.";
+const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
+const PREF_UPDATES_URL = PREF_UPDATES + "url";
+const PREF_UPDATES_INTERVAL = PREF_UPDATES + "interval";
+const PREF_UPDATES_TIMEOUT = PREF_UPDATES + "timeout";
+
+const DEFAULT_UA = navigator.userAgent;
+const UA_OVERRIDE = "DummyUserAgent";
+const UA_ALT_OVERRIDE = "AltUserAgent";
+
+const UA_PARTIAL_FROM = "\\wozilla"; // /\wozilla
+const UA_PARTIAL_SEP = "#";
+const UA_PARTIAL_TO = UA_OVERRIDE;
+const UA_PARTIAL_OVERRIDE = UA_PARTIAL_FROM + UA_PARTIAL_SEP + UA_PARTIAL_TO;
+const UA_PARTIAL_EXPECTED = DEFAULT_UA.replace(new RegExp(UA_PARTIAL_FROM, 'g'), UA_PARTIAL_TO);
+
+function getUA(host) {
+ var url = location.pathname;
+ url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false); // sync request
+ xhr.send();
+ is(xhr.status, 200, 'request failed');
+ is(typeof xhr.response, 'string', 'invalid response');
+ return xhr.response;
+}
+
+function testUAIFrame(host, expected, sameQ, message, testNavQ, navSameQ, navMessage, callback) {
+ let url = location.pathname;
+ url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+ let ifr = document.createElement('IFRAME');
+
+ ifr.src = url;
+
+ document.getElementById('content').appendChild(ifr);
+
+ window.addEventListener("message", function recv(e) {
+ ok(sameQ == (e.data.header.indexOf(expected) != -1), message);
+ if (testNavQ) {
+ ok(navSameQ == (e.data.nav.indexOf(expected) != -1), navMessage);
+ }
+ window.removeEventListener("message", recv, false);
+ callback();
+ }, false);
+}
+
+function testUAIFrameNoNav(host, expected, sameQ, message, callback) {
+ testUAIFrame(host, expected, sameQ, message, false, true, '', callback);
+}
+
+const OVERRIDES = [
+ {
+ domain: 'example.org',
+ override: '%DATE%',
+ host: 'http://example.org'
+ },
+ {
+ domain: 'test1.example.org',
+ override: '%PRODUCT%',
+ expected: SpecialPowers.Services.appinfo.name,
+ host: 'http://test1.example.org'
+ },
+ {
+ domain: 'test2.example.org',
+ override: '%APP_ID%',
+ expected: SpecialPowers.Services.appinfo.ID,
+ host: 'http://test2.example.org'
+ },
+ {
+ domain: 'sub1.test1.example.org',
+ override: '%APP_VERSION%',
+ expected: SpecialPowers.Services.appinfo.version,
+ host: 'http://sub1.test1.example.org'
+ },
+ {
+ domain: 'sub2.test1.example.org',
+ override: '%BUILD_ID%',
+ expected: SpecialPowers.Services.appinfo.appBuildID,
+ host: 'http://sub2.test1.example.org'
+ },
+ {
+ domain: 'sub1.test2.example.org',
+ override: '%OS%',
+ expected: SpecialPowers.Services.appinfo.OS,
+ host: 'http://sub1.test2.example.org'
+ },
+ {
+ domain: 'sub2.test2.example.org',
+ override: UA_PARTIAL_OVERRIDE,
+ expected: UA_PARTIAL_EXPECTED,
+ host: 'http://sub2.test2.example.org'
+ },
+];
+
+function getServerURL() {
+ var url = location.pathname;
+ return location.origin + url.slice(0, url.lastIndexOf('/')) + '/user_agent_update.sjs?';
+}
+
+function getUpdateURL() {
+ var url = getServerURL();
+ var overrides = {};
+ overrides[location.hostname] = UA_OVERRIDE;
+ OVERRIDES.forEach(function (val) {
+ overrides[val.domain] = val.override;
+ });
+ url = url + encodeURIComponent(JSON.stringify(overrides)).replace(/%25/g, '%');
+ return url;
+}
+
+function testDownload(callback) {
+ var startTime = Date.now();
+ var url = getUpdateURL();
+ isnot(navigator.userAgent, UA_OVERRIDE, 'UA already overridden');
+ info('Waiting for UA update: ' + url);
+
+ chromeScript.sendAsyncMessage("notify-on-update");
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_UPDATES_ENABLED, true],
+ [PREF_UPDATES_URL, url],
+ [PREF_UPDATES_TIMEOUT, 10000],
+ [PREF_UPDATES_INTERVAL, 1] // 1 second interval
+ ]
+ });
+
+ function waitForUpdate() {
+ info("Update Happened");
+ testUAIFrameNoNav(location.origin, UA_OVERRIDE, true, 'Header UA not overridden', function() {
+ var updateTime = parseInt(getUA('http://example.org'));
+ todo(startTime <= updateTime, 'Update was before start time');
+ todo(updateTime <= Date.now(), 'Update was after present time');
+
+ let overs = OVERRIDES;
+ (function nextOverride() {
+ val = overs.shift();
+ if (val.expected) {
+ testUAIFrameNoNav(val.host, val.expected, true, 'Incorrect URL parameter: ' + val.override, function() {
+ overs.length ? nextOverride() : callback();
+ });
+ } else {
+ nextOverride();
+ }
+ })();
+ });
+ }
+
+ chromeScript.addMessageListener("useragent-update-complete", waitForUpdate);
+}
+
+function testBadUpdate(callback) {
+ var url = getServerURL() + 'invalid-json';
+ var prevOverride = navigator.userAgent;
+ SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_UPDATES_URL, url],
+ [PREF_UPDATES_INTERVAL, 1] // 1 second interval
+ ]
+ }, function () { setTimeout(function () {
+ var ifr = document.createElement('IFRAME');
+ ifr.src = "about:blank";
+
+ ifr.addEventListener('load', function() {
+ // We want to make sure a bad update doesn't cancel out previous
+ // overrides. We do this by waiting for 5 seconds (assuming the update
+ // occurs within 5 seconds), and check that the previous override hasn't
+ // changed.
+ is(navigator.userAgent, prevOverride,
+ 'Invalid update deleted previous override');
+ callback();
+ }, false);
+ document.getElementById('content').appendChild(ifr);
+ }, 5000); });
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("Test sets timeouts to wait for updates to happen.");
+
+SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_APP_UPDATE_TIMERMINIMUMDELAY, 0]
+ ]
+}, function () {
+ chromeScript.sendSyncMessage("UAO-uninit");
+
+ // Sets the OVERRIDES var in the chrome script.
+ // We do this to avoid code duplication.
+ chromeScript.sendSyncMessage("set-overrides", OVERRIDES);
+
+ // testProfileLoad, testDownload, and testProfileSave must run in this order
+ // because testDownload depends on testProfileLoad to call UAO.init()
+ // and testProfileSave depends on testDownload to save overrides to the profile
+ chromeScript.sendAsyncMessage("testProfileLoad", location.hostname);
+});
+
+
+const chromeScript = SpecialPowers.loadChromeScript(_ => {
+ // Enter update timer manager test mode
+ Components.classes["@mozilla.org/updates/timer-manager;1"].getService(
+ Components.interfaces.nsIObserver).observe(null, "utm-test-init", "");
+
+ Components.utils.import("resource://gre/modules/UserAgentOverrides.jsm");
+
+ var _notifyOnUpdate = false;
+
+ var UAO = UserAgentOverrides;
+ UAO.uninit();
+
+ Components.utils.import("resource://gre/modules/FileUtils.jsm");
+ var FU = FileUtils;
+
+ const { TextDecoder, TextEncoder, OS } = Components.utils.import("resource://gre/modules/osfile.jsm");
+ var OSF = OS.File;
+
+ const KEY_PREFDIR = "PrefD";
+ const KEY_APPDIR = "XCurProcD";
+ const FILE_UPDATES = "ua-update.json";
+
+ const UA_OVERRIDE = "DummyUserAgent";
+ const UA_ALT_OVERRIDE = "AltUserAgent";
+
+ const PREF_UPDATES = "general.useragent.updates.";
+ const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
+ const PREF_UPDATES_LASTUPDATED = PREF_UPDATES + "lastupdated";
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Services.prefs.addObserver(PREF_UPDATES_LASTUPDATED, () => {
+ if (_notifyOnUpdate) {
+ _notifyOnUpdate = false; // Only notify once, for the first update.
+ sendAsyncMessage("useragent-update-complete");
+ }
+ } , false);
+
+ var OVERRIDES = null;
+
+ function is(value, expected, message) {
+ sendAsyncMessage("is-message", {value, expected, message});
+ }
+
+ function info(message) {
+ sendAsyncMessage("info-message", message);
+ }
+
+ function testProfileSave(hostname) {
+ info('Waiting for saving to profile');
+ var file = FU.getFile(KEY_PREFDIR, [FILE_UPDATES]).path;
+ (function waitForSave() {
+ OSF.exists(file).then(
+ (exists) => {
+ if (!exists) {
+ setTimeout(waitForSave, 100);
+ return;
+ }
+ return OSF.read(file).then(
+ (bytes) => {
+ info('Saved new overrides');
+ var decoder = new TextDecoder();
+ var overrides = JSON.parse(decoder.decode(bytes));
+ is(overrides[hostname], UA_OVERRIDE, 'Incorrect saved override');
+ OVERRIDES.forEach(function (val) {
+ val.expected && is(overrides[val.domain], val.expected,
+ 'Incorrect saved override: ' + val.override);
+ });
+ sendAsyncMessage("testProfileSaveDone");
+ }
+ );
+ }
+ ).then(null,
+ (reason) => {
+ throw reason
+ }
+ );
+ })();
+ }
+
+ function testProfileLoad(hostname) {
+ var file = FU.getFile(KEY_APPDIR, [FILE_UPDATES]).path;
+ var encoder = new TextEncoder();
+ var overrides = {};
+ overrides[hostname] = UA_ALT_OVERRIDE;
+ var bytes = encoder.encode(JSON.stringify(overrides));
+
+ var badfile = FU.getFile(KEY_PREFDIR, [FILE_UPDATES]).path;
+ var badbytes = encoder.encode("null");
+
+ OSF.writeAtomic(file, bytes, {tmpPath: file + ".tmp"}).then(
+ () => OSF.writeAtomic(badfile, badbytes, {tmpPath: badfile + ".tmp"})
+ ).then(
+ () => {
+ sendAsyncMessage("testProfileLoadDone");
+ },
+ (reason) => {
+ throw reason
+ }
+ );
+ }
+
+
+ addMessageListener("testProfileSave", testProfileSave);
+ addMessageListener("testProfileLoad", testProfileLoad);
+ addMessageListener("set-overrides", function(overrides) { OVERRIDES = overrides});
+ addMessageListener("UAO-init", function() { UAO.init(); });
+ addMessageListener("UAO-uninit", function() { UAO.uninit(); });
+ addMessageListener("notify-on-update", () => { _notifyOnUpdate = true });
+});
+
+chromeScript.addMessageListener("testProfileSaveDone", SimpleTest.finish);
+chromeScript.addMessageListener("testProfileLoadDone", function() {
+ SpecialPowers.pushPrefEnv({
+ set: [[PREF_UPDATES_ENABLED, true]]
+ }, function () {
+ // initialize UserAgentOverrides.jsm and
+ // UserAgentUpdates.jsm and load saved file
+ chromeScript.sendSyncMessage("UAO-init");
+ (function waitForLoad() {
+ var ifr = document.createElement('IFRAME');
+ ifr.src = "about:blank";
+
+ ifr.addEventListener('load', function() {
+ var nav = ifr.contentWindow.navigator;
+ if (nav.userAgent !== UA_ALT_OVERRIDE) {
+ setTimeout(waitForLoad, 100);
+ return;
+ }
+ testUAIFrameNoNav(location.origin, UA_ALT_OVERRIDE, true, 'Did not apply saved override', function () {
+ testDownload(function() {
+ testBadUpdate(function() {
+ chromeScript.sendAsyncMessage("testProfileSave", location.hostname);
+ })
+ })
+ });
+ }, true);
+
+ document.getElementById('content').appendChild(ifr);
+ })();
+ });
+});
+
+chromeScript.addMessageListener("is-message", function(params) {
+ let {value, expected, message} = params;
+ is(value, expected, message);
+});
+chromeScript.addMessageListener("info-message", function(message) {
+ info(message);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/netwerk/test/mochitests/test_user_agent_updates_reset.html b/netwerk/test/mochitests/test_user_agent_updates_reset.html
new file mode 100644
index 0000000000..5b51fc40d5
--- /dev/null
+++ b/netwerk/test/mochitests/test_user_agent_updates_reset.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=942470
+-->
+<head>
+ <title>Test for Bug 942470</title>
+ <script type="text/javascript" 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=942470">Mozilla Bug 942470</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 942470 **/
+
+function getUA(host) {
+ var url = location.pathname;
+ url = host + url.slice(0, url.lastIndexOf('/')) + '/user_agent.sjs';
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false); // sync request
+ xhr.send();
+ is(xhr.status, 200, 'request failed');
+ is(typeof xhr.response, 'string', 'invalid response');
+ return xhr.response;
+}
+
+const UA_OVERRIDE = "DummyUserAgent";
+
+info("User agent is " + navigator.userAgent);
+isnot(navigator.userAgent, UA_OVERRIDE,
+ "navigator.userAgent is not reverted");
+isnot(getUA(location.origin), UA_OVERRIDE,
+ "User-Agent is not reverted");
+
+</script>
+</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..9a1a35d682
--- /dev/null
+++ b/netwerk/test/mochitests/test_viewsource_unlinkable.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for view-source linkability</title>
+ <script type="text/javascript" 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..236ab210b1
--- /dev/null
+++ b/netwerk/test/mochitests/test_xhr_method_case.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+XHR uppercases certain method names, but not others
+-->
+<head>
+ <title>Test for XHR Method casing</title>
+ <script type="text/javascript" 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/user_agent.sjs b/netwerk/test/mochitests/user_agent.sjs
new file mode 100644
index 0000000000..dea299a204
--- /dev/null
+++ b/netwerk/test/mochitests/user_agent.sjs
@@ -0,0 +1,21 @@
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ // used by test_user_agent tests
+ response.write(
+ "<html><body>\
+ <script type='text/javascript'>\
+ var msg = {\
+ header: '" + request.getHeader('User-Agent') + "',\
+ nav: navigator.userAgent\
+ };\
+ self.parent.postMessage(msg, '*');\
+ </script>\
+ </body></html>"
+ );
+}
diff --git a/netwerk/test/mochitests/user_agent_update.sjs b/netwerk/test/mochitests/user_agent_update.sjs
new file mode 100644
index 0000000000..649ceb9036
--- /dev/null
+++ b/netwerk/test/mochitests/user_agent_update.sjs
@@ -0,0 +1,10 @@
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/json", false);
+
+ // used by test_user_agent_updates test
+ response.write(decodeURIComponent(request.queryString));
+}
diff --git a/netwerk/test/mochitests/web_packaged_app.sjs b/netwerk/test/mochitests/web_packaged_app.sjs
new file mode 100644
index 0000000000..d5587d8d76
--- /dev/null
+++ b/netwerk/test/mochitests/web_packaged_app.sjs
@@ -0,0 +1,35 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+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..3df865c3a8
--- /dev/null
+++ b/netwerk/test/moz.build
@@ -0,0 +1,70 @@
+# -*- 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']
+
+BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
+MOCHITEST_MANIFESTS += ['mochitests/mochitest.ini']
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'unit/xpcshell.ini',
+ 'unit_ipc/xpcshell.ini',
+]
+
+GeckoSimplePrograms([
+ 'PropertiesTest',
+ 'ReadNTLM',
+ 'TestBlockingSocket',
+ 'TestDNS',
+ 'TestIncrementalDownload',
+ 'TestOpen',
+ 'TestProtocols',
+ 'TestServ',
+ 'TestStreamLoader',
+ 'TestUpload',
+ 'TestURLParser',
+ 'urltest',
+])
+
+# XXX Make this work in libxul builds.
+#SIMPLE_PROGRAMS += [
+# TestIDN',
+# TestIOThreads',
+# TestSocketTransport',
+# TestStreamPump',
+# TestStreamTransport',
+# TestUDPSocketProvider',
+#]
+
+CppUnitTests([
+ 'TestBind',
+ 'TestCookie',
+ 'TestUDPSocket',
+])
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ CppUnitTests([
+ 'TestNamedPipeService'
+ ])
+
+RESOURCE_FILES += [
+ 'urlparse.dat',
+ 'urlparse_unx.dat',
+]
+
+USE_LIBS += ['static:js']
+
+if CONFIG['ENABLE_INTL_API'] and CONFIG['MOZ_ICU_DATA_ARCHIVE']:
+ # The ICU libraries linked into libmozjs will not include the ICU data,
+ # so link it directly.
+ USE_LIBS += ['icudata']
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-shadow']
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..05ea512a3d
--- /dev/null
+++ b/netwerk/test/reftest/bug565432-1-ref.html
@@ -0,0 +1,12 @@
+<!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..69980a5346
--- /dev/null
+++ b/netwerk/test/reftest/bug565432-1.html
@@ -0,0 +1,18 @@
+<!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-stylo.list b/netwerk/test/reftest/reftest-stylo.list
new file mode 100644
index 0000000000..d96477f0d5
--- /dev/null
+++ b/netwerk/test/reftest/reftest-stylo.list
@@ -0,0 +1,3 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+== bug565432-1.html bug565432-1.html
+== 658949-1.html 658949-1.html
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/sites.txt b/netwerk/test/sites.txt
new file mode 100644
index 0000000000..e3089595aa
--- /dev/null
+++ b/netwerk/test/sites.txt
@@ -0,0 +1,257 @@
+http://www.yahoo.com/
+http://www.netscape.com/
+http://www.microsoft.com/
+http://www.excite.com/
+http://www.mckinley.com/
+http://www.city.net/
+http://www.webcrawler.com/
+http://www.mirabilis.com/
+http://www.infoseek.com/
+http://www.pathfinder.com/
+http://www.warnerbros.com/
+http://www.cnn.com/
+http://www.altavista.digital.com/
+http://www.altavista.com/
+http://www.usatoday.com/
+http://www.disney.com/
+http://www.starwave.com/
+http://www.hotwired.com/
+http://www.hotbot.com/
+http://www.lycos.com/
+http://www.pointcom.com/
+http://www.cnet.com/
+http://www.search.com/
+http://www.news.com/
+http://www.download.com/
+http://www.geocities.com/
+http://www.aol.com/
+http://members.aol.com/
+http://www.imdb.com/
+http://uk.imdb.com/
+http://www.macromedia.com/
+http://www.infobeat.com/
+http://www.fxweb.com/
+http://www.whowhere.com/
+http://www.real.com/
+http://www.sportsline.com/
+http://www.dejanews.com/
+http://www.the-park.com/
+http://www.cmpnet.com/
+http://www.go2net.com/
+http://www.metacrawler.com/
+http://www.playsite.com/
+http://www.stocksite.com/
+http://www.sony.com/
+http://www.music.sony.com/
+http://www.station.sony.com/
+http://www.scea.sony.com/
+http://www.infospace.com/
+http://www.zdnet.com/
+http://www.hotfiles.com/
+http://www.chathouse.com/
+http://www.looksmart.com/
+http://www.iamginegames.com/
+http://www.macaddict.com/
+http://www.rsac.org/
+http://www.apple.com/
+http://www.beseen.com/
+http://www.dogpile.com/
+http://www.xoom.com/
+http://www.tucows.com/
+http://www.freethemes.com/
+http://www.winfiles.com/
+http://www.vservers.com/
+http://www.mtv.com/
+http://www.the-xfiles.com/
+http://www.datek.com/
+http://www.cyberthrill.com/
+http://www.surplusdirect.com/
+http://www.tomshardware.com/
+http://www.bigyellow.com/
+http://www.100hot.com/
+http://www.messagemates.com/
+http://www.onelist.com/
+http://www.bluemountain.com/
+http://www.ea.com/
+http://www.bullfrog.co.uk/
+http://www.travelocity.com/
+http://www.ibm.com/
+http://www.bigcharts.com/
+http://www.davesclassics.com/
+http://www.goto.com/
+http://www.weather.com/
+http://www.gamespot.com/
+http://www.bloomberg.com/
+http://www.winzip.com/
+http://www.filez.com/
+http://www.westwood.com/
+http://www.internet.com/
+http://www.cardmaster.com/
+http://www.creaf.com/
+http://netaddress.usa.net/
+http://www.occ.com/
+http://www.as.org/
+http://www.amazon.com/
+http://www.drudgereport.com/
+http://www.hardradio.com/
+http://www.intel.com/
+http://www.mp3.com/
+http://www.ebay.com/
+http://www.msn.com/
+http://www.fifa.com/
+http://www.attitude.com/
+http://www.happypuppy.com/
+http://www.gamesdomain.com/
+http://www.onsale.com/
+http://www.tm.com/
+http://www.xlnc1.com/
+http://www.greatsports.com/
+http://www.discovery.com/
+http://www.nai.com/
+http://www.nasa.gov/
+http://www.ogr.com/
+http://www.warzone.com/
+http://www.gamestats.com/
+http://www.winamp.com/
+http://java.sun.com/
+http://www.hp.com/
+http://www.cdnow.com/
+http://www.nytimes.com/
+http://www.majorleaguebaseball.com/
+http://www.washingtonpost.com/
+http://www.planetquake.com/
+http://www.wsj.com/
+http://www.slashdot.org/
+http://www.adobe.com/
+http://www.quicken.com/
+http://www.talkcity.com/
+http://www.developer.com/
+http://www.mapquest.com/
+http://www.yahoo.com/
+http://www.pathfinder.com/
+http://www.msn.com/
+http://www.fifa.com/
+http://www.attitude.com/
+http://www.happypuppy.com/
+http://www.gamesdomain.com/
+http://www.onsale.com/
+http://www.tm.com/
+http://www.xlnc1.com/
+http://www.greatsports.com/
+http://www.discovery.com/
+http://www.warnerbros.com/
+http://www.nai.com/
+http://www.nasa.gov/
+http://www.ogr.com/
+http://www.warzone.com/
+http://www.gamestats.com/
+http://www.winamp.com/
+http://java.sun.com/
+http://www.hp.com/
+http://www.cdnow.com/
+http://www.nytimes.com/
+http://www.majorleaguebaseball.com/
+http://www.planetquake.com/
+http://www.wsj.com/
+http://www.slashdot.org/
+http://www.adobe.com/
+http://www.quicken.com/
+http://www.talkcity.com/
+http://www.developer.com/
+http://www.mapquest.com/
+http://www.altavista.digital.com/
+http://www.altavista.com/
+http://www.usatoday.com/
+http://www.disney.com/
+http://www.starwave.com/
+http://www.hotwired.com/
+http://www.hotbot.com/
+http://www.netscape.com/
+http://www.lycos.com/
+http://www.pointcom.com/
+http://www.cnet.com/
+http://www.search.com/
+http://www.news.com/
+http://www.download.com/
+http://www.geocities.com/
+http://www.imdb.com/
+http://www.microsoft.com/
+http://uk.imdb.com/
+http://www.macromedia.com/
+http://www.infobeat.com/
+http://www.fxweb.com/
+http://www.whowhere.com/
+http://www.real.com/
+http://www.sportsline.com/
+http://www.dejanews.com/
+http://www.the-park.com/
+http://www.cmpnet.com/
+http://www.excite.com/
+http://www.go2net.com/
+http://www.metacrawler.com/
+http://www.playsite.com/
+http://www.stocksite.com/
+http://www.infospace.com/
+http://www.zdnet.com/
+http://www.mckinley.com/
+http://www.hotfiles.com/
+http://www.chathouse.com/
+http://www.looksmart.com/
+http://www.iamginegames.com/
+http://www.macaddict.com/
+http://www.rsac.org/
+http://www.apple.com/
+http://www.beseen.com/
+http://www.dogpile.com/
+http://www.xoom.com/
+http://www.city.net/
+http://www.tucows.com/
+http://www.freethemes.com/
+http://www.winfiles.com/
+http://www.vservers.com/
+http://www.mtv.com/
+http://www.the-xfiles.com/
+http://www.datek.com/
+http://www.cyberthrill.com/
+http://www.surplusdirect.com/
+http://www.tomshardware.com/
+http://www.webcrawler.com/
+http://www.bigyellow.com/
+http://www.100hot.com/
+http://www.messagemates.com/
+http://www.onelist.com/
+http://www.bluemountain.com/
+http://www.ea.com/
+http://www.bullfrog.co.uk/
+http://www.travelocity.com/
+http://www.ibm.com/
+http://www.bigcharts.com/
+http://www.mirabilis.com/
+http://www.davesclassics.com/
+http://www.goto.com/
+http://www.weather.com/
+http://www.gamespot.com/
+http://www.bloomberg.com/
+http://www.winzip.com/
+http://www.filez.com/
+http://www.westwood.com/
+http://www.internet.com/
+http://www.cardmaster.com/
+http://www.infoseek.com/
+http://www.creaf.com/
+http://netaddress.usa.net/
+http://www.occ.com/
+http://www.as.org/
+http://www.amazon.com/
+http://www.drudgereport.com/
+http://www.hardradio.com/
+http://www.intel.com/
+http://www.mp3.com/
+http://www.ebay.com/
+http://www.aol.com/
+http://www.cnn.com/
+http://www.music.sony.com/
+http://www.scea.sony.com/
+http://www.sony.com/
+http://www.station.sony.com/
+http://www.washingtonpost.com/
diff --git a/netwerk/test/unit/CA.cert.der b/netwerk/test/unit/CA.cert.der
new file mode 100644
index 0000000000..67157cabd0
--- /dev/null
+++ b/netwerk/test/unit/CA.cert.der
Binary files differ
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..610e8a1cf5
--- /dev/null
+++ b/netwerk/test/unit/client_cert_chooser.js
@@ -0,0 +1,26 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+var { utils: Cu, interfaces: Ci } = Components;
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+var Prompter = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
+ alert: function() {} // Do nothing when asked to show an alert
+};
+
+function WindowWatcherService() {}
+WindowWatcherService.prototype = {
+ classID: Components.ID("{01ae923c-81bb-45db-b860-d423b0fc4fe1}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]),
+
+ getNewPrompter: function() {
+ return Prompter;
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.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/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..da6a637adb
--- /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('il', null);
+checkPublicSuffix('c.il', null);
+checkPublicSuffix('b.c.il', 'b.c.il');
+checkPublicSuffix('a.b.c.il', 'b.c.il');
+// 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..6c8cf0d4a8
--- /dev/null
+++ b/netwerk/test/unit/head_cache.js
@@ -0,0 +1,147 @@
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+var _CSvc;
+function get_cache_service() {
+ if (_CSvc)
+ return _CSvc;
+
+ return _CSvc = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.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(LoadContextInfo.default);
+ storage.asyncEvictStorage(null);
+ }
+
+ if (clearDisk) {
+ storage = svc.diskCacheStorage(LoadContextInfo.default, false);
+ storage.asyncEvictStorage(null);
+ }
+
+ if (clearAppCache) {
+ storage = svc.appCacheStorage(LoadContextInfo.default, null);
+ storage.asyncEvictStorage(null);
+ }
+}
+
+function createURI(urispec)
+{
+ var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ return ioServ.newURI(urispec, null, null);
+}
+
+function getCacheStorage(where, lci, appcache)
+{
+ if (!lci) lci = 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: function (iid) {
+ if (iid.equals(Components.interfaces.nsICacheEntryOpenCallback) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onCacheEntryCheck: function(entry, appCache) {
+ if (typeof callback === "object")
+ return callback.onCacheEntryCheck(entry, appCache);
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable: function (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: function () {
+ var storage = getCacheStorage(where, lci, this._appCache);
+ storage.asyncOpenURI(key, "", flags, this);
+ }
+ };
+
+ (new CacheListener()).run();
+}
+
+function syncWithCacheIOThread(callback, force)
+{
+ if (!newCacheBackEndUsed() || force) {
+ asyncOpenCacheEntry(
+ "http://nonexistententry/", "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+ function(status, entry) {
+ do_check_eq(status, Components.results.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: function (entryCount, consumption) {
+ do_execute_soon(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);
+ do_check_eq(status, Cr.NS_OK);
+ do_check_true(!!entry);
+ } else {
+ dump("TEST-INFO | checking cache key " + key + " doesn't exist @ " + where);
+ do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ do_check_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..decf04f90b
--- /dev/null
+++ b/netwerk/test/unit/head_cache2.js
@@ -0,0 +1,429 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+function newCacheBackEndUsed()
+{
+ var cache1srv = Components.classes["@mozilla.org/network/cache-service;1"]
+ .getService(Components.interfaces.nsICacheService);
+ var cache2srv = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+
+ return cache1srv.cacheIOTarget != cache2srv.ioTarget;
+}
+
+var callbacks = new Array();
+
+// 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, -1, -1, 0, 0, true);
+ var data = "";
+ pump.asyncRead({
+ onStartRequest: function (aRequest, aContext) { },
+ onDataAvailable: function (aRequest, aContext, 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: function (aRequest, aContext, aStatusCode)
+ {
+ LOG_C2("done reading data: " + aStatusCode);
+ do_check_eq(aStatusCode, Cr.NS_OK);
+ goon(data);
+ },
+ }, null);
+ }
+ else {
+ // blocking stream
+ var data = read_stream(inputStream, inputStream.available());
+ goon(data);
+ }
+}
+
+OpenCallback.prototype =
+{
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICacheEntryOpenCallback)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onCacheEntryCheck: function(entry, appCache)
+ {
+ LOG_C2(this, "onCacheEntryCheck");
+ do_check_true(!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;
+ }
+
+ do_check_eq(entry.getMetaDataElement("meto"), this.workingMetadata);
+
+ // check for sane flag combination
+ do_check_neq(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");
+ if (newCacheBackEndUsed()) {
+ // 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: function(entry, isnew, appCache, status)
+ {
+ if ((this.behavior & MAYBE_NEW) && isnew) {
+ this.behavior |= NEW;
+ }
+
+ LOG_C2(this, "onCacheEntryAvailable, " + this.behavior);
+ do_check_true(!this.onAvailPassed);
+ this.onAvailPassed = true;
+
+ do_check_eq(isnew, !!(this.behavior & NEW));
+
+ if (this.behavior & (NOTFOUND|NOTWANTED)) {
+ do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ do_check_false(!!entry);
+ if (this.behavior & THROWAVAIL)
+ this.throwAndNotify(entry);
+ this.goon(entry);
+ }
+ else if (this.behavior & (NEW|RECREATE)) {
+ do_check_true(!!entry);
+
+ if (this.behavior & RECREATE) {
+ entry = entry.recreate();
+ do_check_true(!!entry);
+ }
+
+ if (this.behavior & THROWAVAIL)
+ this.throwAndNotify(entry);
+
+ if (!(this.behavior & WAITFORWRITE))
+ this.goon(entry);
+
+ if (!(this.behavior & PARTIAL)) {
+ try {
+ entry.getMetaDataElement("meto");
+ do_check_true(false);
+ }
+ catch (ex) {}
+ }
+
+ if (this.behavior & DONTFILL) {
+ do_check_false(this.behavior & WAITFORWRITE);
+ return;
+ }
+
+ var self = this;
+ do_execute_soon(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;
+ }
+ do_execute_soon(function() { // emulate more network latency
+ if (self.behavior & DOOMED) {
+ LOG_C2(self, "checking doom state");
+ try {
+ var os = entry.openOutputStream(0);
+ // Unfortunately, in the undetermined state we cannot even check whether the entry
+ // is actually doomed or not.
+ os.close();
+ do_check_true(!!(self.behavior & MAYBE_NEW));
+ } catch (ex) {
+ do_check_true(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);
+ LOG_C2(self, "writing data");
+ var wrt = os.write(self.workingData, self.workingData.length);
+ do_check_eq(wrt, self.workingData.length);
+ os.close();
+ if (self.behavior & WAITFORWRITE)
+ self.goon(entry);
+
+ entry.close();
+ })
+ })
+ }
+ else { // NORMAL
+ do_check_true(!!entry);
+ do_check_eq(entry.getMetaDataElement("meto"), this.workingMetadata);
+ if (this.behavior & THROWAVAIL)
+ this.throwAndNotify(entry);
+ if (this.behavior & NOTIFYBEFOREREAD)
+ this.goon(entry, true);
+
+ var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ var self = this;
+ pumpReadStream(entry.openInputStream(0), function(data) {
+ do_check_eq(data, self.workingData);
+ self.onDataCheckPassed = true;
+ LOG_C2(self, "entry read done");
+ self.goon(entry);
+ entry.close();
+ });
+ }
+ },
+ selfCheck: function()
+ {
+ LOG_C2(this, "selfCheck");
+
+ do_check_true(this.onCheckPassed || (this.behavior & MAYBE_NEW));
+ do_check_true(this.onAvailPassed);
+ do_check_true(this.onDataCheckPassed || (this.behavior & MAYBE_NEW));
+ },
+ throwAndNotify: function(entry)
+ {
+ LOG_C2(this, "Throwing");
+ var self = this;
+ do_execute_soon(function() {
+ LOG_C2(self, "Notifying");
+ self.goon(entry);
+ });
+ throw 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: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICacheStorageVisitor)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onCacheStorageInfo: function(num, consumption)
+ {
+ LOG_C2(this, "onCacheStorageInfo: num=" + num + ", size=" + consumption);
+ do_check_eq(this.num, num);
+ if (newCacheBackEndUsed()) {
+ // Consumption is as expected only in the new backend
+ do_check_eq(this.consumption, consumption);
+ }
+ if (!this.entries)
+ this.notify();
+ },
+ onCacheEntryInfo: function(aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime)
+ {
+ var key = (aIdEnhance ? (aIdEnhance + ":") : "") + aURI.asciiSpec;
+ LOG_C2(this, "onCacheEntryInfo: key=" + key);
+
+ do_check_true(!!this.entries);
+
+ var index = this.entries.indexOf(key);
+ do_check_true(index > -1);
+
+ this.entries.splice(index, 1);
+ },
+ onCacheEntryVisitCompleted: function()
+ {
+ LOG_C2(this, "onCacheEntryVisitCompleted");
+ if (this.entries)
+ do_check_eq(this.entries.length, 0);
+ this.notify();
+ },
+ notify: function()
+ {
+ do_check_true(!!this.goon);
+ var goon = this.goon;
+ this.goon = null;
+ do_execute_soon(goon);
+ },
+ selfCheck: function()
+ {
+ do_check_true(!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: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICacheEntryDoomCallback)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onCacheEntryDoomed: function(result)
+ {
+ do_check_eq(this.expectedSuccess, result == Cr.NS_OK);
+ this.goon();
+ },
+ selfCheck: function() {}
+}
+
+function EvictionCallback(success, goon)
+{
+ this.expectedSuccess = success;
+ this.goon = goon;
+ callbacks.push(this);
+ this.order = callbacks.length;
+}
+
+MultipleCallbacks.prototype =
+{
+ fired: function()
+ {
+ if (--this.pending == 0)
+ {
+ var self = this;
+ if (this.delayed)
+ do_execute_soon(function() { self.goon(); });
+ else
+ this.goon();
+ }
+ },
+ add: function()
+ {
+ ++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: function() { continue_func(); },
+ 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..5d71716683
--- /dev/null
+++ b/netwerk/test/unit/head_channels.js
@@ -0,0 +1,218 @@
+/**
+ * Read count bytes from stream and return as a String object
+ */
+function read_stream(stream, count) {
+ /* assume stream has non-ASCII data */
+ var wrapper =
+ Components.classes["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Components.interfaces.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;
+}
+ChannelListener.prototype = {
+ _closure: null,
+ _closurectx: null,
+ _buffer: "",
+ _got_onstartrequest: false,
+ _got_onstoprequest: false,
+ _contentLen: -1,
+ _lastEvent: 0,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ try {
+ if (this._got_onstartrequest)
+ do_throw("Got second onStartRequest event!");
+ this._got_onstartrequest = true;
+ this._lastEvent = Date.now();
+
+ request.QueryInterface(Components.interfaces.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: function(request, context, 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: function(request, context, 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)
+ do_check_eq(this._buffer.length, this._contentLen)
+ } catch (ex) {
+ do_throw("Error in onStopRequest: " + ex);
+ }
+ try {
+ this._closure(request, this._buffer, this._closurectx);
+ } catch (ex) {
+ do_throw("Error in closure function: " + ex);
+ }
+ }
+};
+
+var ES_ABORT_REDIRECT = 0x01;
+
+function ChannelEventSink(flags)
+{
+ this._flags = flags;
+}
+
+ChannelEventSink.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIInterfaceRequestor) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function(iid) {
+ if (iid.equals(Ci.nsIChannelEventSink))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
+ if (this._flags & ES_ABORT_REDIRECT)
+ throw Cr.NS_BINDING_ABORTED;
+
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ }
+};
+
+/**
+ * A helper class to construct origin attributes.
+ */
+function OriginAttributes(appId, inIsolatedMozBrowser, privateId) {
+ this.appId = appId;
+ this.inIsolatedMozBrowser = inIsolatedMozBrowser;
+ this.privateBrowsingId = privateId;
+}
+OriginAttributes.prototype = {
+ appId: 0,
+ inIsolatedMozBrowser: false,
+ privateBrowsingId: 0
+};
diff --git a/netwerk/test/unit/socks_client_subprocess.js b/netwerk/test/unit/socks_client_subprocess.js
new file mode 100644
index 0000000000..144bc8757e
--- /dev/null
+++ b/netwerk/test/unit/socks_client_subprocess.js
@@ -0,0 +1,42 @@
+var CC = Components.Constructor;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+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 launchConnection(socks_vers, socks_port, dest_host, dest_port, dns)
+{
+ var pi_flags = 0;
+ if (dns == 'remote')
+ pi_flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+
+ var pps = new ProtocolProxyService();
+ var pi = pps.newProxyInfo(socks_vers, 'localhost', socks_port,
+ pi_flags, -1, null);
+ var trans = sts.createTransport(null, 0, dest_host, dest_port, pi);
+ var input = trans.openInputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
+ var output = trans.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
+ var bin = new BinaryInputStream(input);
+ var data = bin.readBytes(5);
+ if (data == 'PING!') {
+ print('client: got ping, sending pong.');
+ output.write('PONG!', 5);
+ } else {
+ print('client: wrong data from server:', data);
+ output.write('Error: wrong data received.', 27);
+ }
+ output.close();
+}
+
+for (var arg of arguments) {
+ print('client: running test', arg);
+ test = arg.split('|');
+ launchConnection(test[0], parseInt(test[1]), test[2],
+ parseInt(test[3]), test[4]);
+}
diff --git a/netwerk/test/unit/test_1073747.js b/netwerk/test/unit/test_1073747.js
new file mode 100644
index 0000000000..c930514e7e
--- /dev/null
+++ b/netwerk/test/unit/test_1073747.js
@@ -0,0 +1,30 @@
+// Test based on submitted one from Peter B Shalimoff
+
+var test = function(s, funcName){
+ function Arg(){};
+ Arg.prototype.toString = function(){
+ do_print("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 = Components.classes["@mozilla.org/network/url-parser;1?auth=maybe"].getService(Components.interfaces.nsIURLParser);
+ try {
+ up[funcName].apply(up, args);
+ return args;
+ } catch (x) {
+ do_check_true(true); // make sure it throws an exception instead of crashing
+ return x;
+ }
+ // should always have an exception to catch
+ do_check_true(false);
+};
+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..033c337b7f
--- /dev/null
+++ b/netwerk/test/unit/test_304_responses.js
@@ -0,0 +1,95 @@
+"use strict";
+// https://bugzilla.mozilla.org/show_bug.cgi?id=761228
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function clearCache() {
+ var service = Components.classes["@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(Components.interfaces.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 304);
+ do_check_eq(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.asyncOpen2(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",
+ "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(Components.interfaces.nsIHttpChannel);
+ chan.setRequestHeader("If-None-Match", '"foo"', false);
+
+ chan.asyncOpen2(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..fbbcdf000d
--- /dev/null
+++ b/netwerk/test/unit/test_307_redirect.js
@@ -0,0 +1,91 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+ return;
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.writeFrom(metadata.bodyInputStream,
+ metadata.bodyInputStream.available());
+}
+
+function noRedirectStreamObserver(request, buffer)
+{
+ do_check_eq(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.asyncOpen2(new ChannelListener(noHeaderStreamObserver, null));
+}
+
+function noHeaderStreamObserver(request, buffer)
+{
+ do_check_eq(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.asyncOpen2(new ChannelListener(headerStreamObserver, null));
+}
+
+function headerStreamObserver(request, buffer)
+{
+ do_check_eq(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(Components.interfaces.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.asyncOpen2(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..7a9e070291
--- /dev/null
+++ b/netwerk/test/unit/test_421.js
@@ -0,0 +1,60 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(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) {
+ do_check_eq(channel.responseStatus, 200);
+ do_check_eq(channel.responseStatusText, "OK");
+ do_check_true(channel.requestSucceeded);
+
+ do_check_eq(channel.contentType, "text/plain");
+ do_check_eq(channel.contentLength, httpbody.length);
+ do_check_eq(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..2c46a061c2
--- /dev/null
+++ b/netwerk/test/unit/test_MIME_params.js
@@ -0,0 +1,560 @@
+/**
+ * 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
+ */
+
+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
+ ["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],
+
+ ["attachment; filename=foo extension=bla",
+ "attachment", "foo"],
+
+ ["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\\"],
+];
+
+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 = Components.classes["@mozilla.org/network/mime-hdrparam;1"]
+ .getService(Components.interfaces.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);
+
+ do_check_eq(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 {
+ do_check_eq(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);
+
+ do_check_eq(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 {
+ do_check_eq(e.result, expectedFn);
+ } catch(e) {}
+ }
+ continue;
+ }
+ }
+}
+
+function test_decode5987Param() {
+ var mhp = Components.classes["@mozilla.org/network/mime-hdrparam;1"]
+ .getService(Components.interfaces.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) {
+ do_check_eq(rfc5987paramtests[i][1], decoded);
+ do_check_eq(rfc5987paramtests[i][2], lang.value);
+ }
+ else {
+ do_check_eq(rfc5987paramtests[i][3], "instead got: " + decoded);
+ }
+ }
+ catch (e) {
+ do_check_eq(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..7f446ecc46
--- /dev/null
+++ b/netwerk/test/unit/test_NetUtil.js
@@ -0,0 +1,867 @@
+/* -*- 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.
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// 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!
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check the file contents.
+ do_check_eq(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: file, sink: ostream};
+ }
+
+ let bstream = Cc["@mozilla.org/network/buffered-output-stream;1"].
+ createInstance(Ci.nsIBufferedOutputStream);
+ bstream.init(ostream, 256);
+ return {file: file, sink: bstream};
+ }
+ Task.spawn(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");
+ do_print(text);
+ let TEST_DATA = "[" + make_sample(text) + "]";
+ let source = make_input(bufferedInput, TEST_DATA);
+ let {file, sink} = make_output(bufferedOutput);
+ let deferred = Promise.defer();
+ NetUtil.asyncCopy(source, sink, deferred.resolve);
+ let result = yield deferred.promise;
+
+ // Make sure the copy was successful!
+ if (!Components.isSuccessCode(result)) {
+ do_throw(new Components.Exception("asyncCopy error", result));
+ }
+
+ // Check the file contents.
+ do_check_eq(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) {
+ do_check_eq(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, null, null);
+ let NetUtilURI = NetUtil.newURI(TEST_URI);
+ do_check_true(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);
+ do_check_true(iosURI.equals(NetUtilURI));
+
+ run_next_test();
+}
+
+function test_ioService()
+{
+ do_check_true(NetUtil.ioService instanceof Ci.nsIIOService);
+ run_next_test();
+}
+
+function test_asyncFetch_no_channel()
+{
+ try {
+ NetUtil.asyncFetch(null, function() { });
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ run_next_test();
+}
+
+function test_asyncFetch_no_callback()
+{
+ try {
+ NetUtil.asyncFetch({ });
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(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.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(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);
+ do_check_eq(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.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(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);
+ do_check_eq(TEST_DATA, result);
+
+ server.stop(run_next_test);
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(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);
+ do_check_eq(TEST_DATA, result);
+
+ server.stop(run_next_test);
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.
+ do_check_eq(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.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(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);
+ do_check_eq(TEST_DATA, result);
+
+ run_next_test();
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.
+ do_check_true(Components.isSuccessCode(aResult));
+
+ // Check that we got the right data.
+ do_check_eq(aInputStream.available(), TEST_DATA.length);
+ do_check_eq(NetUtil.readInputStreamToString(aInputStream, TEST_DATA.length),
+ TEST_DATA);
+
+ run_next_test();
+ },
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.
+ do_check_true(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) {
+ do_check_eq(e.result, Cr.NS_BASE_STREAM_CLOSED);
+ }
+
+ run_next_test();
+ });
+}
+
+function test_newChannel_no_specifier()
+{
+ try {
+ NetUtil.newChannel();
+ do_throw("should throw!");
+ }
+ catch (e) {
+ do_check_eq(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 = NetUtil.ioService;
+ let iosChannel = ios.newChannel2(TEST_SPEC,
+ null,
+ null,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let NetUtilChannel = NetUtil.newChannel({
+ uri: TEST_SPEC,
+ loadUsingSystemPrincipal: true
+ });
+ do_check_true(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 = NetUtil.ioService.newChannelFromURI2(uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let NetUtilChannel = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+ do_check_true(iosChannel.URI.equals(NetUtilChannel.URI));
+
+ run_next_test();
+}
+
+function test_newChannel_with_options()
+{
+ let uri = "data:text/plain,";
+
+ let iosChannel = NetUtil.ioService.newChannelFromURI2(NetUtil.newURI(uri),
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+
+ function checkEqualToIOSChannel(channel) {
+ do_check_true(iosChannel.URI.equals(channel.URI));
+ }
+
+ checkEqualToIOSChannel(NetUtil.newChannel({
+ uri,
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ 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({});
+ }, /requires the 'uri' property/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({ uri });
+ }, /requires at least one of the 'loadingNode'/);
+
+ Assert.throws(() => {
+ NetUtil.newChannel({
+ uri,
+ loadingPrincipal: systemPrincipal,
+ });
+ }, /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_deprecated_newChannel_API_with_string() {
+ const TEST_SPEC = "http://mozilla.org";
+ let uri = NetUtil.newURI(TEST_SPEC);
+ let oneArgChannel = NetUtil.newChannel(TEST_SPEC);
+ let threeArgChannel = NetUtil.newChannel(TEST_SPEC, null, null);
+ do_check_true(uri.equals(oneArgChannel.URI));
+ do_check_true(uri.equals(threeArgChannel.URI));
+
+ run_next_test();
+}
+
+function test_deprecated_newChannel_API_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-deprecated-newchannel-api-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.
+ do_check_eq(TEST_DATA, getFileContents(file));
+
+ // create a channel using the file
+ let channel = NetUtil.newChannel(file);
+
+ // 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, 0, null);
+
+ let listener = Cc["@mozilla.org/network/simple-stream-listener;1"].
+ createInstance(Ci.nsISimpleStreamListener);
+ listener.init(pipe.outputStream, {
+ onStartRequest: function(aRequest, aContext) {},
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ pipe.outputStream.close();
+ do_check_true(Components.isSuccessCode(aContext));
+
+ // Check that we got the right data.
+ do_check_eq(pipe.inputStream.available(), TEST_DATA.length);
+ let is = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ is.init(pipe.inputStream);
+ let result = is.read(TEST_DATA.length);
+ do_check_eq(TEST_DATA, result);
+ run_next_test();
+ }
+ });
+ channel.asyncOpen2(listener);
+}
+
+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;
+
+ do_check_eq(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) {
+ do_check_eq(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) {
+ do_check_eq(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) {
+ do_check_eq(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) {
+ do_check_eq(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);
+ do_check_eq(NetUtil.readInputStreamToString(istream,
+ TEST_DATA_UTF8.length,
+ { charset: "UTF-8"}),
+ TEST_DATA);
+
+ istream.setData(TEST_DATA_SJIS, TEST_DATA_SJIS.length);
+ do_check_eq(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) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_INPUT);
+ }
+
+ istream.setData(TEST_DATA_UTF8, TEST_DATA_UTF8.length);
+ do_check_eq(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_ioService,
+ 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_deprecated_newChannel_API_with_string,
+ test_deprecated_newChannel_API_with_nsIFile,
+ 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(add_test);
+var index = 0;
+
+function run_test()
+{
+ run_next_test();
+}
+
diff --git a/netwerk/test/unit/test_URIs.js b/netwerk/test/unit/test_URIs.js
new file mode 100644
index 0000000000..b68c4f7874
--- /dev/null
+++ b/netwerk/test/unit/test_URIs.js
@@ -0,0 +1,608 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.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:",
+ path: "blank",
+ ref: "",
+ nsIURL: false, nsINestedURI: true, immutable: true },
+ { spec: "about:foobar",
+ scheme: "about",
+ prePath: "about:",
+ path: "foobar",
+ ref: "",
+ nsIURL: false, nsINestedURI: false, immutable: true },
+ { spec: "chrome://foobar/somedir/somefile.xml",
+ scheme: "chrome",
+ prePath: "chrome://foobar",
+ path: "/somedir/somefile.xml",
+ ref: "",
+ nsIURL: true, nsINestedURI: false, immutable: true },
+ { spec: "data:text/html;charset=utf-8,<html></html>",
+ scheme: "data",
+ prePath: "data:",
+ path: "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:",
+ path: "text/html;charset=utf-8,<html></html>",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "data:text/plain,hello world",
+ scheme: "data",
+ prePath: "data:",
+ path: "text/plain,hello%20world",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "file:///dir/afile",
+ scheme: "data",
+ prePath: "data:",
+ path: "text/plain,2",
+ ref: "",
+ relativeURI: "data:te\nxt/plain,2",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "file://",
+ scheme: "file",
+ prePath: "file://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "file:///",
+ scheme: "file",
+ prePath: "file://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "file:///myFile.html",
+ scheme: "file",
+ prePath: "file://",
+ path: "/myFile.html",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "file:///dir/afile",
+ scheme: "file",
+ prePath: "file://",
+ path: "/dir/data/text/plain,2",
+ ref: "",
+ relativeURI: "data/text/plain,2",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "file:///dir/dir2/",
+ scheme: "file",
+ prePath: "file://",
+ path: "/dir/dir2/data/text/plain,2",
+ ref: "",
+ relativeURI: "data/text/plain,2",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "ftp://",
+ scheme: "ftp",
+ prePath: "ftp://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "ftp:///",
+ scheme: "ftp",
+ prePath: "ftp://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "ftp://ftp.mozilla.org/pub/mozilla.org/README",
+ scheme: "ftp",
+ prePath: "ftp://ftp.mozilla.org",
+ path: "/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",
+ path: "/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: "",
+ path: "/pub/mozilla.org/README",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ //Bug 706249
+ { spec: "gopher://mozilla.org/",
+ scheme: "gopher",
+ prePath: "gopher:",
+ path: "//mozilla.org/",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "http://",
+ scheme: "http",
+ prePath: "http://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http:///",
+ scheme: "http",
+ prePath: "http://",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://www.example.com/",
+ scheme: "http",
+ prePath: "http://www.example.com",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://www.exa\nmple.com/",
+ scheme: "http",
+ prePath: "http://www.example.com",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://10.32.4.239/",
+ scheme: "http",
+ prePath: "http://10.32.4.239",
+ host: "10.32.4.239",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://[::192.9.5.5]/ipng",
+ scheme: "http",
+ prePath: "http://[::192.9.5.5]",
+ host: "::192.9.5.5",
+ path: "/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,
+ path: "/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",
+ path: "/pub/mozilla.org/README.html",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "jar:resource://!/",
+ scheme: "jar",
+ prePath: "jar:",
+ path: "resource:///!/",
+ ref: "",
+ nsIURL: true, nsINestedURI: true },
+ { spec: "jar:resource://gre/chrome.toolkit.jar!/",
+ scheme: "jar",
+ prePath: "jar:",
+ path: "resource://gre/chrome.toolkit.jar!/",
+ ref: "",
+ nsIURL: true, nsINestedURI: true },
+ { spec: "mailto:webmaster@mozilla.com",
+ scheme: "mailto",
+ prePath: "mailto:",
+ path: "webmaster@mozilla.com",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "javascript:new Date()",
+ scheme: "javascript",
+ prePath: "javascript:",
+ path: "new%20Date()",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "blob:123456",
+ scheme: "blob",
+ prePath: "blob:",
+ path: "123456",
+ ref: "",
+ nsIURL: false, nsINestedURI: false, immutable: true },
+ { spec: "place:sort=8&maxResults=10",
+ scheme: "place",
+ prePath: "place:",
+ path: "sort=8&maxResults=10",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "resource://gre/",
+ scheme: "resource",
+ prePath: "resource://gre",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "resource://gre/components/",
+ scheme: "resource",
+ prePath: "resource://gre",
+ path: "/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 do_check_true.
+function do_check_uri_eq(aURI1, aURI2, aCheckTrueFunc) {
+ if (!aCheckTrueFunc) {
+ aCheckTrueFunc = do_check_true;
+ }
+
+ 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 == do_check_true) {
+ 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 do_check_true.
+function do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc) {
+ if (!aCheckTrueFunc) {
+ aCheckTrueFunc = do_check_true;
+ }
+
+ 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 + "'");
+ do_check_eq(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) {
+ do_check_eq(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) {
+ do_check_eq(e.result, aTest.relativeFail);
+ return;
+ }
+ do_throw(e.result);
+ }
+ do_info("relURI.path = " + relURI.path + ", was " + URI.path);
+ URI = relURI;
+ do_info("URI.path now = " + URI.path);
+ }
+
+ // Sanity-check
+ do_info("testing " + aTest.spec + " equals a clone of itself");
+ do_check_uri_eq(URI, URI.clone());
+ do_check_uri_eqExceptRef(URI, URI.cloneIgnoringRef());
+ do_info("testing " + aTest.spec + " instanceof nsIURL");
+ do_check_eq(URI instanceof Ci.nsIURL, aTest.nsIURL);
+ do_info("testing " + aTest.spec + " instanceof nsINestedURI");
+ do_check_eq(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;
+ }
+ do_check_true(threw || !isEqualToNull);
+
+
+ // Check the various components
+ do_check_property(aTest, URI, "scheme");
+ do_check_property(aTest, URI, "prePath");
+ do_check_property(aTest, URI, "path");
+ 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);
+ do_check_eq(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 '#'");
+ do_check_eq(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.clone());
+
+ do_info("testing " + aTest.spec +
+ " doesn't equal self with '" + aSuffix + "' appended");
+
+ do_check_false(origURI.equals(testURI));
+
+ do_info("testing " + aTest.spec +
+ " is equalExceptRef to self with '" + aSuffix + "' appended");
+ do_check_uri_eqExceptRef(origURI, testURI);
+
+ do_check_eq(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.cloneIgnoringRef();
+ do_check_uri_eq(cloneNoRef, origURI);
+ do_check_false(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.cloneWithNewRef("");
+ do_check_uri_eq(cloneNewRef, origURI);
+ do_check_uri_eq(cloneNewRef, cloneNoRef);
+ do_check_false(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.cloneWithNewRef(aSuffix);
+ do_check_uri_eq(cloneNewRef, testURI);
+ do_check_true(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, "path",
+ 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 '#'");
+ do_check_eq(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.ref = aSuffix;
+ 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.ref = suffixLackingHash;
+ 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.ref = "";
+ 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.spec = specWithSuffix;
+ testURI.ref = "";
+ 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 .path directly (including suffix) and then clearing .ref
+ // (same as above, but with now with .path instead of .spec)
+ testURI = NetUtil.newURI(aTest.spec);
+
+ var pathWithSuffix = aTest.path + aSuffix;
+ do_info("testing that setting path to " +
+ pathWithSuffix + " and then clearing ref does what we expect");
+ testURI.path = pathWithSuffix;
+ testURI.ref = "";
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // Also: make sure that clearing .path also clears .ref
+ testURI.path = pathWithSuffix;
+ do_info("testing that clearing path from " +
+ pathWithSuffix + " also clears .ref");
+ testURI.path = "";
+ do_check_eq(testURI.ref, "");
+ }
+ }
+}
+
+// Tests that normally-mutable properties can't be modified on
+// special URIs that are known to be immutable.
+function do_test_immutable(aTest) {
+ do_check_true(aTest.immutable);
+
+ var URI = NetUtil.newURI(aTest.spec);
+ // All the non-readonly attributes on nsIURI.idl:
+ var propertiesToCheck = ["spec", "scheme", "userPass", "username", "password",
+ "hostPort", "host", "port", "path", "ref"];
+
+ propertiesToCheck.forEach(function(aProperty) {
+ var threw = false;
+ try {
+ URI[aProperty] = "anothervalue";
+ } catch(e) {
+ threw = true;
+ }
+
+ do_info("testing that setting '" + aProperty +
+ "' on immutable URI '" + aTest.spec + "' will throw");
+ do_check_true(threw);
+ });
+}
+
+
+// TEST MAIN FUNCTION
+// ------------------
+function run_test()
+{
+ // UTF-8 check - From bug 622981
+ // ASCII
+ let base = gIoService.newURI("http://example.org/xenia?", null, null);
+ let resolved = gIoService.newURI("?x", null, base);
+ let expected = gIoService.newURI("http://example.org/xenia?x",
+ null, null);
+ do_info("Bug 662981: ACSII - comparing " + resolved.spec + " and " + expected.spec);
+ do_check_true(resolved.equals(expected));
+
+ // UTF-8 character "è"
+ // Bug 622981 was triggered by an empty query string
+ base = gIoService.newURI("http://example.org/xènia?", null, null);
+ resolved = gIoService.newURI("?x", null, base);
+ expected = gIoService.newURI("http://example.org/xènia?x",
+ null, null);
+ do_info("Bug 662981: UTF8 - comparing " + resolved.spec + " and " + expected.spec);
+ do_check_true(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) {
+ do_test_immutable(aTest);
+ }
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_URIs2.js b/netwerk/test/unit/test_URIs2.js
new file mode 100644
index 0000000000..201473f58f
--- /dev/null
+++ b/netwerk/test/unit/test_URIs2.js
@@ -0,0 +1,693 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.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:",
+ path: "about:blank",
+ ref: "",
+ nsIURL: false, nsINestedURI: true, immutable: true },
+ { spec: "view-source:http://www.mozilla.org/",
+ scheme: "view-source",
+ prePath: "view-source:",
+ path: "http://www.mozilla.org/",
+ ref: "",
+ nsIURL: false, nsINestedURI: true, immutable: true },
+ { spec: "x-external:",
+ scheme: "x-external",
+ prePath: "x-external:",
+ path: "",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "x-external:abc",
+ scheme: "x-external",
+ prePath: "x-external:",
+ path: "abc",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "http://www2.example.com/",
+ relativeURI: "a/b/c/d",
+ scheme: "http",
+ prePath: "http://www2.example.com",
+ path: "/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:",
+ path: "h",
+ ref: "",
+ nsIURL: false, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "./g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g/",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "/g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "?y",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/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",
+ path: "/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",
+ path: "/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",
+ path: "/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",
+ path: "/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",
+ path: "/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",
+ path: "/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",
+ path: "/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",
+ path: "/b/c/d",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ */
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: ".",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "./",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "..",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../..",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+
+ // abnormal examples
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "../../../../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+
+ // coalesce
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "/./g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "/../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g.",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g.",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: ".g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/.g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g..",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g..",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "..g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/..g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: ".",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "./../g",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/g",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "./g/.",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/b/c/g/",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "http://a/b/c/d;p?q",
+ relativeURI: "g/./h",
+ scheme: "http",
+ prePath: "http://a",
+ path: "/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",
+ path: "/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",
+ path: "/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",
+ path: "/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",
+ path: "/bar",
+ ref: "",
+ nsIURL: true, nsINestedURI: false },
+ { spec: "https://www2.example.com/",
+ relativeURI: "//www3.example2.com/bar",
+ scheme: "https",
+ prePath: "https://www3.example2.com",
+ path: "/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 do_check_true.
+function do_check_uri_eq(aURI1, aURI2, aCheckTrueFunc) {
+ if (!aCheckTrueFunc) {
+ aCheckTrueFunc = do_check_true;
+ }
+
+ 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 == do_check_true) {
+ 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 do_check_true.
+function do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc) {
+ if (!aCheckTrueFunc) {
+ aCheckTrueFunc = do_check_true;
+ }
+
+ 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 + "'");
+ do_check_eq(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) {
+ do_check_eq(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) {
+ do_check_eq(e.result, aTest.relativeFail);
+ return;
+ }
+ do_throw(e.result);
+ }
+ do_info("relURI.path = " + relURI.path + ", was " + URI.path);
+ URI = relURI;
+ do_info("URI.path now = " + URI.path);
+ }
+
+ // Sanity-check
+ do_info("testing " + aTest.spec + " equals a clone of itself");
+ do_check_uri_eq(URI, URI.clone());
+ do_check_uri_eqExceptRef(URI, URI.cloneIgnoringRef());
+ do_info("testing " + aTest.spec + " instanceof nsIURL");
+ do_check_eq(URI instanceof Ci.nsIURL, aTest.nsIURL);
+ do_info("testing " + aTest.spec + " instanceof nsINestedURI");
+ do_check_eq(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;
+ }
+ do_check_true(threw || !isEqualToNull);
+
+
+ // Check the various components
+ do_check_property(aTest, URI, "scheme");
+ do_check_property(aTest, URI, "prePath");
+ do_check_property(aTest, URI, "path");
+ 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);
+ do_check_eq(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 '#'");
+ do_check_eq(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.clone());
+
+ do_info("testing " + aTest.spec +
+ " doesn't equal self with '" + aSuffix + "' appended");
+
+ do_check_false(origURI.equals(testURI));
+
+ do_info("testing " + aTest.spec +
+ " is equalExceptRef to self with '" + aSuffix + "' appended");
+ do_check_uri_eqExceptRef(origURI, testURI);
+
+ do_check_eq(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.cloneIgnoringRef();
+ do_check_uri_eq(cloneNoRef, origURI);
+ do_check_false(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, "path",
+ 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 '#'");
+ do_check_eq(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.ref = aSuffix;
+ 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.ref = suffixLackingHash;
+ 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.ref = "";
+ 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.spec = specWithSuffix;
+ testURI.ref = "";
+ 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 .path directly (including suffix) and then clearing .ref
+ // (same as above, but with now with .path instead of .spec)
+ testURI = NetUtil.newURI(aTest.spec);
+
+ var pathWithSuffix = aTest.path + aSuffix;
+ do_info("testing that setting path to " +
+ pathWithSuffix + " and then clearing ref does what we expect");
+ testURI.path = pathWithSuffix;
+ testURI.ref = "";
+ do_check_uri_eq(testURI, refURIWithoutSuffix);
+ do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
+
+ // Also: make sure that clearing .path also clears .ref
+ testURI.path = pathWithSuffix;
+ do_info("testing that clearing path from " +
+ pathWithSuffix + " also clears .ref");
+ testURI.path = "";
+ do_check_eq(testURI.ref, "");
+ }
+ }
+}
+
+// Tests that normally-mutable properties can't be modified on
+// special URIs that are known to be immutable.
+function do_test_immutable(aTest) {
+ do_check_true(aTest.immutable);
+
+ var URI = NetUtil.newURI(aTest.spec);
+ // All the non-readonly attributes on nsIURI.idl:
+ var propertiesToCheck = ["spec", "scheme", "userPass", "username", "password",
+ "hostPort", "host", "port", "path", "ref"];
+
+ propertiesToCheck.forEach(function(aProperty) {
+ var threw = false;
+ try {
+ URI[aProperty] = "anothervalue";
+ } catch(e) {
+ threw = true;
+ }
+
+ do_info("testing that setting '" + aProperty +
+ "' on immutable URI '" + aTest.spec + "' will throw");
+ do_check_true(threw);
+ });
+}
+
+
+// TEST MAIN FUNCTION
+// ------------------
+function run_test()
+{
+ // UTF-8 check - From bug 622981
+ // ASCII
+ let base = gIoService.newURI("http://example.org/xenia?", null, null);
+ let resolved = gIoService.newURI("?x", null, base);
+ let expected = gIoService.newURI("http://example.org/xenia?x",
+ null, null);
+ do_info("Bug 662981: ACSII - comparing " + resolved.spec + " and " + expected.spec);
+ do_check_true(resolved.equals(expected));
+
+ // UTF-8 character "è"
+ // Bug 622981 was triggered by an empty query string
+ base = gIoService.newURI("http://example.org/xènia?", null, null);
+ resolved = gIoService.newURI("?x", null, base);
+ expected = gIoService.newURI("http://example.org/xènia?x",
+ null, null);
+ do_info("Bug 662981: UTF8 - comparing " + resolved.spec + " and " + expected.spec);
+ do_check_true(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) {
+ do_test_immutable(aTest);
+ }
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_XHR_redirects.js b/netwerk/test/unit/test_XHR_redirects.js
new file mode 100644
index 0000000000..8c9b42437b
--- /dev/null
+++ b/netwerk/test/unit/test_XHR_redirects.js
@@ -0,0 +1,235 @@
+// 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.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.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 = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ 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;
+ do_check_eq(xhr.status, status);
+
+ if (status == 200) {
+ // if followed then check for echoed method name
+ do_check_eq(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..efcd5910ea
--- /dev/null
+++ b/netwerk/test/unit/test_about_networking.js
@@ -0,0 +1,96 @@
+/* -*- 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/. */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const gDashboard = Cc['@mozilla.org/network/dashboard;1']
+ .getService(Ci.nsIDashboard);
+
+const gServerSocket = Components.classes["@mozilla.org/network/server-socket;1"]
+ .createInstance(Components.interfaces.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;
+ }
+ }
+ do_check_eq(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;
+ }
+ }
+ do_check_eq(found, true);
+
+ do_test_pending();
+ gHttpServer.stop(do_test_finished);
+
+ run_next_test();
+ });
+});
+
+add_test(function test_sockets() {
+ 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(null, 0, "127.0.0.1",
+ gServerSocket.port, null);
+ let listener = {
+ onTransportStatus: function(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;
+ }
+ }
+ do_check_eq(found, true);
+
+ run_next_test();
+ });
+ }
+ }
+ };
+ transport.setEventSink(listener, threadManager.currentThread);
+
+ transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);
+});
+
+function run_test() {
+ let ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ gHttpServer.start(-1);
+
+ let uri = ioService.newURI("http://localhost:" + gHttpServer.identity.primaryPort,
+ null, null);
+ let channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+
+ channel.open2();
+
+ gServerSocket.init(-1, true, -1);
+
+ 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..8f45d1c18d
--- /dev/null
+++ b/netwerk/test/unit/test_about_protocol.js
@@ -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/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+var unsafeAboutModule = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+ newChannel: function (aURI, aLoadInfo) {
+ var uri = Services.io.newURI("about:blank", null, null);
+ let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
+ chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
+ return chan;
+ },
+ getURIFlags: function (aURI) {
+ return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT;
+ }
+};
+
+var factory = {
+ createInstance: function(aOuter, aIID) {
+ if (aOuter)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return unsafeAboutModule.QueryInterface(aIID);
+ },
+ lockFactory: function(aLock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.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
+ });
+
+ do_check_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..2abe3c5768
--- /dev/null
+++ b/netwerk/test/unit/test_aboutblank.js
@@ -0,0 +1,32 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test() {
+ var base = NetUtil.newURI("http://www.example.com", null, null);
+ var about1 = NetUtil.newURI("about:blank", null, null);
+ var about2 = NetUtil.newURI("about:blank", null, base);
+
+ var chan1 = NetUtil.newChannel({
+ uri: about1,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIPropertyBag2);
+
+ var chan2 = NetUtil.newChannel({
+ uri: about2,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIPropertyBag2);
+
+ var haveProp = false;
+ var propVal = null;
+ try {
+ propVal = chan1.getPropertyAsInterface("baseURI",
+ Components.interfaces.nsIURI);
+ haveProp = true;
+ } catch (e if e.result == Components.results.NS_ERROR_NOT_AVAILABLE) {
+ // Property shouldn't be there.
+ }
+ do_check_eq(propVal, null);
+ do_check_eq(haveProp, false);
+ do_check_eq(chan2.getPropertyAsInterface("baseURI",
+ Components.interfaces.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..736282a118
--- /dev/null
+++ b/netwerk/test/unit/test_addr_in_use_error.js
@@ -0,0 +1,32 @@
+// 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.
+
+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);
+ do_check_true(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_simple.js b/netwerk/test/unit/test_alt-data_simple.js
new file mode 100644
index 0000000000..a140809231
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_simple.js
@@ -0,0 +1,111 @@
+/**
+ * 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
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+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});
+}
+
+const responseContent = "response body";
+const altContent = "!@#$%^&*()";
+const altContentType = "text/binary";
+
+var servedNotModified = false;
+
+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") {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ servedNotModified = true;
+ } else {
+ 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);
+
+ chan.asyncOpen2(new ChannelListener(readServerContent, null));
+ do_test_pending();
+}
+
+function readServerContent(request, buffer)
+{
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ do_check_eq(buffer, responseContent);
+ do_check_eq(cc.alternativeDataType, "");
+
+ do_execute_soon(() => {
+ var os = cc.openAlternativeOutputStream(altContentType);
+ os.write(altContent, altContent.length);
+ os.close();
+
+ do_execute_soon(flushAndOpenAltChannel);
+ });
+}
+
+// needs to be rooted
+var cacheFlushObserver = cacheFlushObserver = { observe: function() {
+ cacheFlushObserver = null;
+
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(altContentType);
+
+ chan.asyncOpen2(new ChannelListener(readAltContent, null));
+}};
+
+function flushAndOpenAltChannel()
+{
+ // We need to do a GC pass to ensure the cache entry has been freed.
+ gc();
+ Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver);
+}
+
+function readAltContent(request, buffer)
+{
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+ do_check_eq(servedNotModified, true);
+ do_check_eq(cc.alternativeDataType, altContentType);
+ do_check_eq(buffer, altContent);
+
+ 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..da3794dd06
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_stream.js
@@ -0,0 +1,120 @@
+/**
+ * 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.
+ *
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+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});
+}
+
+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);
+
+ chan.asyncOpen2(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);
+
+ do_check_eq(buffer, responseContent);
+ do_check_eq(cc.alternativeDataType, "");
+
+ do_execute_soon(() => {
+ os = cc.openAlternativeOutputStream(altContentType);
+ // Write a quarter of the alt data content
+ os.write(altContent, firstChunkSize);
+
+ do_execute_soon(openAltChannel);
+ });
+}
+
+function openAltChannel()
+{
+ var chan = make_channel(URL);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType(altContentType);
+
+ chan.asyncOpen2(listener);
+}
+
+var listener = {
+ buffer: "",
+ onStartRequest: function(request, context) { },
+ onDataAvailable: function(request, context, 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: function(request, context, status) {
+ var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+ do_check_eq(cc.alternativeDataType, altContentType);
+ do_check_eq(this.buffer.length, altContent.length);
+ do_check_eq(this.buffer, altContent);
+ httpServer.stop(do_test_finished);
+ },
+};
diff --git a/netwerk/test/unit/test_altsvc.js b/netwerk/test/unit/test_altsvc.js
new file mode 100644
index 0000000000..7dafca0285
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc.js
@@ -0,0 +1,378 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var h2Port;
+var prefs;
+var spdypref;
+var http2pref;
+var tlspref;
+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 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");
+ do_check_neq(h2Port, null);
+ do_check_neq(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");
+ tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+ 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.spdy.enforce-tls-profile", false);
+ 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 CA.cert.der
+ // 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, "CA.cert.der", "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 + '": { "tls-ports": [' + h2Port + '] }}';
+ 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.spdy.enforce-tls-profile", tlspref);
+ prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
+ prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+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 der = readFile(certFile);
+ certdb.addCert(der, trustString, null);
+}
+
+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 nextTest;
+var expectPass = true;
+var waitFor = 0;
+
+var Listener = function() {};
+Listener.prototype = {
+ onStartRequest: function testOnStartRequest(request, ctx) {
+ do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+
+ if (expectPass) {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+ do_check_eq(request.responseStatus, 200);
+ } else {
+ do_check_eq(Components.isSuccessCode(request.status), false);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, ctx, status) {
+ var routed = "";
+ try {
+ routed = request.getRequestHeader("Alt-Used");
+ } catch (e) {}
+ dump("routed is " + routed + "\n");
+ do_check_eq(Components.isSuccessCode(status), expectPass);
+
+ if (waitFor != 0) {
+ do_check_eq(routed, "");
+ do_test_pending();
+ loadWithoutClearingMappings = true;
+ do_timeout(waitFor, doTest);
+ waitFor = 0;
+ xaltsvc = "NA";
+ } else if (xaltsvc == "NA") {
+ do_check_eq(routed, "");
+ nextTest();
+ } else if (routed == xaltsvc) {
+ do_check_eq(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();
+ 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;
+ }
+ loadWithoutClearingMappings = false;
+ chan.asyncOpen2(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 = testsDone;
+ do_test_pending();
+ doTest();
+}
+
diff --git a/netwerk/test/unit/test_assoc.js b/netwerk/test/unit/test_assoc.js
new file mode 100644
index 0000000000..ded2e3d5ab
--- /dev/null
+++ b/netwerk/test/unit/test_assoc.js
@@ -0,0 +1,102 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var currentTestIndex = 0;
+
+XPCOMUtils.defineLazyGetter(this, "port", function() {
+ return httpserver.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+ return [
+ // this is valid
+ {url: "/assoc/assoctest?valid",
+ responseheader: ["Assoc-Req: GET http://localhost:" + port +
+ "/assoc/assoctest?valid",
+ "Pragma: X-Verify-Assoc-Req"],
+ flags: 0},
+
+ // this is invalid because the method is wrong
+ {url: "/assoc/assoctest?invalid",
+ responseheader: ["Assoc-Req: POST http://localhost:" + port +
+ "/assoc/assoctest?invalid",
+ "Pragma: X-Verify-Assoc-Req"],
+ flags: CL_EXPECT_LATE_FAILURE},
+
+ // this is invalid because the url is wrong
+ {url: "/assoc/assoctest?notvalid",
+ responseheader: ["Assoc-Req: GET http://localhost:" + port +
+ "/wrongpath/assoc/assoctest?notvalid",
+ "Pragma: X-Verify-Assoc-Req"],
+ flags: CL_EXPECT_LATE_FAILURE},
+
+ // this is invalid because the space between method and URL is missing
+ {url: "/assoc/assoctest?invalid2",
+ responseheader: ["Assoc-Req: GEThttp://localhost:" + port +
+ "/assoc/assoctest?invalid2",
+ "Pragma: X-Verify-Assoc-Req"],
+ flags: CL_EXPECT_LATE_FAILURE},
+ ];
+});
+
+var oldPrefVal;
+var domBranch;
+
+function setupChannel(url)
+{
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + port + url,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+function startIter()
+{
+ var channel = setupChannel(tests[currentTestIndex].url);
+ channel.asyncOpen2(new ChannelListener(completeIter,
+ channel, tests[currentTestIndex].flags));
+}
+
+function completeIter(request, data, ctx)
+{
+ if (++currentTestIndex < tests.length ) {
+ startIter();
+ } else {
+ domBranch.setBoolPref("enforce", oldPrefVal);
+ httpserver.stop(do_test_finished);
+ }
+}
+
+function run_test()
+{
+ var prefService =
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ domBranch = prefService.getBranch("network.http.assoc-req.");
+ oldPrefVal = domBranch.getBoolPref("enforce");
+ domBranch.setBoolPref("enforce", true);
+
+ httpserver.registerPathHandler("/assoc/assoctest", 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[currentTestIndex].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, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
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..a45ef7532e
--- /dev/null
+++ b/netwerk/test/unit/test_auth_dialog_permission.js
@@ -0,0 +1,255 @@
+// 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.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function authprompt_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt: function(title, text, realm, save, defaultText, result) {
+ do_throw("unexpected prompt call");
+ },
+
+ promptUsernameAndPassword: function(title, text, realm, savePW, user, pw) {
+ do_check_true(this.promptExpected,
+ "Not expected the authentication prompt.");
+
+ user.value = this.user;
+ pw.value = this.pass;
+ return true;
+ },
+
+ promptPassword: function(title, text, realm, save, pwd) {
+ do_throw("unexpected promptPassword call");
+ }
+
+};
+
+function Requestor(promptExpected) {
+ this.promptExpected = promptExpected;
+}
+
+Requestor.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIAuthPrompt)) {
+ this.prompter = new AuthPrompt(this.promptExpected);
+ return this.prompter;
+ }
+
+ throw Components.results.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, null, null);
+}
+
+function makeChan(loadingUrl, url, contentPolicy) {
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = make_uri(loadingUrl);
+ var principal = ssm.createCodebasePrincipal(uri, {});
+
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+ contentPolicyType: contentPolicy
+ }).QueryInterface(Components.interfaces.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: function(request, ctx) {
+ try {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code!");
+ }
+
+ if (!(request instanceof Components.interfaces.nsIHttpChannel)) {
+ do_throw("Expecting an HTTP channel");
+ }
+
+ do_check_eq(request.responseStatus, this._expectedCode);
+ // The request should be succeeded iff we expect 200
+ do_check_eq(request.requestSucceeded, this._expectedCode == 200);
+
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function(request, ctx, status) {
+ do_check_eq(status, Components.results.NS_ERROR_ABORT);
+
+ // Clear the auth cache.
+ Components.classes["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Components.interfaces.nsIHttpAuthManager)
+ .clearAll();
+
+ do_timeout(0, run_next_test);
+ },
+
+ run: function() {
+ 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.asyncOpen2(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..e3050105e7
--- /dev/null
+++ b/netwerk/test/unit/test_auth_jar.js
@@ -0,0 +1,49 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function createURI(s) {
+ let service = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ return service.newURI(s, null, null);
+}
+
+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 app1 = secMan.createCodebasePrincipal(createURI(kURI1), {appId: 1});
+ var app10 = secMan.createCodebasePrincipal(createURI(kURI1),{appId: 10});
+ var app1browser = secMan.createCodebasePrincipal(createURI(kURI1), {appId: 1, 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, app1);
+ am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user3", "pass3", false, app1browser);
+ am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user2", "pass2", false, app10);
+
+ let attrs_inBrowser = JSON.stringify({ appId:1, inIsolatedMozBrowser:true });
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_inBrowser);
+
+ var domain = {value: ""}, user = {value: ""}, pass = {value: ""};
+ try {
+ am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app1browser);
+ do_check_false(true); // no identity should be present
+ } catch (x) {
+ do_check_eq(domain.value, "");
+ do_check_eq(user.value, "");
+ do_check_eq(pass.value, "");
+ }
+
+ am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app1);
+ do_check_eq(domain.value, "example.com");
+ do_check_eq(user.value, "user");
+ do_check_eq(pass.value, "pass");
+
+
+ am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app10);
+ do_check_eq(domain.value, "example.com");
+ do_check_eq(user.value, "user2");
+ do_check_eq(pass.value, "pass2");
+}
diff --git a/netwerk/test/unit/test_auth_proxy.js b/netwerk/test/unit/test_auth_proxy.js
new file mode 100644
index 0000000000..ae5260ade8
--- /dev/null
+++ b/netwerk/test/unit/test_auth_proxy.js
@@ -0,0 +1,399 @@
+/* -*- 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"/>
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function authprompt2_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIAuthPrompt2))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ promptAuth:
+ function ap2_promptAuth(channel, encryptionLevel, authInfo)
+ {
+ try {
+
+ // never HOST and PROXY set at the same time in prompt
+ do_check_eq((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)
+ do_check_eq(cred.realmExpected, authInfo.realm);
+
+ // PREVIOUS_FAILED properly set by necko
+ do_check_eq((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;
+ do_execute_soon(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: function cancelable_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICancelable))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ cancel: function cancel() {
+ try {
+ this.onCancelFunc();
+ } catch (e) { do_throw(e); }
+ }
+};
+
+function Requestor(proxyFlags, hostFlags) {
+ this.proxyFlags = proxyFlags;
+ this.hostFlags = hostFlags;
+}
+Requestor.prototype = {
+ QueryInterface: function requestor_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIInterfaceRequestor))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt)) {
+ dump("authprompt1 not implemented\n");
+ throw 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 Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt2: null
+};
+
+var listener = {
+ expectedCode: -1, // uninitialized
+
+ onStartRequest: function test_onStartR(request, ctx) {
+ 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");
+
+ do_check_eq(this.expectedCode, request.responseStatus);
+ // If we expect 200, the request should have succeeded
+ do_check_eq(this.expectedCode == 200, request.requestSucceeded);
+
+ var cookie = "";
+ try {
+ cookie = request.getRequestHeader("Cookie");
+ } catch (e) { }
+ do_check_eq(cookie, "");
+
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Cr.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ do_check_eq(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);
+
+ 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.setCharPref("network.proxy.no_proxies_on", "");
+ prefs.setIntPref("network.proxy.type", 1);
+
+ // Turn off the authentication dialog blocking for this test.
+ prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+
+ 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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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..a7e059a2b7
--- /dev/null
+++ b/netwerk/test/unit/test_authentication.js
@@ -0,0 +1,2074 @@
+// This file tests authentication prompt callbacks
+// TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// 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 nsIAuthPrompt2 = Components.interfaces.nsIAuthPrompt2;
+const nsIAuthInformation = Components.interfaces.nsIAuthInformation;
+
+
+function AuthPrompt1(flags) {
+ this.flags = flags;
+}
+
+AuthPrompt1.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ expectedRealm: "secret",
+
+ QueryInterface: function authprompt_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ 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)
+ {
+ // Note that the realm here isn't actually the realm. it's a pw mgr key.
+ do_check_eq(URL + " (" + this.expectedRealm + ")", realm);
+ if (!(this.flags & CROSS_ORIGIN)) {
+ if (text.indexOf(this.expectedRealm) == -1) {
+ do_throw("Text must indicate the realm");
+ }
+ } else {
+ if (text.indexOf(this.expectedRealm) != -1) {
+ do_throw("There should not be realm for cross origin");
+ }
+ }
+ if (text.indexOf("localhost") == -1)
+ do_throw("Text must indicate the hostname");
+ if (text.indexOf(String(PORT)) == -1)
+ do_throw("Text must indicate the port");
+ if (text.indexOf("-1") != -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";
+
+ 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 {
+ 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: function authprompt2_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt2))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ promptAuth:
+ function ap2_promptAuth(channel, level, authInfo)
+ {
+ var isNTLM = channel.URI.path.indexOf("ntlm") != -1;
+ var isDigest = channel.URI.path.indexOf("digest") != -1;
+
+ if (isNTLM)
+ this.expectedRealm = ""; // NTLM knows no realms
+
+ do_check_eq(this.expectedRealm, authInfo.realm);
+
+ var expectedLevel = (isNTLM || isDigest) ?
+ nsIAuthPrompt2.LEVEL_PW_ENCRYPTED :
+ nsIAuthPrompt2.LEVEL_NONE;
+ do_check_eq(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 = 63; // Don't fail test for newly added flags
+ do_check_eq(expectedFlags, authInfo.flags & kAllKnownFlags);
+
+ var expectedScheme = isNTLM ? "ntlm" : isDigest ? "digest" : "basic";
+ do_check_eq(expectedScheme, authInfo.authenticationScheme);
+
+ // No passwords in the URL -> nothing should be prefilled
+ do_check_eq(authInfo.username, "");
+ do_check_eq(authInfo.password, "");
+ do_check_eq(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";
+
+ 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 {
+ authInfo.password = this.pass;
+ this.flags &= ~FLAG_PREVIOUS_FAILED;
+ }
+ return true;
+ },
+
+ asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+function Requestor(flags, versions) {
+ this.flags = flags;
+ this.versions = versions;
+}
+
+Requestor.prototype = {
+ QueryInterface: function requestor_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function requestor_gi(iid) {
+ if (this.versions & 1 &&
+ iid.equals(Components.interfaces.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(Components.interfaces.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.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ prompt1: null,
+ prompt2: null
+};
+
+function RealmTestRequestor() {}
+
+RealmTestRequestor.prototype = {
+ QueryInterface: function realmtest_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt2))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function realmtest_interface(iid) {
+ if (iid.equals(Components.interfaces.nsIAuthPrompt2))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
+ do_check_eq(authInfo.realm, '\"foo_bar');
+
+ return false;
+ },
+
+ asyncPromptAuth: function realmtest_async(chan, cb, ctx, lvl, info) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+var listener = {
+ expectedCode: -1, // Uninitialized
+
+ onStartRequest: function test_onStartR(request, ctx) {
+ try {
+ if (!Components.isSuccessCode(request.status))
+ do_throw("Channel should have a success code!");
+
+ if (!(request instanceof Components.interfaces.nsIHttpChannel))
+ do_throw("Expecting an HTTP channel");
+
+ do_check_eq(request.responseStatus, this.expectedCode);
+ // The request should be succeeded iff we expect 200
+ do_check_eq(request.requestSucceeded, this.expectedCode == 200);
+
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ do_check_eq(status, Components.results.NS_ERROR_ABORT);
+
+ if (current_test < (tests.length - 1)) {
+ // First, gotta clear the auth cache
+ Components.classes["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Components.interfaces.nsIHttpAuthManager)
+ .clearAll();
+
+ current_test++;
+ tests[current_test]();
+ } else {
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ }
+
+ do_test_finished();
+ }
+};
+
+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.createCodebasePrincipal(ios.newURI(loadingUrl, null, null), {});
+ return NetUtil.newChannel(
+ { uri: url, loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ contentPolicyType: Components.interfaces.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_digest_noauth, test_digest,
+ test_digest_bogus_user, test_large_realm, test_large_domain];
+
+var current_test = 0;
+
+var httpserv = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+
+ httpserv.registerPathHandler("/auth", authHandler);
+ httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
+ httpserv.registerPathHandler("/auth/realm", authRealm);
+ httpserv.registerPathHandler("/auth/digest", authDigest);
+ 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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_prompt1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 1);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_prompt2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen2(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.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_basicrealm() {
+ var chan = makeChan(URL + "/auth/realm", URL);
+
+ chan.notificationCallbacks = new RealmTestRequestor();
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(listener);
+
+ 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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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);
+}
+
+//
+// Digest functions
+//
+function bytesFromString(str) {
+ var converter =
+ Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.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 = Components.classes["@mozilla.org/security/hash;1"]
+ .createInstance(Components.interfaces.nsICryptoHash);
+ ch.init(Components.interfaces.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 largeRealm(metadata, response) {
+ // test > 32KB realm tokens
+ var body;
+
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate",
+ 'Digest realm="' +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ '", 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="' +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ '"');
+
+ 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.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function test_large_domain() {
+ var chan = makeChan(URL + "/largeDomain", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen2(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..d8a1dc40cf
--- /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
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+const nsIAuthInformation = Components.interfaces.nsIAuthInformation;
+const nsIAuthPromptAdapterFactory = Components.interfaces.nsIAuthPromptAdapterFactory;
+
+function run_test() {
+ const contractID = "@mozilla.org/network/authprompt-adapter-factory;1";
+ if (!(contractID in Components.classes)) {
+ print("No adapter factory found, skipping testing");
+ return;
+ }
+ var adapter = Components.classes[contractID].getService();
+ do_check_eq(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
+ do_check_neq(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: function authprompt_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ 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) {
+ do_check_eq(this.scheme + "://" + host + " (" + info.realm + ")", realm);
+
+ do_check_neq(text.indexOf(host), -1);
+ if (info.flags & nsIAuthInformation.ONLY_PASSWORD) {
+ // Should have the username in the text
+ do_check_neq(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 != "")
+ do_check_neq(text.indexOf(info.realm), -1);
+ else
+ do_check_eq(text.indexOf('""'), -1);
+ // No explicit port in the URL; message should not contain -1
+ // for those cases
+ do_check_eq(text.indexOf("-1"), -1);
+ }
+ }
+ };
+
+
+ // Also have to make up a channel
+ var uri = NetUtil.newURI("http://" + host, "", null)
+ var chan = NetUtil.newChannel({
+ uri: 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);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ do_check_eq(info.domain, "");
+ do_check_eq(info.username, prompt1.user);
+ do_check_eq(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);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTP);
+
+ if (rv) {
+ do_check_eq(info.domain, "");
+ do_check_eq(info.username, prompt1.user); // we initialized this
+ do_check_eq(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);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ do_check_eq(info.domain, "foo");
+ do_check_eq(info.username, "bar");
+ do_check_eq(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);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ do_check_eq(info.domain, "");
+ do_check_eq(info.username, prompt1.user);
+ do_check_eq(info.password, prompt1.pw);
+ }
+
+ info.flags &= ~nsIAuthInformation.NEED_DOMAIN;
+
+ info.domain = "";
+ info.username = "";
+ info.password = "";
+
+ // 5: FTP
+ var uri2 = NetUtil.newURI("ftp://" + host, "", null);
+ 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);
+ do_check_eq(rv, prompt1.rv);
+ do_check_eq(prompt1.called, CALLED_PROMPTUP);
+
+ if (rv) {
+ do_check_eq(info.domain, "");
+ do_check_eq(info.username, prompt1.user);
+ do_check_eq(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..a545f596fd
--- /dev/null
+++ b/netwerk/test/unit/test_backgroundfilesaver.js
@@ -0,0 +1,731 @@
+/* -*- 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
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.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
+ 8388608 : "e3611a47714c42bdf326acfb2eb6ed9fa4cca65cb7d7be55217770a5bf5e7ff0",
+ // TEST_DATA_LONG + TEST_DATA_LONG
+ 16777216 : "03a0db69a30140f307587ee746a539247c181bafd85b85c8516a3533c7d9ea1d"
+};
+
+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 * 2;
+const TEST_DATA_LONG = new Array(1 + DESIRED_LENGTH / 256).join(TEST_256_CHARS);
+do_check_eq(TEST_DATA_LONG.length, DESIRED_LENGTH);
+
+/**
+ * Returns a reference to a temporary file. If the file is then created, it
+ * will be removed when tests in this file finish.
+ */
+function getTempFile(aLeafName) {
+ let file = FileUtils.getFile("TmpD", [aLeafName]);
+ do_register_cleanup(function GTF_cleanup() {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ });
+ return file;
+}
+
+/**
+ * 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) {
+ let deferred = Promise.defer();
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(aFile),
+ loadUsingSystemPrincipal: true
+ }, function(aInputStream, aStatus) {
+ do_check_true(Components.isSuccessCode(aStatus));
+ let contents = NetUtil.readInputStreamToString(aInputStream,
+ aInputStream.available());
+ if (contents.length <= TEST_DATA_SHORT.length * 2) {
+ do_check_eq(contents, aExpectedContents);
+ } else {
+ // Do not print the entire content string to the test log.
+ do_check_eq(contents.length, aExpectedContents.length);
+ do_check_true(contents == aExpectedContents);
+ }
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * 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) {
+ let deferred = Promise.defer();
+ aSaver.observer = {
+ onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget)
+ {
+ if (aOnTargetChangeFn) {
+ aOnTargetChangeFn(aTarget);
+ }
+ },
+ onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus)
+ {
+ if (Components.isSuccessCode(aStatus)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception("Saver failed.", aStatus));
+ }
+ },
+ };
+ return deferred.promise;
+}
+
+/**
+ * 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) {
+ let deferred = Promise.defer();
+ 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: function () { },
+ onStopRequest: function (aRequest, aContext, aStatusCode)
+ {
+ if (Components.isSuccessCode(aStatusCode)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception(aResult));
+ }
+ },
+ }, null);
+ return deferred.promise;
+}
+
+/**
+ * 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) {
+ let deferred = Promise.defer();
+ 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, -1, -1, 0, 0, true);
+ pump.asyncRead({
+ onStartRequest: function PPTS_onStartRequest(aRequest, aContext)
+ {
+ aSaverStreamListener.onStartRequest(aRequest, aContext);
+ },
+ onStopRequest: function PPTS_onStopRequest(aRequest, aContext, aStatusCode)
+ {
+ aSaverStreamListener.onStopRequest(aRequest, aContext, aStatusCode);
+ if (Components.isSuccessCode(aStatusCode)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception(aResult));
+ }
+ },
+ onDataAvailable: function PPTS_onDataAvailable(aRequest, aContext,
+ aInputStream, aOffset,
+ aCount)
+ {
+ aSaverStreamListener.onDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+ },
+ }, null);
+ return deferred.promise;
+}
+
+var gStillRunning = true;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+function run_test()
+{
+ run_next_test();
+}
+
+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(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) {
+ do_check_true(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.
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ // Indicate that we are ready to finish, and wait for a successful callback.
+ saver.finish(Cr.NS_OK);
+ yield 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.
+ do_check_true(receivedOnTargetChange);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(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;
+ do_print("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();
+ do_print("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.
+ yield 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) {
+ yield promiseCopyToSaver(testData, saver, true);
+ }
+
+ // Wait for completion, and ensure we succeeded or failed as expected.
+ if (!cancelAtSomePoint) {
+ saver.finish(Cr.NS_OK);
+ }
+ try {
+ yield completionPromise;
+ if (cancelAtSomePoint) {
+ do_throw("Failure expected.");
+ }
+ } catch (ex if cancelAtSomePoint && ex.result == Cr.NS_ERROR_FAILURE) { }
+
+ if (!cancelAtSomePoint) {
+ // In this case, the file must exist.
+ do_check_true(currentFile.exists());
+ let expectedContents = testData + testData;
+ yield promiseVerifyContents(currentFile, expectedContents);
+ do_check_eq(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)) {
+ do_check_false(initialFile.exists());
+ }
+ } else if (!keepPartialOnFailure) {
+ // In this case, the file must not exist.
+ do_check_false(initialFile.exists());
+ do_check_false(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);
+ }
+ }
+
+ do_print("Test case completed in " + (Date.now() - startTime) + " ms.");
+ }
+});
+
+add_task(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.
+ yield 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);
+ yield completionPromise;
+
+ // Verify results.
+ yield promiseVerifyContents(destFile, TEST_DATA_SHORT);
+ do_check_eq(EXPECTED_HASHES[TEST_DATA_SHORT.length],
+ toHex(saver.sha256Hash));
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(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.
+ yield 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);
+ yield completionPromise;
+
+ // Verify results and clean up.
+ do_check_false(destFile1.exists());
+ yield promiseVerifyContents(destFile2, TEST_DATA_SHORT);
+ destFile2.remove(false);
+});
+
+add_task(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);
+ yield 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);
+ yield completionPromise;
+
+ // Verify results and clean up.
+ do_check_false(getTempFile(TEST_FILE_NAME_2).exists());
+ do_check_false(getTempFile(TEST_FILE_NAME_3).exists());
+ yield promiseVerifyContents(destFile, TEST_DATA_SHORT);
+ destFile.remove(false);
+});
+
+add_task(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);
+ yield promiseCopyToSaver(TEST_DATA_LONG, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ let expectedContents = (i == 0 ? TEST_DATA_LONG
+ : TEST_DATA_LONG + TEST_DATA_LONG);
+ yield promiseVerifyContents(destFile, expectedContents);
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(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);
+
+ yield 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);
+ yield completionPromise;
+
+ // Verify results.
+ do_check_false(firstFile.exists());
+ let expectedContents = (i == 0 ? TEST_DATA_SHORT
+ : TEST_DATA_SHORT + TEST_DATA_SHORT);
+ yield promiseVerifyContents(secondFile, expectedContents);
+ }
+
+ // Clean up.
+ destFile1.remove(false);
+});
+
+add_task(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);
+ yield promiseCopyToSaver(TEST_DATA_LONG, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ let expectedContents = (i == 0 ? TEST_DATA_LONG
+ : TEST_DATA_LONG + TEST_DATA_LONG);
+ yield promiseVerifyContents(destFile, expectedContents);
+ do_check_eq(EXPECTED_HASHES[expectedContents.length],
+ toHex(saver.sha256Hash));
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(function test_finish_only()
+{
+ // This test checks creating the object and doing nothing.
+ let destFile = getTempFile(TEST_FILE_NAME_1);
+ 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);
+ yield completionPromise;
+});
+
+add_task(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);
+ yield promiseCopyToSaver("", saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ do_check_true(destFile.exists());
+ do_check_eq(destFile.fileSize, 0);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(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);
+ yield promiseCopyToSaver("", saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // Verify results.
+ do_check_eq(destFile.fileSize, 0);
+ do_check_eq(EXPECTED_HASHES[0], toHex(saver.sha256Hash));
+ }
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(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 {
+ let hash = 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) { }
+ // 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 {
+ let hash = 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) { }
+ // Wait for completion so that the worker thread finishes dealing with the
+ // target file. We expect it to fail.
+ try {
+ yield completionPromise;
+ do_throw("completionPromise should throw");
+ } catch (ex if ex.result == Cr.NS_ERROR_FAILURE) { }
+});
+
+add_task(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 {
+ let signatureInfo = saver.signatureInfo;
+ do_throw("Can't get signature if saver is not complete");
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+
+ saver.enableSignatureInfo();
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+ yield promiseVerifyContents(destFile, TEST_DATA_SHORT);
+
+ // signatureInfo is an empty nsIArray
+ do_check_eq(0, saver.signatureInfo.length);
+
+ // Clean up.
+ destFile.remove(false);
+});
+
+add_task(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);
+ yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+ try {
+ let signatureInfo = saver.signatureInfo;
+ do_throw("Can't get signature if not enabled");
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+
+ // 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..2c6ac46ad5
--- /dev/null
+++ b/netwerk/test/unit/test_be_conservative.js
@@ -0,0 +1,213 @@
+// -*- 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.
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Promise: promise } =
+ Cu.import("resource://gre/modules/Promise.jsm", {});
+
+// Get a profile directory and ensure PSM initializes NSS.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+function getCert() {
+ let deferred = promise.defer();
+ let certService = Cc["@mozilla.org/security/local-cert-service;1"]
+ .getService(Ci.nsILocalCertService);
+ certService.getOrCreateCert("beConservative-test", {
+ handleCert: function(c, rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve(c);
+ }
+ });
+ return deferred.promise;
+}
+
+class InputStreamCallback {
+ constructor(output) {
+ this.output = output;
+ this.stopped = false;
+ }
+
+ onInputStreamReady(stream) {
+ do_print("input stream ready");
+ if (this.stopped) {
+ do_print("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();
+ do_print("done with input stream ready");
+ }
+
+ stop() {
+ this.stopped = true;
+ }
+}
+
+class TLSServerSecurityObserver {
+ constructor(input, output) {
+ this.input = input;
+ this.output = output;
+ this.callbacks = [];
+ this.stopped = false;
+ }
+
+ onHandshakeDone(socket, status) {
+ do_print("TLS handshake done");
+ do_print(`TLS version used: ${status.tlsVersionUsed}`);
+
+ if (this.stopped) {
+ do_print("handshake done callback stopped - closing streams and bailing");
+ this.input.close();
+ this.output.close();
+ 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.callbacks.forEach((callback) => {
+ callback.stop();
+ });
+ }
+}
+
+class ServerSocketListener {
+ constructor() {
+ this.securityObservers = [];
+ }
+
+ onSocketAccepted(socket, transport) {
+ do_print("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() {
+ do_print("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.setSessionCache(false);
+ 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;
+ let deferred = promise.defer();
+ req.onload = () => {
+ ok(expectSuccess,
+ `should ${expectSuccess ? "" : "not "}have gotten load event`);
+ equal(req.responseText, "OK", "response text should be 'OK'");
+ deferred.resolve();
+ };
+ req.onerror = () => {
+ ok(!expectSuccess,
+ `should ${!expectSuccess ? "" : "not "}have gotten an error`);
+ deferred.resolve();
+ };
+
+ req.send();
+ return deferred.promise;
+}
+
+add_task(function*() {
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ Services.prefs.setCharPref("network.dns.localDomains", hostname);
+ let cert = yield 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);
+ yield 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);
+ yield startClient(server.port, true /*be conservative*/,
+ false /*should fail*/);
+
+ // However, a non-conservative client should succeed.
+ yield startClient(server.port, false /*don't be conservative*/,
+ true /*should succeed*/);
+ server.close();
+});
+
+do_register_cleanup(function() {
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+});
diff --git a/netwerk/test/unit/test_bug1064258.js b/netwerk/test/unit/test_bug1064258.js
new file mode 100644
index 0000000000..3f76837ae9
--- /dev/null
+++ b/netwerk/test/unit/test_bug1064258.js
@@ -0,0 +1,153 @@
+/**
+ * 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
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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:
+ do_check_true(metadata.hasHeader("If-None-Match"));
+ do_check_eq(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");
+ caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen2(new ChannelListener(contentListener1a, null));
+}
+
+function contentListener1a(request, buffer)
+{
+ do_check_eq(buffer, responseBody1);
+
+ asyncOpenCacheEntry(URL + "/content1", "disk", 0, null, cacheCheck1)
+}
+
+function cacheCheck1(status, entry)
+{
+ do_check_eq(status, 0);
+ do_check_eq(entry.dataSize, 0);
+ try {
+ do_check_neq(entry.getMetaDataElement("response-head"), null);
+ }
+ catch (ex) {
+ do_throw("Missing response head");
+ }
+
+ var chan = make_channel(URL + "/content1");
+ caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen2(new ChannelListener(contentListener1b, null, CL_IGNORE_CL));
+}
+
+function contentListener1b(request, buffer)
+{
+ request.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(request.requestMethod, "GET");
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(request.getResponseHeader("Cache-control"), "max-age=999999");
+
+ do_check_eq(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");
+ caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen2(new ChannelListener(contentListener2a, null));
+}
+
+function contentListener2a(request, buffer)
+{
+ do_check_eq(buffer, responseBody2a);
+
+ asyncOpenCacheEntry(URL + "/content2", "disk", 0, null, cacheCheck2)
+}
+
+function cacheCheck2(status, entry)
+{
+ do_check_eq(status, 0);
+ do_check_eq(entry.dataSize, 0);
+ try {
+ do_check_neq(entry.getMetaDataElement("response-head"), null);
+ do_check_true(entry.getMetaDataElement("response-head").match('Etag: testetag'));
+ }
+ catch (ex) {
+ do_throw("Missing response head");
+ }
+
+ var chan = make_channel(URL + "/content2");
+ caching = chan.QueryInterface(Ci.nsICachingChannel);
+ caching.cacheOnlyMetadata = true;
+ chan.asyncOpen2(new ChannelListener(contentListener2b, null));
+}
+
+function contentListener2b(request, buffer)
+{
+ do_check_eq(buffer, responseBody2b);
+
+ httpServer.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_bug1195415.js b/netwerk/test/unit/test_bug1195415.js
new file mode 100644
index 0000000000..b97d209d3f
--- /dev/null
+++ b/netwerk/test/unit/test_bug1195415.js
@@ -0,0 +1,116 @@
+// Test for bug 1195415
+
+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", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com");
+ uri.port = 90;
+ var prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com:90");
+ do_check_eq(prin.origin, "http://foo.com:90");
+
+ uri = ios.newURI("http://foo.com:10/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com:10");
+ uri.port = 500;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com:500");
+ do_check_eq(prin.origin, "http://foo.com:500");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com:5000");
+ uri.port = 20;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com:20");
+ do_check_eq(prin.origin, "http://foo.com:20");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com:5000");
+ uri.port = -1;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com");
+ do_check_eq(prin.origin, "http://foo.com");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "foo.com:5000");
+ uri.port = 80;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "foo.com");
+ do_check_eq(prin.origin, "http://foo.com");
+
+ // UNICODE
+ uri = ios.newURI("http://jos\u00e9.example.net.ch/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ uri.port = 90;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:90");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch:90");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:10/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:10");
+ uri.port = 500;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:500");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch:500");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri.port = 20;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:20");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch:20");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri.port = -1;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch");
+
+ uri = ios.newURI("http://jos\u00e9.example.net.ch:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch:5000");
+ uri.port = 80;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "xn--jos-dma.example.net.ch");
+ do_check_eq(prin.origin, "http://xn--jos-dma.example.net.ch");
+
+ // ipv6
+ uri = ios.newURI("http://[123:45::678]/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]");
+ uri.port = 90;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:90");
+ do_check_eq(prin.origin, "http://[123:45::678]:90");
+
+ uri = ios.newURI("http://[123:45::678]:10/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:10");
+ uri.port = 500;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:500");
+ do_check_eq(prin.origin, "http://[123:45::678]:500");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:5000");
+ uri.port = 20;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:20");
+ do_check_eq(prin.origin, "http://[123:45::678]:20");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:5000");
+ uri.port = -1;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]");
+ do_check_eq(prin.origin, "http://[123:45::678]");
+
+ uri = ios.newURI("http://[123:45::678]:5000/file.txt", null, null);
+ do_check_eq(uri.asciiHostPort, "[123:45::678]:5000");
+ uri.port = 80;
+ prin = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(uri.asciiHostPort, "[123:45::678]");
+ do_check_eq(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..cbab52797e
--- /dev/null
+++ b/netwerk/test/unit/test_bug1218029.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+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: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ let chunkIndex = 0;
+
+ let observer = {
+ onStreamComplete: function(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: function (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:
+ XPCOMUtils.generateQI([Ci.nsIIncrementalStreamLoaderObserver])
+ };
+
+ let listener = Cc["@mozilla.org/network/incremental-stream-loader;1"]
+ .createInstance(Ci.nsIIncrementalStreamLoader);
+ listener.init(observer);
+
+ listener.onStartRequest(channel, null);
+ var offset = 0;
+ test.chunks.forEach(function (chunkLength) {
+ listener.onDataAvailable(channel, null, stream, offset, chunkLength);
+ offset += chunkLength;
+ });
+ listener.onStopRequest(channel, null, 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..8111a6c4bc
--- /dev/null
+++ b/netwerk/test/unit/test_bug1279246.js
@@ -0,0 +1,97 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, ctx) {
+ do_check_eq(request.status, Cr.NS_OK);
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, cx, 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: function(request, ctx, status) {
+ if (pass == 0) {
+ do_check_eq(this._buffer.length, responseLen);
+ pass++;
+
+ var channel = setupChannel();
+ channel.loadFlags = Ci.nsIRequest.VALIDATE_NEVER;
+ channel.asyncOpen2(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");
+
+ httpserver.registerPathHandler(testUrl, handler);
+ httpserver.start(-1);
+
+ var channel = setupChannel();
+ channel.asyncOpen2(new Listener());
+
+ do_test_pending();
+}
+
+function handler(metadata, response) {
+ do_check_eq(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 = Components.classes["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(responseBody, responseBody.length);
+ response.finish();
+}
diff --git a/netwerk/test/unit/test_bug203271.js b/netwerk/test/unit/test_bug203271.js
new file mode 100644
index 0000000000..34463811ef
--- /dev/null
+++ b/netwerk/test/unit/test_bug203271.js
@@ -0,0 +1,177 @@
+//
+// 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
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+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: function(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(Components.interfaces.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.asyncOpen2(new ChannelListener(checkValueAndTrigger, channel));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data, ctx);
+ do_check_eq(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..ee6930c609
--- /dev/null
+++ b/netwerk/test/unit/test_bug248970_cache.js
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// 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) {
+ do_execute_soon(store_cb);
+ return;
+ }
+
+ asyncOpenCacheEntry(entries[store_idx][0],
+ entries[store_idx][2],
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ LoadContextInfo.custom(false,
+ {privateBrowsingId : entries[store_idx][3] ? 0 : 1}),
+ store_data,
+ appCache);
+}
+
+var store_data = function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0);
+
+ 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++;
+ do_execute_soon(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) {
+ do_execute_soon(check_cb);
+ return;
+ }
+
+ asyncOpenCacheEntry(entries[check_idx][0],
+ entries[check_idx][2],
+ Ci.nsICacheStorage.OPEN_READONLY,
+ LoadContextInfo.custom(false,
+ {privateBrowsingId : entries[check_idx][3] ? 0 : 1}),
+ check_data,
+ appCache);
+}
+
+var check_data = function (status, entry) {
+ var cont = function() {
+ check_idx++;
+ do_execute_soon(check_entries);
+ }
+
+ if (!check_pb_exited || entries[check_idx][3]) {
+ do_check_eq(status, Cr.NS_OK);
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function(read) {
+ entry.close();
+ do_check_eq(read, entries[check_idx][1]);
+ cont();
+ });
+ } else {
+ do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ cont();
+ }
+};
+
+function run_test() {
+ // Simulate a profile dir for xpcshell
+ do_get_profile();
+
+ 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", null);
+
+ // Make sure the memory device is not empty
+ get_device_entry_count(kMemoryDevice, null, function(count) {
+ do_check_eq(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..41c45e1059
--- /dev/null
+++ b/netwerk/test/unit/test_bug248970_cookie.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/. */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(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) {
+ var chan = setup_chan('present?cookie=' + value.replace('=','|'), isPrivate, function(req) {
+ req.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(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").indexOf(match[1].replace("|","=")) != -1;
+ } 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);
+
+ 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);
+ do_check_eq(req.responseStatus, 200);
+ try {
+ do_check_true(req.getResponseHeader("Set-Cookie") != "", "expected a Set-Cookie header");
+ } catch (x) {
+ do_throw("missing Set-Cookie header");
+ }
+
+ runNextTest();
+ }
+
+ let tests = [];
+
+ function runNextTest() {
+ do_execute_soon(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", null);
+ // 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..4f4de6037d
--- /dev/null
+++ b/netwerk/test/unit/test_bug261425.js
@@ -0,0 +1,26 @@
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var newURI = ios.newURI("http://foo.com", null, null);
+
+ var success = false;
+ try {
+ newURI.spec = "http: //foo.com";
+ }
+ 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.host = " foo.com";
+ }
+ 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..8262c07e84
--- /dev/null
+++ b/netwerk/test/unit/test_bug263127.js
@@ -0,0 +1,61 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var server;
+const BUGID = "263127";
+
+var listener = {
+ QueryInterface: function(iid) {
+ if (!iid.equals(nsIDownloadObserver) &&
+ !iid.equals(nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+
+ onDownloadComplete: function(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);
+ }
+
+ do_check_false(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.asyncOpen2(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..f8da54356a
--- /dev/null
+++ b/netwerk/test/unit/test_bug282432.js
@@ -0,0 +1,42 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test() {
+ do_test_pending();
+
+ function StreamListener() {}
+
+ StreamListener.prototype = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIStreamListener) ||
+ aIID.equals(Components.interfaces.nsIRequestObserver) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ onStartRequest: function(aRequest, aContext) {},
+
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ // Make sure we can catch the error NS_ERROR_FILE_NOT_FOUND here.
+ do_check_eq(aStatusCode, Components.results.NS_ERROR_FILE_NOT_FOUND);
+ do_test_finished();
+ },
+
+ onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
+ do_throw("The channel must not call onDataAvailable().");
+ }
+ };
+
+ let listener = new StreamListener();
+ let ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ // This file does not exist.
+ let file = do_get_file("_NOT_EXIST_.txt", true);
+ do_check_false(file.exists());
+ let channel = NetUtil.newChannel({
+ uri: ios.newFileURI(file),
+ loadUsingSystemPrincipal: true
+ });
+ channel.asyncOpen2(listener);
+}
diff --git a/netwerk/test/unit/test_bug321706.js b/netwerk/test/unit/test_bug321706.js
new file mode 100644
index 0000000000..8ddbce6e75
--- /dev/null
+++ b/netwerk/test/unit/test_bug321706.js
@@ -0,0 +1,11 @@
+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, null, null);
+ do_check_eq(newURI.spec, url);
+ do_check_eq(newURI.path, "/folder/file?/.");
+ do_check_eq(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..0533e9bc5f
--- /dev/null
+++ b/netwerk/test/unit/test_bug331825.js
@@ -0,0 +1,42 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var server;
+const BUGID = "331825";
+
+function TestListener() {
+}
+TestListener.prototype.onStartRequest = function(request, context) {
+}
+TestListener.prototype.onStopRequest = function(request, context, status) {
+ var channel = request.QueryInterface(Components.interfaces.nsIHttpChannel);
+ do_check_eq(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(Components.interfaces.nsIHttpChannel);
+ channel.setRequestHeader("If-None-Match", "foobar", false);
+ channel.asyncOpen2(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..c27aff5e9b
--- /dev/null
+++ b/netwerk/test/unit/test_bug336501.js
@@ -0,0 +1,27 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+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);
+ do_check_eq(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..bcf8cdb749
--- /dev/null
+++ b/netwerk/test/unit/test_bug337744.js
@@ -0,0 +1,114 @@
+/* verify that certain invalid URIs are not parsed by the resource
+ protocol handler */
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+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", null, null);
+var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+
+function get_channel(spec)
+{
+ var channelURI = NetUtil.newURI(spec, null, null);
+
+ var channel = NetUtil.newChannel({
+ uri: NetUtil.newURI(spec, null, null),
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ });
+
+ try {
+ channel.asyncOpen2(null);
+ ok(false, "asyncOpen2() of URI: " + spec + "should throw");
+ }
+ catch (e) {
+ // make sure we get the right error code in the exception
+ // ERROR code for NS_ERROR_DOM_BAD_URI is 1012
+ equal(e.code, 1012);
+ }
+
+ try {
+ channel.open2();
+ ok(false, "Open2() of uri: " + spec + "should throw");
+ }
+ catch (e) {
+ // make sure we get the right error code in the exception
+ // ERROR code for NS_ERROR_DOM_BAD_URI is 1012
+ equal(e.code, 1012);
+ }
+
+ return channel;
+}
+
+function check_safe_resolution(spec, rootURI)
+{
+ do_print(`Testing URL "${spec}"`);
+
+ let channel = get_channel(spec);
+
+ ok(channel.name.startsWith(rootURI), `URL resolved safely to ${channel.name}`);
+ ok(!/%2f/i.test(channel.name), `URL contains no escaped / characters`);
+}
+
+function check_resolution_error(spec)
+{
+ try {
+ get_channel(spec);
+ ok(false, "Expected an error");
+ } catch (e) {
+ equal(e.result, Components.results.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);
+
+ resProto.setSubstitution("res-test", rootURI);
+ do_register_cleanup(() => {
+ resProto.setSubstitution("res-test", null);
+ });
+
+ let baseRoot = resProto.resolveURI(Services.io.newURI("resource:///", null, null));
+ let greRoot = resProto.resolveURI(Services.io.newURI("resource://gre/", null, null));
+
+ for (var spec of specs) {
+ check_safe_resolution(spec, rootURI.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..d905f8ae4d
--- /dev/null
+++ b/netwerk/test/unit/test_bug365133.js
@@ -0,0 +1,111 @@
+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 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 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 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 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 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 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 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 FILE \n"
+ ]
+]
+
+function checkData(request, data, ctx) {
+ do_check_eq(tests[0][1], data);
+ tests.shift();
+ do_execute_soon(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 = {
+ password: "",
+ asciiSpec: URL,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
+ };
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending: function() {
+ return this.pending;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, null, 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() {
+ do_execute_soon(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..8f511e5595
--- /dev/null
+++ b/netwerk/test/unit/test_bug368702.js
@@ -0,0 +1,150 @@
+function run_test() {
+ var tld =
+ Cc["@mozilla.org/network/effective-tld-service;1"].
+ getService(Ci.nsIEffectiveTLDService);
+
+ var etld;
+
+ do_check_eq(tld.getPublicSuffixFromHost("localhost"), "localhost");
+ do_check_eq(tld.getPublicSuffixFromHost("localhost."), "localhost.");
+ do_check_eq(tld.getPublicSuffixFromHost("domain.com"), "com");
+ do_check_eq(tld.getPublicSuffixFromHost("domain.com."), "com.");
+ do_check_eq(tld.getPublicSuffixFromHost("domain.co.uk"), "co.uk");
+ do_check_eq(tld.getPublicSuffixFromHost("domain.co.uk."), "co.uk.");
+ do_check_eq(tld.getPublicSuffixFromHost("co.uk"), "co.uk");
+ do_check_eq(tld.getBaseDomainFromHost("domain.co.uk"), "domain.co.uk");
+ do_check_eq(tld.getBaseDomainFromHost("domain.co.uk."), "domain.co.uk.");
+
+ try {
+ etld = tld.getPublicSuffixFromHost("");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("domain.co.uk", 1);
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("co.uk");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("1.2.3.4");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("2010:836B:4179::836B:4179");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("3232235878");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("::ffff:192.9.5.5");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("::1");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(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 {
+ etld = tld.getPublicSuffixFromHost("127.0.0.1.");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_HOST_IS_IP_ADDRESS);
+ }
+
+ try {
+ etld = tld.getPublicSuffixFromHost("::ffff:127.0.0.1.");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(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 = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ var uri = ioService.newURI("http://b\u00FCcher.co.uk", null, null);
+ do_check_eq(tld.getBaseDomain(uri), "xn--bcher-kva.co.uk");
+ do_check_eq(tld.getBaseDomainFromHost("b\u00FCcher.co.uk"), "xn--bcher-kva.co.uk");
+ do_check_eq(tld.getPublicSuffix(uri), "co.uk");
+ do_check_eq(tld.getPublicSuffixFromHost("b\u00FCcher.co.uk"), "co.uk");
+
+ // check that malformed hosts are rejected as invalid args
+ try {
+ etld = tld.getBaseDomainFromHost("domain.co.uk..");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("domain.co..uk");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost(".domain.co.uk");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost(".domain.co.uk");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost(".");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ try {
+ etld = tld.getBaseDomainFromHost("..");
+ do_throw("this should fail");
+ } catch(e) {
+ do_check_eq(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..d59bef005c
--- /dev/null
+++ b/netwerk/test/unit/test_bug369787.js
@@ -0,0 +1,71 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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;
+ do_check_eq(channel.contentType, newType);
+ channel.contentType = origType;
+ do_check_eq(channel.contentType, origType);
+}
+
+function TestListener() {
+}
+TestListener.prototype.onStartRequest = function(request, context) {
+ try {
+ // request might be different from channel
+ channel = request.QueryInterface(Components.interfaces.nsIChannel);
+
+ change_content_type();
+ } catch (ex) {
+ print(ex);
+ throw ex;
+ }
+}
+TestListener.prototype.onStopRequest = function(request, context, 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(Components.interfaces.nsIHttpChannel);
+ channel.asyncOpen2(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..75752d3836
--- /dev/null
+++ b/netwerk/test/unit/test_bug371473.js
@@ -0,0 +1,44 @@
+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 {
+ var newURI = ios.newURI(spec, null, null);
+ }
+ 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.
+ var newURI = ios.newURI(spec, null, null);
+ }
+ catch (e) {
+ return;
+ }
+}
+
+function run_test() {
+ test_not_too_long();
+ test_too_long();
+}
diff --git a/netwerk/test/unit/test_bug376660.js b/netwerk/test/unit/test_bug376660.js
new file mode 100644
index 0000000000..8208eef6a6
--- /dev/null
+++ b/netwerk/test/unit/test_bug376660.js
@@ -0,0 +1,72 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var listener = {
+ expect_failure: false,
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIUnicharStreamLoaderObserver)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onDetermineCharset : function onDetermineCharset(loader, context, data)
+ {
+ return "us-ascii";
+ },
+ onStreamComplete : function onStreamComplete (loader, context, status, data)
+ {
+ try {
+ if (this.expect_failure)
+ do_check_false(Components.isSuccessCode(status));
+ else
+ do_check_eq(status, Components.results.NS_OK);
+ do_check_eq(data, "");
+ do_check_neq(loader.channel, null);
+ tests[current_test++]();
+ } finally {
+ do_test_finished();
+ }
+ }
+};
+
+var current_test = 0;
+var tests = [test1, test2, done];
+
+function run_test() {
+ tests[current_test++]();
+}
+
+function test1() {
+ var f =
+ Cc["@mozilla.org/network/unichar-stream-loader;1"].
+ createInstance(Ci.nsIUnicharStreamLoader);
+ f.init(listener);
+
+ var chan = NetUtil.newChannel({
+ uri: "data:text/plain,",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(f);
+ do_test_pending();
+}
+
+function test2() {
+ var f =
+ Cc["@mozilla.org/network/unichar-stream-loader;1"].
+ createInstance(Ci.nsIUnicharStreamLoader);
+ f.init(listener);
+
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:0/",
+ loadUsingSystemPrincipal: true
+ });
+ listener.expect_failure = true;
+ chan.asyncOpen2(f);
+ do_test_pending();
+}
+
+function done() {
+}
diff --git a/netwerk/test/unit/test_bug376844.js b/netwerk/test/unit/test_bug376844.js
new file mode 100644
index 0000000000..9a21b0171e
--- /dev/null
+++ b/netwerk/test/unit/test_bug376844.js
@@ -0,0 +1,21 @@
+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], null, null);
+ do_check_eq(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..a068c555a3
--- /dev/null
+++ b/netwerk/test/unit/test_bug376865.js
@@ -0,0 +1,20 @@
+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, -1, -1, 0, 0, false);
+
+ // When we pass a null listener argument too asyncRead we expect it to throw
+ // instead of crashing.
+ try {
+ pump.asyncRead(null, 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..0177e904c5
--- /dev/null
+++ b/netwerk/test/unit/test_bug379034.js
@@ -0,0 +1,18 @@
+function run_test() {
+ const ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ var base = ios.newURI("http://localhost/bug379034/index.html", null, null);
+
+ var uri = ios.newURI("http:a.html", null, base);
+ do_check_eq(uri.spec, "http://localhost/bug379034/a.html");
+
+ uri = ios.newURI("HtTp:b.html", null, base);
+ do_check_eq(uri.spec, "http://localhost/bug379034/b.html");
+
+ uri = ios.newURI("https:c.html", null, base);
+ do_check_eq(uri.spec, "https://c.html/");
+
+ uri = ios.newURI("./https:d.html", null, base);
+ do_check_eq(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..b9b9d5bd60
--- /dev/null
+++ b/netwerk/test/unit/test_bug380994.js
@@ -0,0 +1,22 @@
+/* check resource: protocol for traversal problems */
+
+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, null, null);
+ if (uri.spec.indexOf("..") != -1)
+ 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..112678d00d
--- /dev/null
+++ b/netwerk/test/unit/test_bug388281.js
@@ -0,0 +1,24 @@
+function run_test() {
+ const ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var uri = ios.newURI("http://foo.com/file.txt", null, null);
+ uri.port = 90;
+ do_check_eq(uri.hostPort, "foo.com:90");
+
+ uri = ios.newURI("http://foo.com:10/file.txt", null, null);
+ uri.port = 500;
+ do_check_eq(uri.hostPort, "foo.com:500");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ uri.port = 20;
+ do_check_eq(uri.hostPort, "foo.com:20");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ uri.port = -1;
+ do_check_eq(uri.hostPort, "foo.com");
+
+ uri = ios.newURI("http://foo.com:5000/file.txt", null, null);
+ uri.port = 80;
+ do_check_eq(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..0bcfa83624
--- /dev/null
+++ b/netwerk/test/unit/test_bug396389.js
@@ -0,0 +1,71 @@
+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:///", null, null);
+ do_check_true(uri1 instanceof Ci.nsIFileURL);
+
+ var uri2 = uri1.clone();
+ do_check_true(uri2 instanceof Ci.nsIFileURL);
+ do_check_true(uri1.equals(uri2));
+
+ var uri3 = round_trip(uri1);
+ do_check_true(uri3 instanceof Ci.nsIFileURL);
+ do_check_true(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) {
+ try {
+ pref.oldVal = prefs.getBoolPref(pref.name);
+ } catch(e) {
+ }
+ 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/", null, null);
+ do_check_eq(uri4.asciiHost, "xn--jos-dma.example.net.ch");
+ do_check_eq(uri4.host, "jos\u00e9.example.net.ch");
+
+ var uri5 = round_trip(uri4);
+ do_check_true(uri4.equals(uri5));
+ do_check_eq(uri4.host, uri5.host);
+ do_check_eq(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..e7643fa9de
--- /dev/null
+++ b/netwerk/test/unit/test_bug401564.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+"use strict";
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(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(Components.interfaces.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.asyncOpen2(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..9ac9d1d745
--- /dev/null
+++ b/netwerk/test/unit/test_bug411952.js
@@ -0,0 +1,35 @@
+function run_test() {
+ try {
+ var cm = Cc["@mozilla.org/cookiemanager;1"].
+ getService(Ci.nsICookieManager2);
+ do_check_neq(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, {});
+ const now = Math.floor((new Date()).getTime() / 1000);
+
+ var enumerator = cm.enumerator, found = false;
+ while (enumerator.hasMoreElements()) {
+ var cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ if (cookie.host == "example.com" &&
+ cookie.path == "/" &&
+ cookie.name == "C") {
+ do_check_true("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.
+ do_check_true(Math.abs(creationTime - now) <= 1,
+ "Cookie's creationTime is set incorrectly");
+ found = true;
+ break;
+ }
+ }
+
+ do_check_true(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..dd4358413e
--- /dev/null
+++ b/netwerk/test/unit/test_bug412457.js
@@ -0,0 +1,44 @@
+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/", null, null);
+ do_check_eq(newURI.asciiHost, "xn--kkr.com");
+
+ // escaped UTF8
+ newURI.spec = "http://%e5%8d%81.com";
+ do_check_eq(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.spec = "http://%80.com"; }, "illegal UTF character");
+
+ // test parsing URL with all possible host terminators
+ newURI.spec = "http://example.com?foo";
+ do_check_eq(newURI.asciiHost, "example.com");
+
+ newURI.spec = "http://example.com#foo";
+ do_check_eq(newURI.asciiHost, "example.com");
+
+ newURI.spec = "http://example.com:80";
+ do_check_eq(newURI.asciiHost, "example.com");
+
+ newURI.spec = "http://example.com/foo";
+ do_check_eq(newURI.asciiHost, "example.com");
+
+ // Characters that are invalid in the host, shouldn't be decoded.
+ newURI.spec = "http://example.com%3ffoo";
+ do_check_eq(newURI.asciiHost, "example.com%3ffoo");
+ newURI.spec = "http://example.com%23foo";
+ do_check_eq(newURI.asciiHost, "example.com%23foo");
+ newURI.spec = "http://example.com%3bfoo";
+ do_check_eq(newURI.asciiHost, "example.com%3bfoo");
+ newURI.spec = "http://example.com%3a80";
+ do_check_eq(newURI.asciiHost, "example.com%3a80");
+ newURI.spec = "http://example.com%2ffoo";
+ do_check_eq(newURI.asciiHost, "example.com%2ffoo");
+ newURI.spec = "http://example.com%00";
+ do_check_eq(newURI.asciiHost, "example.com%00");
+} \ No newline at end of file
diff --git a/netwerk/test/unit/test_bug412945.js b/netwerk/test/unit/test_bug412945.js
new file mode 100644
index 0000000000..e8b39774b3
--- /dev/null
+++ b/netwerk/test/unit/test_bug412945.js
@@ -0,0 +1,42 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv;
+
+function TestListener() {
+}
+
+TestListener.prototype.onStartRequest = function(request, context) {
+}
+
+TestListener.prototype.onStopRequest = function(request, context, 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(Components.interfaces.nsIHttpChannel);
+ channel.requestMethod = "POST";
+ channel.asyncOpen2(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..7e5a418d01
--- /dev/null
+++ b/netwerk/test/unit/test_bug414122.js
@@ -0,0 +1,58 @@
+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);
+ do_check_eq(actualSuffix, expectedSuffix);
+}
diff --git a/netwerk/test/unit/test_bug427957.js b/netwerk/test/unit/test_bug427957.js
new file mode 100644
index 0000000000..e869725a40
--- /dev/null
+++ b/netwerk/test/unit/test_bug427957.js
@@ -0,0 +1,106 @@
+/**
+ * Test for Bidi restrictions on IDNs from RFC 3454
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var idnService;
+
+function expected_pass(inputIDN)
+{
+ var isASCII = {};
+ var displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ do_check_eq(displayIDN, inputIDN);
+}
+
+function expected_fail(inputIDN)
+{
+ var isASCII = {};
+ var displayIDN = "";
+
+ try {
+ displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ }
+ catch(e) {}
+
+ do_check_neq(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\200ebar.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..a6f1452c25
--- /dev/null
+++ b/netwerk/test/unit/test_bug429347.js
@@ -0,0 +1,38 @@
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var uri1 = ios.newURI("http://example.com#bar", null, null);
+ var uri2 = ios.newURI("http://example.com/#bar", null, null);
+ do_check_true(uri1.equals(uri2));
+
+ uri1.spec = "http://example.com?bar";
+ uri2.spec = "http://example.com/?bar";
+ do_check_true(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.spec = "http://example.com;bar";
+ uri2.spec = "http://example.com/;bar";
+ do_check_false(uri1.equals(uri2));
+
+ uri1.spec = "http://example.com#";
+ uri2.spec = "http://example.com/#";
+ do_check_true(uri1.equals(uri2));
+
+ uri1.spec = "http://example.com?";
+ uri2.spec = "http://example.com/?";
+ do_check_true(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.spec = "http://example.com;";
+ uri2.spec = "http://example.com/;";
+ do_check_false(uri1.equals(uri2));
+
+ uri1.spec = "http://example.com";
+ uri2.spec = "http://example.com/";
+ do_check_true(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..564831df3b
--- /dev/null
+++ b/netwerk/test/unit/test_bug455311.js
@@ -0,0 +1,128 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function getLinkFile()
+{
+ 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);
+var link;
+var linkURI;
+const newURI = ios.newURI("http://www.mozilla.org/", null, null);
+
+function NotificationCallbacks(origURI, newURI)
+{
+ this._origURI = origURI;
+ this._newURI = newURI;
+}
+NotificationCallbacks.prototype = {
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIInterfaceRequestor) ||
+ iid.equals(Ci.nsIChannelEventSink)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ getInterface: function (iid)
+ {
+ return this.QueryInterface(iid);
+ },
+ asyncOnChannelRedirect: function(oldChan, newChan, flags, callback)
+ {
+ do_check_eq(oldChan.URI.spec, this._origURI.spec);
+ do_check_eq(oldChan.URI, this._origURI);
+ do_check_eq(oldChan.originalURI.spec, this._origURI.spec);
+ do_check_eq(oldChan.originalURI, this._origURI);
+ do_check_eq(newChan.originalURI.spec, this._newURI.spec);
+ do_check_eq(newChan.originalURI, newChan.URI);
+ do_check_eq(newChan.URI.spec, this._newURI.spec);
+ throw Cr.NS_ERROR_ABORT;
+ }
+};
+
+function RequestObserver(origURI, newURI, nextTest)
+{
+ this._origURI = origURI;
+ this._newURI = newURI;
+ this._nextTest = nextTest;
+}
+RequestObserver.prototype = {
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsIStreamListener)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ onStartRequest: function (req, ctx)
+ {
+ var chan = req.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.URI.spec, this._origURI.spec);
+ do_check_eq(chan.URI, this._origURI);
+ do_check_eq(chan.originalURI.spec, this._origURI.spec);
+ do_check_eq(chan.originalURI, this._origURI);
+ },
+ onDataAvailable: function(req, ctx, stream, offset, count)
+ {
+ do_throw("Unexpected call to onDataAvailable");
+ },
+ onStopRequest: function (req, ctx, status)
+ {
+ var chan = req.QueryInterface(Ci.nsIChannel);
+ try {
+ do_check_eq(chan.URI.spec, this._origURI.spec);
+ do_check_eq(chan.URI, this._origURI);
+ do_check_eq(chan.originalURI.spec, this._origURI.spec);
+ do_check_eq(chan.originalURI, this._origURI);
+ do_check_eq(status, Cr.NS_ERROR_ABORT);
+ do_check_false(chan.isPending());
+ } catch(e) {}
+ this._nextTest();
+ }
+};
+
+function test_cancel()
+{
+ var chan = NetUtil.newChannel({
+ uri: linkURI,
+ loadUsingSystemPrincipal: true
+ });
+ do_check_eq(chan.URI, linkURI);
+ do_check_eq(chan.originalURI, linkURI);
+ chan.asyncOpen2(new RequestObserver(linkURI, newURI, do_test_finished));
+ do_check_true(chan.isPending());
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ do_check_true(chan.isPending());
+}
+
+function run_test()
+{
+ if (mozinfo.os != "win" && mozinfo.os != "linux") {
+ return;
+ }
+
+ link = getLinkFile();
+ linkURI = ios.newFileURI(link);
+
+ do_test_pending();
+ var chan = NetUtil.newChannel({
+ uri: linkURI,
+ loadUsingSystemPrincipal: true
+ });
+ do_check_eq(chan.URI, linkURI);
+ do_check_eq(chan.originalURI, linkURI);
+ chan.notificationCallbacks = new NotificationCallbacks(linkURI, newURI);
+ chan.asyncOpen2(new RequestObserver(linkURI, newURI, test_cancel));
+ do_check_true(chan.isPending());
+}
diff --git a/netwerk/test/unit/test_bug455598.js b/netwerk/test/unit/test_bug455598.js
new file mode 100644
index 0000000000..d0f9e459ec
--- /dev/null
+++ b/netwerk/test/unit/test_bug455598.js
@@ -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/. */
+
+function run_test() {
+ const time = (new Date("Jan 1, 2030")).getTime() / 1000;
+ var cookie = {
+ name: "foo",
+ value: "bar",
+ isDomain: true,
+ host: "example.com",
+ path: "/baz",
+ isSecure: false,
+ expires: time,
+ status: 0,
+ policy: 0,
+ isSession: false,
+ expiry: time,
+ isHttpOnly: true,
+ QueryInterface: function(iid) {
+ const validIIDs = [Components.interfaces.nsISupports,
+ Components.interfaces.nsICookie,
+ Components.interfaces.nsICookie2];
+ for (var i = 0; i < validIIDs.length; ++i)
+ if (iid == validIIDs[i])
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ };
+ var cm = Components.classes["@mozilla.org/cookiemanager;1"].
+ getService(Components.interfaces.nsICookieManager2);
+ do_check_false(cm.cookieExists(cookie));
+ // if the above line does not crash, the test was successful
+ do_test_finished();
+}
diff --git a/netwerk/test/unit/test_bug464591.js b/netwerk/test/unit/test_bug464591.js
new file mode 100644
index 0000000000..34d7c46b52
--- /dev/null
+++ b/netwerk/test/unit/test_bug464591.js
@@ -0,0 +1,81 @@
+
+const StandardURL = Components.Constructor("@mozilla.org/network/standard-url;1",
+ "nsIStandardURL",
+ "init");
+
+ // 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
+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"],
+ ["www.mozill%61%2f.org", "www.mozilla%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",
+ "www.e%00xample.xn--comwww-re3c.xn--mozill-8nf.xn--comwww-rq0c.mozilla.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.blacklist_chars",
+ minimumList: "\u2215\u0430\u2044",
+ };
+
+function stringToURL(str) {
+ return (new StandardURL(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 80,
+ str, "UTF-8", null))
+ .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) {
+ }
+
+ do_register_cleanup(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]);
+ }
+ }
+}
diff --git a/netwerk/test/unit/test_bug468426.js b/netwerk/test/unit/test_bug468426.js
new file mode 100644
index 0000000000..95ce32674d
--- /dev/null
+++ b/netwerk/test/unit/test_bug468426.js
@@ -0,0 +1,100 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.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.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ do_check_eq(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..66d463190e
--- /dev/null
+++ b/netwerk/test/unit/test_bug468594.js
@@ -0,0 +1,127 @@
+//
+// 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.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ httpChan.setRequestHeader("x-request", value, false);
+ return httpChan;
+}
+
+function triggerNextTest() {
+ var channel = setupChannel(tests[index].url, tests[index].server);
+ channel.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data);
+ do_check_eq(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..7d2ab4bc2b
--- /dev/null
+++ b/netwerk/test/unit/test_bug470716.js
@@ -0,0 +1,174 @@
+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: function(request, context) { },
+
+ onStopRequest: function(request, cx, statusCode)
+ {
+ // check status code
+ do_check_eq(statusCode, test_result);
+
+ // check number of copied bytes
+ do_check_eq(pipe2.inputStream.available(), test_content.length);
+
+ // check content
+ var scinp = new ScriptableInputStream(pipe2.inputStream);
+ var content = scinp.read(scinp.available());
+ do_check_eq(content, test_content);
+
+ // check closed sink
+ try {
+ pipe2.outputStream.write("closedSinkTest", 14);
+ do_check_false(test_sink_closed);
+ }
+ catch (ex) {
+ do_check_true(test_sink_closed);
+ }
+
+ // check closed source
+ try {
+ pipe1.outputStream.write("closedSourceTest", 16);
+ do_check_false(test_source_closed);
+ }
+ catch (ex) {
+ do_check_true(test_source_closed);
+ }
+
+ do_timeout(0, do_test);
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+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..942ab1e005
--- /dev/null
+++ b/netwerk/test/unit/test_bug477578.js
@@ -0,0 +1,50 @@
+// test that methods are not normalized
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.nsIHttpChannel);
+
+ for (var i = 0; i < testMethods.length; i++) {
+ chan.requestMethod = testMethods[i];
+ do_check_eq(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..1a5e3335be
--- /dev/null
+++ b/netwerk/test/unit/test_bug479413.js
@@ -0,0 +1,59 @@
+/**
+ * Test for unassigned code points in IDNs (RFC 3454 section 7)
+ */
+
+var idnService;
+
+function expected_pass(inputIDN)
+{
+ var isASCII = {};
+ var displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ do_check_eq(displayIDN, inputIDN);
+}
+
+function expected_fail(inputIDN)
+{
+ var isASCII = {};
+ var displayIDN = "";
+
+ try {
+ displayIDN = idnService.convertToDisplayIDN(inputIDN, isASCII);
+ }
+ catch(e) {}
+
+ do_check_neq(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..05f0bc4f0b
--- /dev/null
+++ b/netwerk/test/unit/test_bug479485.js
@@ -0,0 +1,47 @@
+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, null, null);
+ }
+ 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");
+ do_check_eq(exception_threw, exception_expected);
+
+ exception_threw = false;
+ newURI = ios.newURI("http://foo.com", null, null);
+ try {
+ newURI.spec = "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 setting a spec of a URI with "+port+" as a port");
+ do_check_eq(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..fde70f005e
--- /dev/null
+++ b/netwerk/test/unit/test_bug482601.js
@@ -0,0 +1,233 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv = null;
+var test_nr = 0;
+var observers_called = "";
+var handlers_called = "";
+var buffer = "";
+
+var observer = {
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Ci.nsISupports) ||
+ aIID.equals(Ci.nsIObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data) {
+ if (observers_called.length)
+ observers_called += ",";
+
+ observers_called += topic;
+ }
+};
+
+var listener = {
+ onStartRequest: function (request, ctx) {
+ buffer = "";
+ },
+
+ onDataAvailable: function (request, ctx, stream, offset, count) {
+ buffer = buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest: function (request, ctx, status) {
+ do_check_eq(status, Cr.NS_OK);
+ do_check_eq(buffer, "0123456789");
+ do_check_eq(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", false);
+ obs.addObserver(observer, "http-on-examine-merged-response", false);
+ obs.addObserver(observer, "http-on-examine-cached-response", false);
+
+ do_timeout(0, do_test);
+ do_test_pending();
+}
+
+function do_test() {
+ if (test_nr < tests.length) {
+ tests[test_nr]();
+ }
+ else {
+ do_check_eq(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);
+ 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.asyncOpen2(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) {
+ do_check_eq(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.asyncOpen2(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) {
+ do_check_eq(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.asyncOpen2(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) {
+ do_check_eq(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.asyncOpen2(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) {
+ do_check_true(metadata.hasHeader("If-Range"));
+ do_check_eq(metadata.getHeader("If-Range"),
+ "Thu, 1 Jan 2009 00:00:00 GMT");
+ do_check_true(metadata.hasHeader("Range"));
+ do_check_eq(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) {
+ do_check_true(metadata.hasHeader("If-Modified-Since"));
+ do_check_eq(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_bug484684.js b/netwerk/test/unit/test_bug484684.js
new file mode 100644
index 0000000000..259d8cec1b
--- /dev/null
+++ b/netwerk/test/unit/test_bug484684.js
@@ -0,0 +1,115 @@
+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 FILE \n" +
+ "201: \"%20file2\" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 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 FILE \n" +
+ "201: \"file2\" 214059 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 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 FILE \n" +
+ "201: \"%20file2\" 192 Sat%2C%2001%20Jan%202000%2000%3A00%3A00 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 DIRECTORY \n" +
+ "201: \"junction1\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"file1\" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 FILE \n" +
+ "201: \"%20dir2\" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 DIRECTORY \n" +
+ "201: \"%20junction2\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"%20file2\" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 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 DIRECTORY \n" +
+ "201: \"dir2\" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 DIRECTORY \n" +
+ "201: \"dir3\" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 DIRECTORY \n" +
+ "201: \"junction1\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"junction2\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"junction3\" Sat%2C%2001%20Jan%202000%2001%3A00%3A00 SYMBOLIC-LINK \n" +
+ "201: \"file1\" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 FILE \n" +
+ "201: \"file2\" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00 FILE \n"]
+]
+
+function checkData(request, data, ctx) {
+ do_check_eq(tests[0][1], data);
+ tests.shift();
+ do_execute_soon(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 = {
+ password: "",
+ asciiSpec: URL,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
+ };
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending: function() {
+ return this.pending;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, null, 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() {
+ do_execute_soon(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..8b588b1a8a
--- /dev/null
+++ b/netwerk/test/unit/test_bug490095.js
@@ -0,0 +1,116 @@
+//
+// Verify that the VALIDATE_NEVER and LOAD_FROM_CACHE flags override
+// heuristic query freshness as defined in RFC 2616 section 13.9
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: Components.interfaces.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: Components.interfaces.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(Components.interfaces.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.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ logit(index, data);
+ do_check_eq(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..5c28dfa326
--- /dev/null
+++ b/netwerk/test/unit/test_bug504014.js
@@ -0,0 +1,69 @@
+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 {
+ var uri = ios.newURI(valid_URIs[i], null, null);
+ } catch (e) {
+ do_throw("cannot create URI:" + valid_URIs[i]);
+ }
+ }
+
+ for (var i=0 ; i<invalid_URIs.length ; i++) {
+ try {
+ var uri = ios.newURI(invalid_URIs[i], null, null);
+ do_throw("should throw: " + invalid_URIs[i]);
+ } catch (e) {
+ do_check_eq(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..176b5ed378
--- /dev/null
+++ b/netwerk/test/unit/test_bug510359.js
@@ -0,0 +1,77 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.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.asyncOpen2(new ChannelListener(checkValueAndTrigger, null));
+}
+
+function checkValueAndTrigger(request, data, ctx) {
+ do_check_eq(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 {
+ var IMS = 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..28e43a3de9
--- /dev/null
+++ b/netwerk/test/unit/test_bug515583.js
@@ -0,0 +1,73 @@
+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 FILE \n" +
+ "201: \"E\" 2048 Mon%2C%2008%20Mar%201993%2018%3A09%3A01 FILE \n"]
+ ,
+ ["\r\r\r\n"
+ ,
+ "300: " + URL + "\n" +
+ "200: filename content-length last-modified file-type\n"]
+]
+
+function checkData(request, data, ctx) {
+ do_check_eq(tests[0][1], data);
+ tests.shift();
+ do_execute_soon(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 = {
+ password: "",
+ asciiSpec: URL,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
+ };
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending: function() {
+ return this.pending;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, null, 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() {
+ do_execute_soon(next_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug528292.js b/netwerk/test/unit/test_bug528292.js
new file mode 100644
index 0000000000..ef030baeeb
--- /dev/null
+++ b/netwerk/test/unit/test_bug528292.js
@@ -0,0 +1,90 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+ return;
+}
+
+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;
+}
+
+function run_test()
+{
+ // 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);
+ }
+
+ 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 (since nsICookieService::setCookieString
+ // requires such a channel in order to successfully 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, "", null);
+ Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService).
+ setCookieString(postRedirectURI, null, sentCookieVal, chan);
+
+ // Load the pre-redirect URI.
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+ do_test_pending();
+}
+
+function finish_test(event)
+{
+ do_check_eq(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..1dcb475be7
--- /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 */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// 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: function (req, ctx) {
+ },
+
+ onDataAvailable: function (req, ctx, stream, off, count) {
+ do_check_eq(req.getResponseHeader("Content-Length"), CONTENT_LENGTH);
+
+ // We're done here, cancel the channel
+ req.cancel(NS_BINDING_ABORT);
+ },
+
+ onStopRequest: function (req, ctx, 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.asyncOpen2(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..e44fa9c170
--- /dev/null
+++ b/netwerk/test/unit/test_bug540566.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function continue_test(status, entry) {
+ do_check_eq(status, Components.results.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..ed25f3ebed
--- /dev/null
+++ b/netwerk/test/unit/test_bug543805.js
@@ -0,0 +1,93 @@
+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 FILE \n" +
+ "201: \"%20test.blankfile\" 22 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"%20test2.blankfile\" 33 Tue%2C%2001%20Apr%202008%2000%3A00%3A00 FILE \n" +
+ "201: \"nodup.file\" 44 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"test.file\" 55 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"test2.file\" 66 Tue%2C%2001%20Apr%202008%2000%3A00%3A00 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 FILE \n" +
+ "201: \"%20test.blankfile\" 22 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"%20test2.blankfile\" 33 Tue%2C%2001%20Apr%202008%2000%3A00%3A00 FILE \n" +
+ "201: \"nodup.file\" 44 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"test.file\" 55 " + day + "%2C%2001%20Jan%20" + year + "%2020%3A19%3A00 FILE \n" +
+ "201: \"test2.file\" 66 Tue%2C%2001%20Apr%202008%2000%3A00%3A00 FILE \n"]
+]
+
+function checkData(request, data, ctx) {
+ do_check_eq(tests[0][1], data);
+ tests.shift();
+ do_execute_soon(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 = {
+ password: "",
+ asciiSpec: URL,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
+ };
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending: function() {
+ return this.pending;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel])
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, null, 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() {
+ do_execute_soon(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..00e222a2c0
--- /dev/null
+++ b/netwerk/test/unit/test_bug553970.js
@@ -0,0 +1,44 @@
+function makeURL(spec) {
+ return Cc["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService).
+ newURI(spec, null, null).
+ QueryInterface(Components.interfaces.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];
+ do_check_eq(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..d794aeaf6d
--- /dev/null
+++ b/netwerk/test/unit/test_bug561042.js
@@ -0,0 +1,38 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function (request, ctx) {
+ },
+
+ onDataAvailable: function (request, ctx, stream) {
+ },
+
+ onStopRequest: function (request, ctx, status) {
+ do_check_eq(status, Components.results.NS_OK);
+ do_test_finished();
+ },
+
+};
+
+function run_test() {
+ var server = new HttpServer();
+ 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(Components.interfaces.nsIHttpChannel);
+ chan.asyncOpen2(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..fd67d24dc6
--- /dev/null
+++ b/netwerk/test/unit/test_bug561276.js
@@ -0,0 +1,68 @@
+//
+// Verify that we hit the net if we discover a cycle of redirects
+// coming from cache.
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.nsIHttpChannel);
+ httpChan.requestMethod = "GET";
+ return httpChan;
+}
+
+function checkValueAndTrigger(request, data, ctx)
+{
+ do_check_eq("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.asyncOpen2(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..3e2c495e0e
--- /dev/null
+++ b/netwerk/test/unit/test_bug580508.js
@@ -0,0 +1,26 @@
+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/", null, null);
+ 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");
+ do_check_eq(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",
+ null, null);
+ let myNetUtilURI = ioService.newURI("resource://my-gre-modules/NetUtil.jsm",
+ null, null);
+ do_check_eq(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..008577da06
--- /dev/null
+++ b/netwerk/test/unit/test_bug586908.js
@@ -0,0 +1,92 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.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: function (iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsISystemProxySettings))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ mainThreadOnly: true,
+ PACURI: "http://localhost:" + httpserv.identity.primaryPort + "/redirect",
+ getProxyForURI: function(aURI) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ };
+});
+
+function checkValue(request, data, ctx) {
+ do_check_true(called);
+ do_check_eq("ok", data);
+ httpserv.stop(do_test_finished);
+}
+
+function makeChan(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Components.interfaces.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",
+ Components.interfaces.nsIProtocolProxyService.PROXYCONFIG_SYSTEM);
+
+ // clear cache
+ evict_cache_entries();
+
+ var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort +
+ "/target");
+ chan.asyncOpen2(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..e86bdf817b
--- /dev/null
+++ b/netwerk/test/unit/test_bug596443.js
@@ -0,0 +1,97 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.nsIHttpChannel);
+ httpChan.setRequestHeader("x-request", xRequest, false);
+
+ return httpChan;
+}
+
+function Listener(response) {
+ this._response = response;
+}
+Listener.prototype = {
+ _response: null,
+ _buffer: null,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function (request, ctx) {
+ this._buffer = "";
+ },
+ onDataAvailable: function (request, ctx, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+ onStopRequest: function (request, ctx, status) {
+ do_check_eq(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);
+
+ // make sure we have a profile so we can use the disk-cache
+ do_get_profile();
+
+ // clear cache
+ evict_cache_entries();
+
+ var ch0 = setupChannel("/bug596443", "Response0", Ci.nsIRequest.LOAD_BYPASS_CACHE);
+ ch0.asyncOpen2(new Listener("Response0"));
+
+ var ch1 = setupChannel("/bug596443", "Response1", Ci.nsIRequest.LOAD_BYPASS_CACHE);
+ ch1.asyncOpen2(new Listener("Response1"));
+
+ var ch2 = setupChannel("/bug596443", "Should not be used");
+ ch2.asyncOpen2(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..811608a619
--- /dev/null
+++ b/netwerk/test/unit/test_bug618835.js
@@ -0,0 +1,115 @@
+//
+// 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.
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function(request, context) { },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(1, numberOfCLHandlerCalls);
+ do_execute_soon(function() {
+ var channel = setupChannel("http://localhost:" +
+ httpserv.identity.primaryPort + "/post");
+ channel.requestMethod = "POST";
+ channel.asyncOpen2(new RedirectingListener());
+ });
+ }
+};
+
+// Verify that Location-URI has been loaded once, reload post_target
+function RedirectingListener() { }
+RedirectingListener.prototype = {
+ onStartRequest: function(request, context) { },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(1, numberOfHandlerCalls);
+ do_execute_soon(function() {
+ var channel = setupChannel("http://localhost:" +
+ httpserv.identity.primaryPort + "/post");
+ channel.requestMethod = "POST";
+ channel.asyncOpen2(new VerifyingListener());
+ });
+ }
+};
+
+// Verify that Location-URI has been loaded twice (cached entry invalidated),
+// reload Content-Location-URI
+function VerifyingListener() { }
+VerifyingListener.prototype = {
+ onStartRequest: function(request, context) { },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(2, numberOfHandlerCalls);
+ var channel = setupChannel("http://localhost:" +
+ httpserv.identity.primaryPort + "/cl");
+ channel.asyncOpen2(new FinalListener());
+ }
+};
+
+// Verify that Location-URI has been loaded twice (cached entry invalidated),
+// stop test
+function FinalListener() { }
+FinalListener.prototype = {
+ onStartRequest: function(request, context) { },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(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.asyncOpen2(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..e5ac078b4c
--- /dev/null
+++ b/netwerk/test/unit/test_bug633743.js
@@ -0,0 +1,186 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.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: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) { },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(this._buffer, this._expectVal);
+ do_timeout(0, run_next_test);
+ },
+
+ run: function() {
+ 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.asyncOpen2(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
+ do_check_false(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_bug650995.js b/netwerk/test/unit/test_bug650995.js
new file mode 100644
index 0000000000..3c1ea8d67e
--- /dev/null
+++ b/netwerk/test/unit/test_bug650995.js
@@ -0,0 +1,159 @@
+//
+// Test that "max_entry_size" prefs for disk- and memory-cache prevents
+// caching resources with size out of bounds
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.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;
+ }
+ do_execute_soon(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) {
+ try {
+ cap = prefService.getIntPref("browser.cache.memory.capacity");
+ }
+ catch(ex) {
+ cap = 0;
+ }
+ if (cap == 0) {
+ prefService.setIntPref("browser.cache.memory.capacity", 1024);
+ }
+ }
+ prefService.setBoolPref("browser.cache.disk.enable", diskDevice);
+ if (diskDevice) {
+ try {
+ cap = prefService.getIntPref("browser.cache.disk.capacity");
+ }
+ catch(ex) {
+ cap = 0;
+ }
+ if (cap == 0) {
+ prefService.setIntPref("browser.cache.disk.capacity", 1024);
+ }
+ }
+ var channel = setupChannel("/bug650995", "Initial value");
+ channel.asyncOpen2(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.asyncOpen2(new ChannelListener(this.initialLoad, this));
+ },
+
+ this.initialLoad = function(request, data, ctx) {
+ do_check_eq(firstRequest, data);
+ var channel = setupChannel("/bug650995", secondRequest);
+ do_execute_soon(function() {
+ channel.asyncOpen2(new ChannelListener(ctx.testAndTriggerNext, ctx));
+ });
+ },
+
+ this.testAndTriggerNext = function(request, data, ctx) {
+ do_check_eq(secondExpectedReply, data);
+ do_execute_soon(nextTest);
+ }
+}
+
+function run_test()
+{
+ httpserver.registerPathHandler("/bug650995", handler);
+ httpserver.start(-1);
+
+ prefService.setBoolPref("browser.cache.offline.enable", 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..e2b781da87
--- /dev/null
+++ b/netwerk/test/unit/test_bug652761.js
@@ -0,0 +1,17 @@
+// This is just a crashtest for a url that is rejected at parse time (port 80,000)
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test()
+{
+ // Bug 1301621 makes invalid ports throw
+ Assert.throws(() => {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:80000/",
+ loadUsingSystemPrincipal: true
+ });
+ }, "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..83fd7286f6
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926.js
@@ -0,0 +1,88 @@
+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)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0);
+ var data = gen_1MiB();
+
+ // 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)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.dataSize);
+ 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..74c9f66c0e
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926_doom_and_read.js
@@ -0,0 +1,77 @@
+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)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0);
+ var data = gen_1MiB();
+
+ 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)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.dataSize);
+ var data = gen_1MiB();
+
+ entry.asyncDoom(null);
+ write_and_check(os, data, data.length);
+
+ os.close();
+
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function(read) {
+ do_check_eq(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..2916b03805
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926_test_seek.js
@@ -0,0 +1,63 @@
+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)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(0);
+ var data = gen_1MiB();
+
+ 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)
+{
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.dataSize);
+
+ // 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..b9b7d105f7
--- /dev/null
+++ b/netwerk/test/unit/test_bug659569.js
@@ -0,0 +1,57 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+
+function setupChannel(suffix)
+{
+ return NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + suffix,
+ loadUsingSystemPrincipal: true
+ });
+}
+
+function checkValueAndTrigger(request, data, ctx)
+{
+ do_check_eq("Ok", data);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test()
+{
+ // 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.asyncOpen2(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..99c01c40ce
--- /dev/null
+++ b/netwerk/test/unit/test_bug660066.js
@@ -0,0 +1,42 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+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...");
+ do_check_false(uri1.equals(uri2));
+ do_check_false(uri1.equalsExceptRef(uri2));
+
+ do_info("Checking equality in reverse direction...");
+ do_check_false(uri2.equals(uri1));
+ do_check_false(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.spec = BLOBURI_SPEC;
+
+ do_info("Verifying that .spec matches");
+ do_check_eq(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_bug667818.js b/netwerk/test/unit/test_bug667818.js
new file mode 100644
index 0000000000..e730e74c1b
--- /dev/null
+++ b/netwerk/test/unit/test_bug667818.js
@@ -0,0 +1,21 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function makeURI(str) {
+ return Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(str, null, null);
+}
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ var serv = Components.classes["@mozilla.org/cookieService;1"]
+ .getService(Components.interfaces.nsICookieService);
+ var uri = makeURI("http://example.com/");
+ // Try an expiration time before the epoch
+ serv.setCookieString(uri, null, "test=test; path=/; domain=example.com; expires=Sun, 31-Dec-1899 16:00:00 GMT;", null);
+ do_check_eq(serv.getCookieString(uri, null), null);
+ // Now sanity check
+ serv.setCookieString(uri, null, "test2=test2; path=/; domain=example.com;", null);
+ do_check_eq(serv.getCookieString(uri, null), "test2=test2");
+}
diff --git a/netwerk/test/unit/test_bug667907.js b/netwerk/test/unit/test_bug667907.js
new file mode 100644
index 0000000000..8e33422740
--- /dev/null
+++ b/netwerk/test/unit/test_bug667907.js
@@ -0,0 +1,84 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ this.contentType);
+ request.cancel(Cr.NS_BINDING_ABORTED);
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_throw("Unexpected onDataAvailable");
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(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.asyncOpen2(new listener("text/plain", function() { run_test2();}));
+
+ do_test_pending();
+}
+
+function run_test2()
+{
+ var channel = make_channel(uri2);
+ channel.asyncOpen2(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..bbb376f8f6
--- /dev/null
+++ b/netwerk/test/unit/test_bug669001.js
@@ -0,0 +1,160 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function() { },
+ test: function(response) {
+ do_check_true(fetched);
+ }
+},
+{
+ prepare: function() { },
+ test: function(response) {
+ do_check_false(fetched);
+ }
+},
+{
+ prepare: function() {
+ setUA("A different User Agent");
+ },
+ test: function(response) {
+ do_check_true(fetched);
+ }
+},
+{
+ prepare: function() { },
+ test: function(response) {
+ do_check_false(fetched);
+ }
+},
+{
+ prepare: function() {
+ setUA("And another User Agent");
+ },
+ test: function(response) {
+ do_check_true(fetched);
+ }
+},
+{
+ prepare: function() { },
+ test: function(response) {
+ do_check_false(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...
+ do_execute_soon(function() {
+ chan.asyncOpen2(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..e10976559e
--- /dev/null
+++ b/netwerk/test/unit/test_bug767025.js
@@ -0,0 +1,275 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+
+Cu.import("resource://testing-common/httpd.js");
+
+/**
+ * This is testcase do following steps to make sure bug767025 removing
+ * files as expection.
+ *
+ * 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.
+ */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+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) {
+ do_print("manifest\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest);
+}
+
+function datafile_handler(metadata, response) {
+ do_print("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) {
+ do_print("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.setComplexValue("browser.cache.offline.parent_directory",
+ Ci.nsILocalFile, do_get_profile());
+ do_print("profile " + do_get_profile());
+}
+
+function init_http_server() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/app.appcache", manifest_handler);
+ httpServer.registerPathHandler("/app", app_handler);
+ for (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(LoadContextInfo.default, null);
+ storage.asyncEvictStorage(null);
+}
+
+function do_app_cache(manifestURL, pageURL) {
+ let update_service = Cc[kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID].
+ getService(Ci.nsIOfflineCacheUpdateService);
+
+ Services.perms.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: function QueryInterface(iftype) {
+ return this;
+ },
+
+ updateStateChanged: stateChangeHandler,
+ applicationCacheAvailable: cacheAvailHandler
+ };~
+ update.addObserver(observer, false);
+
+ 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, null, null),
+ ioService.newURI(pageURL, null, null));
+ 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() {
+ do_print("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) {
+ do_print("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(LoadContextInfo.default, appcache);
+
+ // Doom foo1 & foo2
+ storage.asyncDoomURI(createURI(kHttpLocation + "pages/foo1"), "", { onCacheEntryDoomed: function() {
+ storage.asyncDoomURI(createURI(kHttpLocation + "pages/foo2"), "", { onCacheEntryDoomed: function() {
+ 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");
+ do_check_eq(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("C");
+ file.append("2");
+ file.append("5F356A168B5E3B-0");
+ do_check_eq(file.exists(), false);
+
+ // activate foo3
+ asyncOpenCacheEntry(
+ kHttpLocation + "pages/foo3",
+ "appcache", Ci.nsICacheStorage.OPEN_READONLY, null,
+ function(status, entry, appcache) {
+ var hold_entry_foo3 = entry;
+
+ // evict all documents.
+ let storage = get_cache_service().appCacheStorage(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");
+ do_check_eq(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("0");
+ file.append("0");
+ file.append("61FEE819921D39-0");
+ do_check_eq(file.exists(), false);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("3");
+ file.append("9");
+ file.append("0D8759F1DE5452-0");
+ do_check_eq(file.exists(), false);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("C");
+ file.append("2");
+ file.append("5F356A168B5E3B-0");
+ do_check_eq(file.exists(), false);
+
+ // foo3
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("D");
+ file.append("C");
+ file.append("1ADCCC843B5C00-0");
+ do_check_eq(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("F");
+ file.append("0");
+ file.append("FC3E6D6C1164E9-0");
+ do_check_eq(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..8fd7abcea4
--- /dev/null
+++ b/netwerk/test/unit/test_bug770243.js
@@ -0,0 +1,207 @@
+/* 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
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv;
+
+function addCreds(scheme, host)
+{
+ var authMgr = Components.classes['@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 = Components.classes['@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) {
+ do_check_eq(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) {
+ do_check_eq(metadata.hasHeader("Authorization"), false);
+ do_check_eq(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) {
+ do_check_eq(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) {
+ do_check_eq(metadata.hasHeader("Authorization"), false);
+ do_check_eq(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) {
+ do_check_eq(metadata.hasHeader("Authorization"), true);
+ do_check_eq(metadata.getHeader("If-None-Match"), '"two"');
+ response.setStatusLine(metadata.httpVersion, 304, "OK");
+ response.setHeader("ETag", '"two"', false);
+ clearCreds();
+ },
+
+ // Test 4
+ function(metadata, response) {
+ do_check_eq(metadata.hasHeader("Authorization"), false);
+ do_check_eq(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) {
+ do_check_eq(metadata.hasHeader("Proxy-Authorization"), true);
+ do_check_eq(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) {
+ do_check_eq(metadata.hasHeader("Proxy-Authorization"), false);
+ do_check_eq(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) {
+ do_check_eq(metadata.hasHeader("Proxy-Authorization"), true);
+ do_check_eq(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.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(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.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(body, "Response body 2");
+ sync_and_run_next_test();
+ }, null, CL_NOT_FROM_CACHE));
+ },
+
+ // Test 3: 401 and 304
+ function() {
+ var ch = makeChan();
+ ch.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(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.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(body, "Response body 3");
+ sync_and_run_next_test();
+ }, null, CL_NOT_FROM_CACHE));
+ },
+
+ // Test 5: 407 and 304
+ function() {
+ var ch = makeChan();
+ ch.asyncOpen2(new ChannelListener(function(req, body) {
+ do_check_eq(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.setCharPref("network.proxy.no_proxies_on", "");
+ prefs.setIntPref("network.proxy.type", 1);
+
+ 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..ecda0780cd
--- /dev/null
+++ b/netwerk/test/unit/test_bug812167.js
@@ -0,0 +1,127 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+/*
+- 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;
+ return;
+}
+
+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;
+ return;
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function check_response(path, request, buffer, expectedExpiration, continuation)
+{
+ do_check_eq(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) {
+ do_check_eq(status, 0);
+
+ // Expired entry is on disk, no-store entry is in memory
+ do_check_eq(entry.persistent, expectedExpiration);
+
+ // Do the request again and check the server handler is called appropriately
+ var chan = make_channel(path);
+ chan.asyncOpen2(new ChannelListener(function(request, buffer) {
+ do_check_eq(buffer, responseBody);
+
+ if (expectedExpiration) {
+ // Handler had to be called second time
+ do_check_eq(redirectHandler_ExpiresInPast_calls, 2);
+ }
+ else {
+ // Handler had to be called second time (no-store forces validate),
+ // and we are just in memory
+ do_check_eq(redirectHandler_NoStore_calls, 2);
+ do_check_true(!entry.persistent);
+ }
+
+ continuation();
+ }, null));
+ });
+}
+
+function run_test_no_store()
+{
+ var chan = make_channel(randomURI1);
+ chan.asyncOpen2(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.asyncOpen2(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..233e13a9e3
--- /dev/null
+++ b/netwerk/test/unit/test_bug826063.js
@@ -0,0 +1,107 @@
+/* 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
+ */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+var URIs = [
+ "http://example.org",
+ "https://example.org",
+ "ftp://example.org"
+ ];
+
+function LoadContext(usePrivateBrowsing) {
+ this.usePrivateBrowsing = usePrivateBrowsing;
+}
+LoadContext.prototype = {
+ originAttributes: {},
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext, Ci.nsIInterfaceRequestor]),
+ getInterface: XPCOMUtils.generateQI([Ci.nsILoadContext])
+};
+
+function getChannels() {
+ for (let u of URIs) {
+ yield NetUtil.newChannel({
+ uri: u,
+ loadUsingSystemPrincipal: true
+ });
+ }
+}
+
+function checkPrivate(channel, shouldBePrivate) {
+ do_check_eq(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 = new LoadContext(true);
+ 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 = new LoadContext(false);
+ 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.
+
+
+function run_test() {
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_bug856978.js b/netwerk/test/unit/test_bug856978.js
new file mode 100644
index 0000000000..9624ef64e1
--- /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.
+
+Components.utils.import("resource://testing-common/httpd.js");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+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;
+ }
+ do_check_true(noAuthHeader);
+ } else {
+ // Not our test request yet.
+ 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: function() {
+ do_print("Registering " + notification);
+ Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService).
+ addObserver(this, notification, true);
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIObserver) || iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic == notification) {
+ if (!(subject instanceof Ci.nsIHttpChannel)) {
+ do_throw(notification + " observed a non-HTTP channel.");
+ }
+ try {
+ let authHeader = 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, ctx) {},
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, 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.asyncOpen2(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.asyncOpen2(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..97b9ee20f6
--- /dev/null
+++ b/netwerk/test/unit/test_bug894586.js
@@ -0,0 +1,158 @@
+/*
+ * Tests for bug 894586: nsSyncLoadService::PushSyncStreamToListener
+ * should not fail for channels of unknown size
+ */
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"]
+ .getService(Ci.nsIContentSecurityManager);
+
+function ProtocolHandler() {
+ this.uri = Cc["@mozilla.org/network/simple-uri;1"].
+ createInstance(Ci.nsIURI);
+ this.uri.spec = this.scheme + ":dummy";
+ this.uri.QueryInterface(Ci.nsIMutable).mutable = false;
+}
+
+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;
+ },
+ newURI: function(aSpec, aOriginCharset, aBaseURI) {
+ return this.uri;
+ },
+ newChannel2: function(aURI, aLoadInfo) {
+ this.loadInfo = aLoadInfo;
+ return this;
+ },
+ newChannel: function(aURI) {
+ return this;
+ },
+ allowPort: function(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", NS_ERROR_NOT_IMPLEMENTED);
+ },
+ open: function() {
+ var file = do_get_file("test_bug894586.js", false);
+ do_check_true(file.exists());
+ var url = Services.io.newFileURI(file);
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true}).open2();
+ },
+ open2: function() {
+ // throws an error if security checks fail
+ contentSecManager.performSecurityCheck(this, null);
+ return this.open();
+ },
+ asyncOpen: function(aListener, aContext) {
+ throw Components.Exception("Not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ asyncOpen2: function(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: function(status) {},
+ loadGroup: null,
+ loadFlags: Ci.nsIRequest.LOAD_NORMAL |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE,
+
+ /** nsIFactory */
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.Exception("createInstance no aggregation",
+ Cr.NS_ERROR_NO_AGGREGATION);
+ }
+ return this.QueryInterface(aIID);
+ },
+ lockFactory: function() {},
+
+ /** nsISupports */
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler,
+ Ci.nsIRequest,
+ Ci.nsIChannel,
+ Ci.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);
+ do_check_true(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..5e2ba65693
--- /dev/null
+++ b/netwerk/test/unit/test_bug935499.js
@@ -0,0 +1,7 @@
+function run_test() {
+ var idnService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+
+ var isASCII = {};
+ do_check_eq(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..bed26de0e6
--- /dev/null
+++ b/netwerk/test/unit/test_cache-control_request.js
@@ -0,0 +1,385 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+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();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since it depends on the new APIs.");
+ return;
+ }
+
+ do_test_pending();
+
+ 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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_false(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_false(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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.asyncOpen2(new ChannelListener(function(request, data) {
+ do_check_true(hit_server);
+ do_check_true(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_cache2-00-service-get.js b/netwerk/test/unit/test_cache2-00-service-get.js
new file mode 100644
index 0000000000..6a8b2e10c9
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-00-service-get.js
@@ -0,0 +1,16 @@
+function run_test()
+{
+ // Just check the contract ID alias works well.
+ try {
+ var serviceA = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ do_check_true(serviceA);
+ var serviceB = Components.classes["@mozilla.org/network/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ do_check_true(serviceB);
+
+ do_check_eq(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..dd8c340871
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01-basic.js
@@ -0,0 +1,28 @@
+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..bf1d313171
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01a-basic-readonly.js
@@ -0,0 +1,28 @@
+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..e46d6ab5f0
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01b-basic-datasize.js
@@ -0,0 +1,32 @@
+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
+ do_check_eq(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
+ do_check_eq(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
+ do_check_eq(entry.dataSize, 3);
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ do_check_eq(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..0467656f09
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01c-basic-hasmeta-only.js
@@ -0,0 +1,28 @@
+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..b07831d7b0
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01d-basic-not-wanted.js
@@ -0,0 +1,28 @@
+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..156add50e1
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01e-basic-bypass-if-busy.js
@@ -0,0 +1,35 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // 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) {
+ do_check_false(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
+ do_execute_soon(function() {
+ do_check_true(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..d1ef54b5fc
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01f-basic-openTruncate.js
@@ -0,0 +1,24 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ var storage = getCacheStorage("disk");
+ var entry = storage.openTruncate(createURI("http://new1/"), "");
+ do_check_true(!!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..584ff7dfa7
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-02-open-non-existing.js
@@ -0,0 +1,28 @@
+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..79c367410d
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-03-oncacheentryavail-throws.js
@@ -0,0 +1,24 @@
+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..f4e59bd360
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-04-oncacheentryavail-throws2x.js
@@ -0,0 +1,28 @@
+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..5501622710
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-05-visit.js
@@ -0,0 +1,78 @@
+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.
+ do_execute_soon(function() {
+ syncWithCacheIOThread(function() {
+
+ var expectedConsumption = newCacheBackEndUsed()
+ ? 4096
+ : 48;
+
+ 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
+ );
+ });
+ });
+ }, !newCacheBackEndUsed());
+
+ 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..616499df99
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-06-pb-mode.js
@@ -0,0 +1,41 @@
+function exitPB()
+{
+ var obsvc = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ obsvc.notifyObservers(null, "last-pb-context-exited", null);
+}
+
+function run_test()
+{
+ do_get_profile();
+
+ // Store PB entry
+ asyncOpenCacheEntry("http://p1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.private,
+ new OpenCallback(NEW, "p1m", "p1d", function(entry) {
+ asyncOpenCacheEntry("http://p1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.private,
+ new OpenCallback(NORMAL, "p1m", "p1d", function(entry) {
+ // Check it's there
+ syncWithCacheIOThread(function() {
+ var storage = getCacheStorage("disk", 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..03b5aba80a
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-07-visit-memory.js
@@ -0,0 +1,82 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // 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..1018a4cbaa
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-07a-open-memory.js
@@ -0,0 +1,53 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // 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) {
+ do_check_false(entryM1.persistent);
+ asyncOpenCacheEntry("http://mem-first/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL, "mem1-meta", "mem1-data", function(entryM2) {
+ do_check_false(entryM1.persistent);
+ do_check_false(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) {
+ do_check_true(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) {
+ do_check_true(entryD1.persistent);
+ do_check_false(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) {
+ do_check_true(entryD1.persistent);
+ do_check_false(entryD2.persistent);
+ do_check_false(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..246cb789c1
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-08-evict-disk-by-memory-storage.js
@@ -0,0 +1,18 @@
+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..24c736fe20
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-09-evict-disk-by-uri.js
@@ -0,0 +1,21 @@
+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..edeeee4167
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-10-evict-direct.js
@@ -0,0 +1,20 @@
+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..0f9048db1b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-10b-evict-direct-immediate.js
@@ -0,0 +1,21 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ 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..90eced7de1
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-11-evict-memory.js
@@ -0,0 +1,61 @@
+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 = newCacheBackEndUsed()
+ ? 2048
+ : 24;
+
+ storage.asyncVisitStorage(
+ new VisitCallback(2, expectedConsumption, ["http://a/", "http://b/"], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ })
+ );
+ }, !newCacheBackEndUsed());
+
+ 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..0ce7ec0844
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-12-evict-disk.js
@@ -0,0 +1,61 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ 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
+ );
+ })
+ );
+ }, !newCacheBackEndUsed());
+
+ 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..1aa1072953
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-13-evict-non-existing.js
@@ -0,0 +1,13 @@
+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..7355a2a9e8
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-14-concurent-readers.js
@@ -0,0 +1,31 @@
+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..c5ceb99a09
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-14b-concurent-readers-complete.js
@@ -0,0 +1,48 @@
+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;
+ do_check_eq(order, newCacheBackEndUsed() ? 3 : 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;
+ do_check_eq(order, newCacheBackEndUsed() ? 1 : 2);
+ } else {
+ mc.fired();
+ }
+ })
+ );
+ asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NORMAL|NOTIFYBEFOREREAD, "x1m", "x1d", function(entry, beforeReading) {
+ if (beforeReading) {
+ ++order;
+ do_check_eq(order, newCacheBackEndUsed() ? 2 : 3);
+ } 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..9375cdbe8d
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-15-conditional-304.js
@@ -0,0 +1,39 @@
+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
+ do_execute_soon(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..601cca97e9
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-16-conditional-200.js
@@ -0,0 +1,52 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // 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)
+ do_execute_soon(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..3033e42d6b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-17-evict-all.js
@@ -0,0 +1,17 @@
+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..c83a78c7af
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-18-not-valid.js
@@ -0,0 +1,30 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // 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..ceb782d4e7
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-19-range-206.js
@@ -0,0 +1,44 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // 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..349dd343ee
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-20-range-200.js
@@ -0,0 +1,45 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // 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..6f325077d8
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-21-anon-storage.js
@@ -0,0 +1,38 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // Create and check an entry anon disk storage
+ asyncOpenCacheEntry("http://anon1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.anonymous,
+ new OpenCallback(NEW, "an1", "an1", function(entry) {
+ asyncOpenCacheEntry("http://anon1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, 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, LoadContextInfo.default,
+ new OpenCallback(NEW, "na1", "na1", function(entry) {
+ asyncOpenCacheEntry("http://anon1/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, 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, 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..bc8b15822b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-22-anon-visit.js
@@ -0,0 +1,58 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ function checkNewBackEnd()
+ {
+ var storage = getCacheStorage("disk", LoadContextInfo.default);
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 1024, ["http://an2/"], function() {
+ storage = getCacheStorage("disk", LoadContextInfo.anonymous);
+ storage.asyncVisitStorage(
+ new VisitCallback(1, 1024, ["http://an2/"], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ }
+
+ function checkOldBackEnd()
+ {
+ syncWithCacheIOThread(function() {
+ var storage = getCacheStorage("disk", LoadContextInfo.default);
+ storage.asyncVisitStorage(
+ new VisitCallback(2, 24, ["http://an2/"], function() {
+ storage = getCacheStorage("disk", LoadContextInfo.anonymous);
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, ["http://an2/"], function() {
+ finish_cache2_test();
+ }),
+ true
+ );
+ }),
+ true
+ );
+ });
+ }
+
+ var mc = new MultipleCallbacks(2, newCacheBackEndUsed() ? checkNewBackEnd : checkOldBackEnd, !newCacheBackEndUsed());
+
+ asyncOpenCacheEntry("http://an2/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+ new OpenCallback(NEW|WAITFORWRITE, "an2", "an2", function(entry) {
+ mc.fired();
+ })
+ );
+
+ asyncOpenCacheEntry("http://an2/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, 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..92959645dd
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-23-read-over-chunk.js
@@ -0,0 +1,35 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ 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, LoadContextInfo.default,
+ new OpenCallback(NEW|WAITFORWRITE, "", payload, function(entry) {
+ var is = entry.openInputStream(0);
+ pumpReadStream(is, function(read) {
+ do_check_eq(read.length, kChunkSize + 10);
+ is.close();
+ do_check_true(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..fee8a2ee75
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-24-exists.js
@@ -0,0 +1,38 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ var mc = new MultipleCallbacks(2, function() {
+ var mem = getCacheStorage("memory");
+ var disk = getCacheStorage("disk");
+
+ do_check_true(disk.exists(createURI("http://m1/"), ""));
+ do_check_true(mem.exists(createURI("http://m1/"), ""));
+ do_check_false(mem.exists(createURI("http://m2/"), ""));
+ do_check_true(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, LoadContextInfo.default,
+ new OpenCallback(NEW | WAITFORWRITE, "meta", "data", function(entry) {
+ mc.fired();
+ })
+ );
+
+ asyncOpenCacheEntry("http://m1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, 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..0999dc8d2f
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
@@ -0,0 +1,51 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+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();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ 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) {
+ do_check_eq(status, Cr.NS_OK);
+ oStr = entry.openOutputStream(0);
+ var data = gen_200k();
+ do_check_eq(data.length, oStr.write(data, data.length));
+
+ asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ var oStr2 = entry.openOutputStream(0);
+ 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..f54bc19a5c
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-26-no-outputstream-open.js
@@ -0,0 +1,27 @@
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ // 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
+ do_execute_soon(() => {
+ 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..c3663751df
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-27-force-valid-for.js
@@ -0,0 +1,37 @@
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+function run_test()
+{
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ var mc = new MultipleCallbacks(2, function() {
+ finish_cache2_test();
+ });
+
+ asyncOpenCacheEntry("http://m1/", "memory", Ci.nsICacheStorage.OPEN_NORMALLY, 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..b8d93dc44b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-28-last-access-attrs.js
@@ -0,0 +1,39 @@
+function run_test()
+{
+ do_get_profile();
+ function NowSeconds() {
+ return parseInt((new Date()).getTime() / 1000);
+ }
+ function do_check_time(t, min, max) {
+ do_check_true(t >= min);
+ do_check_true(t <= max);
+ }
+
+ var timeStart = NowSeconds();
+
+ asyncOpenCacheEntry("http://t/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ new OpenCallback(NEW, "m", "d", function(entry) {
+
+ var firstOpen = NowSeconds();
+ do_check_eq(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();
+ do_check_eq(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..fdc66e10fb
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-28a-OPEN_SECRETLY.js
@@ -0,0 +1,34 @@
+function run_test()
+{
+ do_get_profile();
+ function NowSeconds() {
+ return parseInt((new Date()).getTime() / 1000);
+ }
+ function do_check_time(a, b) {
+ do_check_true(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();
+ do_check_eq(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) {
+ do_check_eq(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..d291b5f665
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
@@ -0,0 +1,72 @@
+/*
+
+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
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+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);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer)
+{
+ do_check_eq(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..67f6467bb2
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
@@ -0,0 +1,71 @@
+/*
+
+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
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null, CL_ALLOW_UNKNOWN_CL));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null, CL_ALLOW_UNKNOWN_CL));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBodyDecoded);
+}
+
+function secondTimeThrough(request, buffer)
+{
+ do_check_eq(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..f82d685e1e
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js
@@ -0,0 +1,91 @@
+/*
+
+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
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+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
+ do_check_true(responseBody.length > 1024);
+
+ do_get_profile();
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+}
+
+function secondTimeThrough(request, buffer)
+{
+ do_check_eq(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..a5461d8545
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js
@@ -0,0 +1,95 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29c test, this test checks that a corrupted 206 response is correctly handled (no crashes or asserion failures)
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- the response to the range request is broken (bad Content-Range header)
+- the first must deliver full content w/o errors
+- the second channel must correctly fail
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+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
+ do_check_true(responseBody.length > 1024);
+
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null, CL_EXPECT_FAILURE));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(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..dc0190eca1
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js
@@ -0,0 +1,90 @@
+/*
+
+Checkes if the concurrent cache read/write works when the write is interrupted because of max-entry-size limits.
+This is enhancement of 29c test, this test checks that a corrupted 206 response is correctly handled (no crashes or asserion failures)
+This test is using a resumable response.
+- with a profile, set max-entry-size to 1 (=1024 bytes)
+- first channel makes a request for a resumable response
+- second channel makes a request for the same resource, concurrent read happens
+- first channel sets predicted data size on the entry with every chunk, it's doomed on 1024
+- second channel now must engage interrupted concurrent write algorithm and read the rest of the content from the network
+- the response to the range request is plain 200
+- the first must deliver full content w/o errors
+- the second channel must correctly fail
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+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
+ do_check_true(responseBody.length > 1024);
+
+ do_get_profile();
+
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test doesn't run when the old cache back end is used since the behavior is different");
+ return;
+ }
+
+ Services.prefs.setIntPref("browser.cache.disk.max_entry_size", 1);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan1 = make_channel(URL + "/content");
+ chan1.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ var chan2 = make_channel(URL + "/content");
+ chan2.asyncOpen2(new ChannelListener(secondTimeThrough, null, CL_EXPECT_FAILURE));
+
+ do_test_pending();
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(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..3a5db421aa
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30a-entry-pinning.js
@@ -0,0 +1,32 @@
+function run_test()
+{
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ // Open for write, write
+ asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, LoadContextInfo.default,
+ new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, 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, 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..a9410ff757
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js
@@ -0,0 +1,38 @@
+function run_test()
+{
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+ var lci = 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..91c621ce59
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
@@ -0,0 +1,134 @@
+/*
+
+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.)
+
+*/
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
+
+function run_test()
+{
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+
+ var lci = LoadContextInfo.default;
+ var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
+ do_check_true(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();
+ do_execute_soon(() => {
+ 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: function(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", false);
+
+
+ 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..07105d535f
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
@@ -0,0 +1,113 @@
+/*
+
+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.
+
+*/
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
+
+function run_test()
+{
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+ var lci = LoadContextInfo.default;
+ var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
+ do_check_true(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();
+ do_execute_soon(() => {
+ 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: function(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", false);
+
+
+ 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..8a49242eee
--- /dev/null
+++ b/netwerk/test/unit/test_cacheForOfflineUse_no-store.js
@@ -0,0 +1,93 @@
+"use strict";
+// https://bugzilla.mozilla.org/show_bug.cgi?id=760955
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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 = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.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, null, null);
+}
+
+const responseBody = "response body";
+
+// A HTTP channel for updating the offline cache should normally succeed.
+function normalHandler(metadata, response)
+{
+ do_print("normalHandler");
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+function checkNormal(request, buffer)
+{
+ do_check_eq(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.asyncOpen2(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)
+{
+ do_print("noStoreHandler");
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-store");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+function checkNoStore(request, buffer)
+{
+ do_check_eq(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.asyncOpen2(new ChannelListener(checkNoStore, chan, CL_EXPECT_FAILURE));
+});
+
+function run_test()
+{
+ do_get_profile();
+
+ 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_jar.js b/netwerk/test/unit/test_cache_jar.js
new file mode 100644
index 0000000000..126e811f86
--- /dev/null
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -0,0 +1,126 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+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, appId, inIsolatedMozBrowser, userContextId) {
+ var chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+ chan.loadInfo.originAttributes = { appId: appId,
+ inIsolatedMozBrowser: inIsolatedMozBrowser,
+ userContextId: userContextId,
+ };
+ return chan;
+}
+
+// [appId, inIsolatedMozBrowser, userContextId, expected_handlers_called]
+var firstTests = [
+ [0, false, 0, 1], [0, true, 0, 1], [1, false, 0, 1], [1, true, 0, 1],
+ [0, false, 1, 1], [0, true, 1, 1], [1, false, 1, 1], [1, true, 1, 1]
+];
+var secondTests = [
+ [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 0], [1, true, 0, 1],
+ [0, false, 1, 0], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
+];
+var thirdTests = [
+ [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 1], [1, true, 0, 1],
+ [0, false, 1, 0], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
+];
+var fourthTests = [
+ [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 0], [1, true, 0, 0],
+ [0, false, 1, 1], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
+];
+
+function run_all_tests() {
+ for (let test of firstTests) {
+ handlers_called = 0;
+ var chan = makeChan(URL, test[0], test[1], test[2]);
+ chan.asyncOpen2(new ChannelListener(doneFirstLoad, test[3]));
+ yield undefined;
+ }
+
+ // 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;
+
+ let attrs_inBrowser = JSON.stringify({ appId:1, inIsolatedMozBrowser:true });
+ let attrs_notInBrowser = JSON.stringify({ appId:1 });
+
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_inBrowser);
+
+ for (let test of secondTests) {
+ handlers_called = 0;
+ var chan = makeChan(URL, test[0], test[1], test[2]);
+ chan.asyncOpen2(new ChannelListener(doneFirstLoad, test[3]));
+ yield undefined;
+ }
+
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_notInBrowser);
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_inBrowser);
+
+ for (let test of thirdTests) {
+ handlers_called = 0;
+ var chan = makeChan(URL, test[0], test[1], test[2]);
+ chan.asyncOpen2(new ChannelListener(doneFirstLoad, test[3]));
+ yield undefined;
+ }
+
+ let attrs_userContextId = JSON.stringify({ userContextId: 1 });
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs_userContextId);
+
+ for (let test of fourthTests) {
+ handlers_called = 0;
+ var chan = makeChan(URL, test[0], test[1], test[2]);
+ chan.asyncOpen2(new ChannelListener(doneFirstLoad, test[3]));
+ yield undefined;
+ }
+}
+
+var gTests;
+function run_test() {
+ do_get_profile();
+ if (!newCacheBackEndUsed()) {
+ do_check_true(true, "This test checks only cache2 specific behavior.");
+ return;
+ }
+ do_test_pending();
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/cached", cached_handler);
+ httpserv.start(-1);
+ gTests = run_all_tests();
+ gTests.next();
+}
+
+function doneFirstLoad(req, buffer, expected) {
+ // Load it again, make sure it hits the cache
+ var oa = req.loadInfo.originAttributes;
+ var chan = makeChan(URL, oa.appId, oa.isInIsolatedMozBrowserElement, oa.userContextId);
+ chan.asyncOpen2(new ChannelListener(doneSecondLoad, expected));
+}
+
+function doneSecondLoad(req, buffer, expected) {
+ do_check_eq(handlers_called, expected);
+ try {
+ gTests.next();
+ } catch (x) {
+ do_test_finished();
+ }
+}
diff --git a/netwerk/test/unit/test_cacheflags.js b/netwerk/test/unit/test_cacheflags.js
new file mode 100644
index 0000000000..28c24b14c4
--- /dev/null
+++ b/netwerk/test/unit/test_cacheflags.js
@@ -0,0 +1,370 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.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;
+
+// We attach this to channel when we want to test Private Browsing mode
+function LoadContext(usePrivateBrowsing) {
+ this.usePrivateBrowsing = usePrivateBrowsing;
+ this.originAttributes.privateBrowsingId = usePrivateBrowsing ? 1 : 0;
+}
+
+LoadContext.prototype = {
+ originAttributes: {
+ privateBrowsingId : 0
+ },
+ usePrivateBrowsing: false,
+ // don't bother defining rest of nsILoadContext fields: don't need 'em
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsILoadContext))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function(iid) {
+ if (iid.equals(Ci.nsILoadContext))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+};
+
+var PrivateBrowsingLoadContext = new LoadContext(true);
+
+function make_channel(url, flags, usePrivateBrowsing) {
+ var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
+
+ var uri = Services.io.newURI(url, null, null);
+ var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri,
+ { privateBrowsingId : usePrivateBrowsing ? 1 : 0 });
+
+ var req = NetUtil.newChannel({uri: uri,
+ loadingPrincipal: principal,
+ securityFlags: 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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ var cachingChannel = request.QueryInterface(Ci.nsICacheInfoChannel);
+ this._isFromCache = request.isPending() && cachingChannel.isFromCache();
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(Components.isSuccessCode(status), this.expectSuccess);
+ do_check_eq(this._isFromCache, this.readFromCache);
+ do_check_eq(gHitServer, this.hitServer);
+
+ do_timeout(0, run_next_test);
+ },
+
+ run: function() {
+ 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.asyncOpen2(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_channel_close.js b/netwerk/test/unit/test_channel_close.js
new file mode 100644
index 0000000000..a7a90e04fd
--- /dev/null
+++ b/netwerk/test/unit/test_channel_close.js
@@ -0,0 +1,59 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+
+ var local_channel;
+
+ // Opened channel that has no remaining references on shutdown
+ local_channel = setupChannel(testpath);
+ local_channel.asyncOpen2(new ChannelListener(checkRequest, local_channel));
+
+ // Opened channel that has no remaining references after being opened
+ setupChannel(testpath).asyncOpen2(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].asyncOpen2(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) {
+ do_check_eq(data, httpbody);
+}
+
+function checkRequestFinish(request, data, context) {
+ checkRequest(request, data, context);
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_chunked_responses.js b/netwerk/test/unit/test_chunked_responses.js
new file mode 100644
index 0000000000..396e266144
--- /dev/null
+++ b/netwerk/test/unit/test_chunked_responses.js
@@ -0,0 +1,175 @@
+/*
+ * Test Chunked-Encoded response parsing.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+var test_flags = new Array();
+var testPathBase = "/chunked_hdrs";
+
+function run_test()
+{
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen2(new ChannelListener(eval("completeTest" + num),
+ channel, flags));
+}
+
+function setupChannel(url)
+{
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(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)
+{
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.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..8e68fc6a4c
--- /dev/null
+++ b/netwerk/test/unit/test_compareURIs.js
@@ -0,0 +1,49 @@
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+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 = Components.classes["@mozilla.org/scriptsecuritymanager;1"].getService(Components.interfaces.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);
+ equal = true;
+ } catch (e) {
+ equal = false
+ }
+ do_check_eq(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..275c944330
--- /dev/null
+++ b/netwerk/test/unit/test_compressappend.js
@@ -0,0 +1,80 @@
+//
+// Test that data can be appended to a cache entry even when the data is
+// compressed by the cache compression feature - bug 648429.
+//
+
+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: function() {
+ evict_cache_entries();
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ this.writeData.bind(this));
+ },
+
+ writeData: function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ if (this._compress)
+ entry.setMetaDataElement("uncompressed-len", "0");
+ var os = entry.openOutputStream(0);
+ write_and_check(os, "12345", 5);
+ os.close();
+ entry.close();
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+ this.appendData.bind(this));
+ },
+
+ appendData: function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ var os = entry.openOutputStream(entry.storageDataSize);
+ write_and_check(os, "abcde", 5);
+ os.close();
+ entry.close();
+
+ asyncOpenCacheEntry("http://data/",
+ "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+ this.checkData.bind(this));
+ },
+
+ checkData: function(status, entry) {
+ do_check_eq(status, Cr.NS_OK);
+ var self = this;
+ pumpReadStream(entry.openInputStream(0), function(str) {
+ do_check_eq(str.length, 10);
+ do_check_eq(str, "12345abcde");
+ entry.close();
+
+ do_execute_soon(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..165080b434
--- /dev/null
+++ b/netwerk/test/unit/test_content_encoding_gzip.js
@@ -0,0 +1,114 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(new ChannelListener(completeIter, channel, tests[index].flags));
+}
+
+function completeIter(request, data, ctx) {
+ if (!(tests[index].flags & CL_EXPECT_FAILURE)) {
+ do_check_eq(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 = Components.classes["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(tests[index].body, tests[index].body.length);
+ 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..f9891a7d3d
--- /dev/null
+++ b/netwerk/test/unit/test_content_length_underrun.js
@@ -0,0 +1,278 @@
+/*
+ * Test Content-Length underrun behavior
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+var test_flags = new Array();
+var testPathBase = "/cl_hdrs";
+
+var prefs;
+var enforcePrefStrict;
+var enforcePrefSoft;
+
+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");
+ prefs.setBoolPref("network.http.enforce-framing.http1", true);
+
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen2(new ChannelListener(eval("completeTest" + num),
+ channel, flags));
+}
+
+function run_gzip_test(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+
+ function StreamListener() {}
+
+ StreamListener.prototype = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIStreamListener) ||
+ aIID.equals(Components.interfaces.nsIRequestObserver) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(aRequest, aContext) {},
+
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ // Make sure we catch the error NS_ERROR_NET_PARTIAL_TRANSFER here.
+ do_check_eq(aStatusCode, Components.results.NS_ERROR_NET_PARTIAL_TRANSFER);
+ // do_test_finished();
+ endTests();
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {}
+ };
+
+ let listener = new StreamListener();
+
+ channel.asyncOpen2(listener);
+
+}
+
+function setupChannel(url)
+{
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.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);
+ 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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, Components.results.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);
+ 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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, Components.results.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..c422e0497c
--- /dev/null
+++ b/netwerk/test/unit/test_content_sniffer.js
@@ -0,0 +1,131 @@
+// This file tests nsIContentSniffer, introduced in bug 324985
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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;
+
+/**
+ * This object is both a factory and an nsIContentSniffer implementation (so, it
+ * is de-facto a service)
+ */
+var sniffer = {
+ QueryInterface: function sniffer_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIFactory) ||
+ iid.equals(Components.interfaces.nsIContentSniffer))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ createInstance: function sniffer_ci(outer, iid) {
+ if (outer)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+ lockFactory: function sniffer_lockf(lock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ getMIMETypeFromContent: function (request, data, length) {
+ return sniffedType;
+ }
+};
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ try {
+ var chan = request.QueryInterface(Components.interfaces.nsIChannel);
+ if (chan.contentType == unknownType)
+ do_throw("Type should not be unknown!");
+ 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.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, 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 |= Components.interfaces.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+
+ return chan;
+}
+
+var httpserv = null;
+var urls = null;
+
+function run_test() {
+ httpserv = new HttpServer();
+ 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
+];
+
+ Components.manager.nsIComponentRegistrar.registerFactory(snifferCID,
+ "Unit test content sniffer", snifferContract, sniffer);
+
+ run_test_iteration(1);
+}
+
+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 = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+ catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test",
+ snifferContract, false, true);
+ }
+
+ var chan = makeChan(urls[index - 1]);
+
+ listener._iteration++;
+ chan.asyncOpen2(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..d9ef2922aa
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_blacklist.js
@@ -0,0 +1,19 @@
+const GOOD_COOKIE = "GoodCookie=OMNOMNOM";
+const SPACEY_COOKIE = "Spacey Cookie=Major Tom";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var cookieURI = ios.newURI("http://mozilla.org/test_cookie_blacklist.js",
+ null, null);
+
+ var cookieService = Cc["@mozilla.org/cookieService;1"]
+ .getService(Ci.nsICookieService);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie1=\x01", null, null);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie2=\v", null, null);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "Bad\x07Name=illegal", null, null);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, GOOD_COOKIE, null, null);
+ cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, SPACEY_COOKIE, null, null);
+
+ var storedCookie = cookieService.getCookieString(cookieURI, null);
+ do_check_eq(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..e3ac76d93c
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_header.js
@@ -0,0 +1,100 @@
+// This file tests bug 250375
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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");
+ do_check_eq(chanValue, value);
+}
+
+var cookieVal = "C1=V1";
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ try {
+ var chan = request.QueryInterface(Components.interfaces.nsIHttpChannel);
+ check_request_header(chan, "Cookie", cookieVal);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ if (this._iteration == 1) {
+ 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(Components.interfaces.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);
+
+ httpserv = new HttpServer();
+ httpserv.start(-1);
+
+ var chan = makeChan();
+
+ chan.setRequestHeader("Cookie", cookieVal, false);
+
+ chan.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function run_test_continued() {
+ var chan = makeChan();
+
+ var cookServ = Components.classes["@mozilla.org/cookieService;1"]
+ .getService(Components.interfaces.nsICookieService);
+ var cookie2 = "C2=V2";
+ cookServ.setCookieString(chan.URI, null, cookie2, chan);
+ 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.asyncOpen2(listener);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cookiejars.js b/netwerk/test/unit/test_cookiejars.js
new file mode 100644
index 0000000000..2e0fae8f4b
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars.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/. */
+
+/*
+ * Test that channels with different LoadInfo
+ * are stored in separate namespaces ("cookie jars")
+ */
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(new ChannelListener(setNextCookie, null));
+}
+
+function setNextCookie(request, data, context)
+{
+ if (++i == tests.length) {
+ // all cookies set: switch to checking them
+ i = 0;
+ checkCookie();
+ } else {
+ do_print("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.asyncOpen2(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;
+ found = (cookiesSeen.indexOf(cookieToCheck) != -1);
+ 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.
+ do_print("Saw only correct cookie '" + expectedCookie + "'");
+ do_check_true(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);
+
+ 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..c4e12aff06
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars_safebrowsing.js
@@ -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/. */
+
+/*
+ * Description of the test:
+ * We show that we can separate the safebrowsing cookie by creating a custom
+ * OriginAttributes using a reserved AppId (UINT_32_MAX - 1). Setting this
+ * custom OriginAttributes on the loadInfo of the channel allows us to query the
+ * AppId and therefore separate the safebrowing 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).
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+ "resource://gre/modules/SafeBrowsing.jsm");
+
+var setCookiePath = "/setcookie";
+var checkCookiePath = "/checkcookie";
+var safebrowsingUpdatePath = "/safebrowsingUpdate";
+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 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);
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(setCookiePath, cookieSetHandler);
+ httpserver.registerPathHandler(checkCookiePath, cookieCheckHandler);
+ httpserver.registerPathHandler(safebrowsingUpdatePath, safebrowsingUpdateHandler);
+
+ 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 dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"]
+ .getService(Ci.nsIUrlClassifierDBService);
+ 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_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.asyncOpen2(new ChannelListener(checkNonSafeBrowsingCookie, null));
+ }
+
+ function checkNonSafeBrowsingCookie() {
+ var channel = setupChannel(checkCookiePath, originAttributes);
+ channel.asyncOpen2(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");
+ do_check_eq(cookiesSeen, expectedCookie);
+ run_next_test();
+ }
+
+ setNonSafeBrowsingCookie();
+});
+
+add_test(function test_safebrowsing_cookie() {
+
+ var cookieName = 'sbCookie_id4294967294';
+ var originAttributes = new OriginAttributes(Ci.nsIScriptSecurityManager.SAFEBROWSING_APP_ID, false, 0);
+
+ function setSafeBrowsingCookie() {
+ var channel = setupChannel(setCookiePath, originAttributes);
+ channel.setRequestHeader("set-cookie", cookieName, false);
+ channel.asyncOpen2(new ChannelListener(checkSafeBrowsingCookie, null));
+ }
+
+ function checkSafeBrowsingCookie() {
+ var channel = setupChannel(checkCookiePath, originAttributes);
+ channel.asyncOpen2(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 regular cookie with custom loadcontext defined in this test.
+ var expectedCookies = "sb-update-cookie=1; ";
+ expectedCookies += cookieName + "=1";
+ request.QueryInterface(Ci.nsIHttpChannel);
+ var cookiesSeen = request.getResponseHeader("saw-cookies");
+
+ do_check_eq(cookiesSeen, expectedCookies);
+ httpserver.stop(do_test_finished);
+ }
+
+ setSafeBrowsingCookie();
+});
diff --git a/netwerk/test/unit/test_data_protocol.js b/netwerk/test/unit/test_data_protocol.js
new file mode 100644
index 0000000000..a7f02025de
--- /dev/null
+++ b/netwerk/test/unit/test_data_protocol.js
@@ -0,0 +1,58 @@
+/* run some tests on the data: protocol handler */
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// The behaviour wrt spaces is:
+// - Textual content keeps all spaces
+// - Other content strips unescaped spaces
+// - Base64 content strips escaped and unescaped spaces
+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", ""]
+];
+
+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] + ">");
+
+ /* 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();
+ }
+
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ 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.asyncOpen2(new ChannelListener(on_read_complete, i));
+ }
+}
+
diff --git a/netwerk/test/unit/test_dns_cancel.js b/netwerk/test/unit/test_dns_cancel.js
new file mode 100644
index 0000000000..c2bdd04f73
--- /dev/null
+++ b/netwerk/test/unit/test_dns_cancel.js
@@ -0,0 +1,83 @@
+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: function(inRequest, inRecord, inStatus) {
+ // One request should be resolved and two request should be canceled.
+ if (inRequest == requestList1NotCanceled) {
+ // This request should not be canceled.
+ do_check_neq(inStatus, Cr.NS_ERROR_ABORT);
+
+ do_test_finished();
+ }
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+var listener2 = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ // One request should be resolved and the other canceled.
+ if (inRequest == requestList2NotCanceled) {
+ // The request should not be canceled.
+ do_check_neq(inStatus, Cr.NS_ERROR_ABORT);
+
+ do_test_finished();
+ }
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+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, flags, listener1, mainThread);
+ dns.cancelAsyncResolve(hostname2, flags, listener1, Cr.NS_ERROR_ABORT);
+
+ // This one will not be canceled.
+ requestList1NotCanceled = dns.asyncResolve(hostname1, flags, listener1, mainThread);
+
+ // This one will be canceled with cancel(Cr.NS_ERROR_ABORT).
+ requestList1Canceled2 = dns.asyncResolve(hostname1, flags, listener1, mainThread);
+ requestList1Canceled2.cancel(Cr.NS_ERROR_ABORT);
+
+ // This one will not be canceled.
+ requestList2NotCanceled = dns.asyncResolve(hostname1, flags, listener2, mainThread);
+
+ // This one will be canceled with cancel(Cr.NS_ERROR_ABORT).
+ requestList2Canceled = dns.asyncResolve(hostname2, flags, listener2, mainThread);
+ 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..ec334b1f6d
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disable_ipv4.js
@@ -0,0 +1,40 @@
+//
+// Tests that calling asyncResolve with the RESOLVE_DISABLE_IPV4 flag doesn't
+// return any IPv4 addresses.
+//
+
+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: function(inRequest, inRecord, inStatus) {
+ if (inStatus != Cr.NS_OK) {
+ do_check_eq(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+ do_test_finished();
+ return;
+ }
+
+ while (true) {
+ try {
+ var answer = inRecord.getNextAddrAsString();
+ // If there is an answer it should be an IPv6 address
+ dump(answer);
+ do_check_true(answer.indexOf(':') != -1);
+ } catch (e) {
+ break;
+ }
+ }
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ do_test_pending();
+ try {
+ dns.asyncResolve("example.org", Ci.nsIDNSService.RESOLVE_DISABLE_IPV4, listener, null);
+ } catch (e) {
+ dump(e);
+ do_check_true(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..af5558d53e
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disable_ipv6.js
@@ -0,0 +1,41 @@
+//
+// Tests that calling asyncResolve with the RESOLVE_DISABLE_IPV6 flag doesn't
+// return any IPv6 addresses.
+//
+
+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: function(inRequest, inRecord, inStatus) {
+ if (inStatus != Cr.NS_OK) {
+ do_check_eq(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
+ do_test_finished();
+ return;
+ }
+
+ while (true) {
+ try {
+ var answer = inRecord.getNextAddrAsString();
+ // If there is an answer it should be an IPv4 address
+ dump(answer);
+ do_check_true(answer.indexOf(':') == -1);
+ do_check_true(answer.indexOf('.') != -1);
+ } catch (e) {
+ break;
+ }
+ }
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ do_test_pending();
+ try {
+ dns.asyncResolve("example.com", Ci.nsIDNSService.RESOLVE_DISABLE_IPV6, listener, null);
+ } catch (e) {
+ dump(e);
+ do_check_true(false);
+ do_test_finished();
+ }
+}
diff --git a/netwerk/test/unit/test_dns_localredirect.js b/netwerk/test/unit/test_dns_localredirect.js
new file mode 100644
index 0000000000..71e312ebc7
--- /dev/null
+++ b/netwerk/test/unit/test_dns_localredirect.js
@@ -0,0 +1,31 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+var listener = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+
+ prefs.clearUserPref("network.dns.localDomains");
+
+ do_test_finished();
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+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;
+ dns.asyncResolve("local.vingtetun.org", 0, listener, mainThread);
+
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_dns_offline.js b/netwerk/test/unit/test_dns_offline.js
new file mode 100644
index 0000000000..87a9ad8b1e
--- /dev/null
+++ b/netwerk/test/unit/test_dns_offline.js
@@ -0,0 +1,74 @@
+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: function(inRequest, inRecord, inStatus) {
+ do_check_eq(inStatus, Cr.NS_ERROR_OFFLINE);
+ test2();
+ do_test_finished();
+ }
+};
+
+var listener2 = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ do_check_eq(inStatus, Cr.NS_OK);
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+ test3();
+ do_test_finished();
+ }
+};
+
+var listener3 = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ do_check_eq(inStatus, Cr.NS_OK);
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+ cleanup();
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ do_test_pending();
+ prefs.setBoolPref("network.dns.offline-localhost", false);
+ ioService.offline = true;
+ try {
+ dns.asyncResolve("localhost", 0, listener1, mainThread);
+ } catch (e) {
+ do_check_eq(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", 0, listener2, mainThread);
+}
+
+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", 0, listener3, mainThread);
+}
+
+function cleanup() {
+ prefs.clearUserPref("network.dns.offline-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..02647b9647
--- /dev/null
+++ b/netwerk/test/unit/test_dns_onion.js
@@ -0,0 +1,70 @@
+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: function(inRequest, inRecord, inStatus) {
+ do_check_false(Components.isSuccessCode(inStatus));
+ do_test_dontBlock();
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+// check that we do lookup .onion (via pref)
+var listenerDontBlock = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+ all_done();
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function do_test_dontBlock() {
+ prefs.setBoolPref("network.dns.blockDotOnion", false);
+ dns.asyncResolve("private.onion", 0, listenerDontBlock, mainThread);
+}
+
+function do_test_block() {
+ prefs.setBoolPref("network.dns.blockDotOnion", true);
+ try {
+ dns.asyncResolve("private.onion", 0, listenerBlock, mainThread);
+ } catch (e) {
+ // it is ok for this negative test to fail fast
+ do_check_true(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_per_interface.js b/netwerk/test/unit/test_dns_per_interface.js
new file mode 100644
index 0000000000..b4c69f8c36
--- /dev/null
+++ b/netwerk/test/unit/test_dns_per_interface.js
@@ -0,0 +1,79 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+
+// This test checks DNSService host resolver when a network interface is supplied
+// as well. In the test 3 request are sent: two with a network interface set
+// and one without a network interface.
+// All requests have the same host to be resolved and the same flags.
+// One of the request with the network interface will be canceled.
+// The request with and without a network interface should not be mixed during
+// the requests lifetime.
+
+var netInterface1 = "interface1";
+var netInterface2 = "interface2";
+
+// We are not using localhost because on e10s a host resolve callback is almost
+// always faster than a cancel request, therefore cancel operation would not be
+// tested.
+var hostname = "thisshouldnotexist.mozilla.com";
+
+// 3 requests.
+var requestWithInterfaceCanceled;
+var requestWithoutInterfaceNotCanceled;
+var requestWithInterfaceNotCanceled;
+
+var listener = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ // Two requests should be resolved and one request should be canceled.
+ // Since cancalation of a request is racy we will check only for not
+ // canceled request - they should not be canceled.
+ if ((inRequest == requestWithoutInterfaceNotCanceled) ||
+ (inRequest == requestWithInterfaceNotCanceled)) {
+ // This request should not be canceled.
+ do_check_neq(inStatus, Cr.NS_ERROR_ABORT);
+
+ do_test_finished();
+ } else if (inRequest == requestWithInterfaceCanceled) {
+ // We do not check the outcome for this one because it is racy -
+ // whether the request cancelation is faster than resolving the request.
+ do_test_finished();
+ }
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+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.
+ requestWithInterfaceCanceled = dns.asyncResolveExtended(hostname, flags,
+ netInterface1,
+ listener,
+ mainThread);
+ requestWithInterfaceCanceled.cancel(Cr.NS_ERROR_ABORT);
+
+ // This one will not be canceled. This is the request without a network
+ // interface.
+ requestWithoutInterfaceNotCanceled = dns.asyncResolve(hostname, flags,
+ listener, mainThread);
+
+ // This one will not be canceled.
+ requestWithInterfaceNotCanceled = dns.asyncResolveExtended(hostname, flags,
+ netInterface2,
+ listener,
+ mainThread);
+ // We wait for notifications for the requests.
+ // For each request onLookupComplete will be called.
+ do_test_pending();
+ do_test_pending();
+ do_test_pending();
+}
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..6443f7ade0
--- /dev/null
+++ b/netwerk/test/unit/test_dns_proxy_bypass.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+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: function() {
+ this.obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ this.obs.addObserver(this, "dns-resolution-request", false);
+ },
+
+ unregister: function() {
+ if (this.obs) {
+ this.obs.removeObserver(this, "dns-resolution-request");
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic == "dns-resolution-request") {
+ do_print(data);
+ if (data.indexOf("dnsleak.example.com") > -1) {
+ try {
+ do_check_true(false);
+ } catch (e) {}
+ }
+ }
+ }
+};
+
+var listener = {
+ onAcknowledge: function(aContext, aSize) {},
+ onBinaryMessageAvailable: function(aContext, aMsg) {},
+ onMessageAvailable: function(aContext, aMsg) {},
+ onServerClose: function(aContext, aCode, aReason) {},
+ onStart: function(aContext) {},
+ onStop: function(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(Components.interfaces.nsIWebSocketChannel);
+
+ chan.initLoadInfo(null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET);
+
+ var uri = ioService.newURI(url, null, null);
+ 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..04c1faeefe
--- /dev/null
+++ b/netwerk/test/unit/test_dns_service.js
@@ -0,0 +1,26 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+
+var listener = {
+ onLookupComplete: function(inRequest, inRecord, inStatus) {
+ var answer = inRecord.getNextAddrAsString();
+ do_check_true(answer == "127.0.0.1" || answer == "::1");
+
+ do_test_finished();
+ },
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIDNSListener) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function run_test() {
+ var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+ var mainThread = threadManager.currentThread;
+ dns.asyncResolve("localhost", 0, listener, mainThread);
+
+ do_test_pending();
+}
+
diff --git a/netwerk/test/unit/test_doomentry.js b/netwerk/test/unit/test_doomentry.js
new file mode 100644
index 0000000000..5929523068
--- /dev/null
+++ b/netwerk/test/unit/test_doomentry.js
@@ -0,0 +1,97 @@
+/**
+ * Test for nsICacheStorage.asyncDoomURI().
+ * It tests dooming
+ * - an existent inactive entry
+ * - a non-existent inactive entry
+ * - an existent active entry
+ */
+
+function doom(url, callback)
+{
+ get_cache_service()
+ .diskCacheStorage(LoadContextInfo.default, false)
+ .asyncDoomURI(createURI(url), "", {
+ onCacheEntryDoomed: function(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));
+ });
+}
+
+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)
+{
+ do_check_eq(status, Cr.NS_OK);
+ doom("http://nonexistententry/", check_doom2);
+}
+
+function check_doom2(status)
+{
+ do_check_eq(status, Cr.NS_ERROR_NOT_AVAILABLE);
+ asyncOpenCacheEntry("http://testentry/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null, function(status, entry) {
+ write_entry2(entry, entry.openOutputStream(0));
+ });
+}
+
+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)
+{
+ do_check_eq(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)
+{
+ do_check_eq(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..80c1708871
--- /dev/null
+++ b/netwerk/test/unit/test_duplicate_headers.js
@@ -0,0 +1,605 @@
+/*
+ * Tests bugs 597706, 655389: prevent duplicate headers with differing values
+ * for some headers like Content-Length, Location, etc.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+var test_flags = new Array();
+var testPathBase = "/dupe_hdrs";
+
+function run_test()
+{
+ httpserver.start(-1);
+
+ do_test_pending();
+ run_test_number(1);
+}
+
+function run_test_number(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen2(new ChannelListener(eval("completeTest" + num),
+ channel, flags));
+}
+
+function setupChannel(url)
+{
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, Components.results.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 {
+ referer = request.getResponseHeader("Referer");
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.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(Components.interfaces.nsIHttpChannel);
+
+ try {
+ referer = request.getResponseHeader("Referer");
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.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:
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, 0);
+
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ do_check_eq(chan.contentDispositionFilename, "foo");
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+
+ run_test_number(16);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// empty content length is ok
+test_flags[16] = CL_ALLOW_UNKNOWN_CL;
+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)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.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)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.NS_OK);
+ do_check_eq(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)
+{
+ do_check_eq(request.status, Components.results.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..45c01683a7
--- /dev/null
+++ b/netwerk/test/unit/test_event_sink.js
@@ -0,0 +1,170 @@
+// This file tests channel event sinks (bug 315598 et al)
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function eventsink_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIFactory) ||
+ iid.equals(Components.interfaces.nsIChannelEventSink))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ createInstance: function eventsink_ci(outer, iid) {
+ if (outer)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+ lockFactory: function eventsink_lockf(lock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ asyncOnChannelRedirect: function eventsink_onredir(oldChan, newChan, flags, callback) {
+ // veto
+ this.called = true;
+ throw Components.results.NS_BINDING_ABORTED;
+ },
+
+ getInterface: function eventsink_gi(iid) {
+ if (iid.equals(Components.interfaces.nsIChannelEventSink))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ called: false
+};
+
+var listener = {
+ expectSinkCall: true,
+
+ onStartRequest: function test_onStartR(request, ctx) {
+ 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 Components.interfaces.nsIChannel) ||
+ !request.URI.equals(request.originalURI))
+ do_throw("Wrong URI: Is <" + request.URI.spec + ">, should be <" +
+ request.originalURI.spec + ">");
+
+ if (request instanceof Components.interfaces.nsIHttpChannel) {
+ // As we expect a blocked redirect, verify that we have a 3xx status
+ do_check_eq(Math.floor(request.responseStatus / 100), 3);
+ do_check_eq(request.requestSucceeded, false);
+ }
+
+ do_check_eq(eventsink.called, this.expectSinkCall);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, 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.asyncOpen2(listener);
+
+ do_test_pending();
+}
+
+function run_test_continued() {
+ eventsink.called = false;
+
+ var catMan = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.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.asyncOpen2(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_extract_charset_from_content_type.js b/netwerk/test/unit/test_extract_charset_from_content_type.js
new file mode 100644
index 0000000000..224be7052e
--- /dev/null
+++ b/netwerk/test/unit/test_extract_charset_from_content_type.js
@@ -0,0 +1,163 @@
+/* -*- 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/. */
+
+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) {
+ do_check_eq(aHadCharset, hadCharset);
+ do_check_eq(aCharset, charset.value);
+ do_check_eq(aCharsetStart, charsetStart.value);
+ do_check_eq(aCharsetEnd, charsetEnd.value);
+}
+
+function run_test() {
+ var netutil = Components.classes["@mozilla.org/network/util;1"]
+ .getService(Components.interfaces.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..5f06b29608
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_no-cache-entry_canceled.js
@@ -0,0 +1,112 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+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, null, null);
+}
+
+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)
+{
+ do_check_eq(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.createCodebasePrincipal(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.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ 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.asyncOpen2(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", false);
+
+ 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..cd389421b8
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_no-cache-entry_passing.js
@@ -0,0 +1,110 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+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)
+{
+ do_check_eq(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.createCodebasePrincipal(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.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ 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.asyncOpen2(new ChannelListener(finish_test));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ 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..eddfcbec0f
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js
@@ -0,0 +1,115 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+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)
+{
+ do_check_eq(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.createCodebasePrincipal(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.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ 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.asyncOpen2(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", false);
+
+ 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..a725161efc
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js
@@ -0,0 +1,114 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+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)
+{
+ do_check_eq(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.createCodebasePrincipal(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.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ 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.asyncOpen2(new ChannelListener(finish_test));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ 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..02129e6b4e
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_request-error_canceled.js
@@ -0,0 +1,121 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+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)
+{
+ do_check_eq(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.createCodebasePrincipal(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.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ 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.asyncOpen2(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", false);
+
+ 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..8dc935273b
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_request-error_passing.js
@@ -0,0 +1,119 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+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)
+{
+ do_check_eq(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.createCodebasePrincipal(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.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var _x = 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.asyncOpen2(new ChannelListener(finish_test));
+ });
+ }}
+
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ 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..e025f29678
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_response-error_canceled.js
@@ -0,0 +1,116 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+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, null, null);
+}
+
+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)
+{
+ do_check_eq(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.createCodebasePrincipal(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.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ 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.asyncOpen2(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", false);
+
+ 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..e59fad7d09
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_response-error_passing.js
@@ -0,0 +1,114 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+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)
+{
+ do_check_eq(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.createCodebasePrincipal(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.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
+
+ cacheUpdateObserver = {observe: function() {
+ 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.asyncOpen2(new ChannelListener(finish_test));
+ }}
+
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
+
+ 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_partial_inputstream.js b/netwerk/test/unit/test_file_partial_inputstream.js
new file mode 100644
index 0000000000..6eb4a3ac8b
--- /dev/null
+++ b/netwerk/test/unit/test_file_partial_inputstream.js
@@ -0,0 +1,512 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIPartialFileInputStream
+// NOTE! These tests often use do_check_true(a == b) rather than
+// do_check_eq(a, b) to avoid outputting characters which confuse
+// the console
+
+var CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+const PR_RDONLY = 0x1; // see prio.h
+
+// We need the profile directory so the test harness will clean up our test
+// files.
+do_get_profile();
+
+var binary_test_file_name = "data/image.png";
+var text_test_file_name = "test_file_partial_inputstream.js";
+// This is a global variable since if it's passed as an argument stack traces
+// become unreadable.
+var test_file_data;
+
+function run_test()
+{
+ // Binary tests
+ let binaryFile = do_get_file(binary_test_file_name);
+ let size = binaryFile.fileSize;
+ // Want to make sure we're working with a large enough file
+ dump("**** binary file size is: " + size + " ****\n");
+ do_check_true(size > 65536);
+
+ let binaryStream = new BinaryInputStream(new_file_input_stream(binaryFile));
+ test_file_data = "";
+ while ((avail = binaryStream.available()) > 0) {
+ test_file_data += binaryStream.readBytes(avail);
+ }
+ do_check_eq(test_file_data.length, size);
+ binaryStream.close();
+
+ test_binary_portion(0, 10);
+ test_binary_portion(0, 20000);
+ test_binary_portion(0, size);
+ test_binary_portion(20000, 10);
+ test_binary_portion(20000, 20000);
+ test_binary_portion(20000, size-20000);
+ test_binary_portion(size-10, 10);
+ test_binary_portion(size-20000, 20000);
+ test_binary_portion(0, 0);
+ test_binary_portion(20000, 0);
+ test_binary_portion(size-1, 1);
+
+
+ // Text-file tests
+ let textFile = do_get_file(binary_test_file_name);
+ size = textFile.fileSize;
+ // Want to make sure we're working with a large enough file
+ dump("**** text file size is: " + size + " ****\n");
+ do_check_true(size > 7000);
+
+ let textStream = new BinaryInputStream(new_file_input_stream(textFile));
+ test_file_data = "";
+ while ((avail = textStream.available()) > 0)
+ test_file_data += textStream.readBytes(avail);
+ do_check_eq(test_file_data.length, size);
+ textStream.close();
+
+ test_text_portion(0, 100);
+ test_text_portion(0, size);
+ test_text_portion(5000, 1000);
+ test_text_portion(size-10, 10);
+ test_text_portion(size-5000, 5000);
+ test_text_portion(10, 0);
+ test_text_portion(size-1, 1);
+
+ // Test auto-closing files
+ // Test behavior when *not* autoclosing
+ let tempFile = create_temp_file("01234567890123456789");
+ let tempInputStream = new_partial_file_input_stream(tempFile, 5, 10);
+ tempInputStream.QueryInterface(Ci.nsILineInputStream);
+ do_check_eq(read_line_stream(tempInputStream)[1], "5678901234");
+ try {
+ // This fails on some platforms
+ tempFile.remove(false);
+ }
+ catch (ex) {
+ }
+ tempInputStream.QueryInterface(Ci.nsISeekableStream);
+ tempInputStream.seek(SET, 1);
+ do_check_eq(read_line_stream(tempInputStream)[1], "678901234");
+
+ // Test removing the file when autoclosing
+ tempFile = create_temp_file("01234567890123456789");
+ tempInputStream = new_partial_file_input_stream(tempFile, 5, 10,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF |
+ Ci.nsIFileInputStream.REOPEN_ON_REWIND);
+ tempInputStream.QueryInterface(Ci.nsILineInputStream);
+ do_check_eq(read_line_stream(tempInputStream)[1], "5678901234");
+ tempFile.remove(false);
+ tempInputStream.QueryInterface(Ci.nsISeekableStream);
+ try {
+ // The seek should reopen the file, which should fail.
+ tempInputStream.seek(SET, 1);
+ do_check_true(false);
+ }
+ catch (ex) {
+ }
+
+ // Test editing the file when autoclosing
+ tempFile = create_temp_file("01234567890123456789");
+ tempInputStream = new_partial_file_input_stream(tempFile, 5, 10,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF |
+ Ci.nsIFileInputStream.REOPEN_ON_REWIND);
+ tempInputStream.QueryInterface(Ci.nsILineInputStream);
+ do_check_eq(read_line_stream(tempInputStream)[1], "5678901234");
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(tempFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ let newData = "abcdefghijklmnopqrstuvwxyz";
+ ostream.write(newData, newData.length);
+ ostream.close();
+ tempInputStream.QueryInterface(Ci.nsISeekableStream);
+ tempInputStream.seek(SET, 1);
+ do_check_eq(read_line_stream(tempInputStream)[1], newData.substr(6,9));
+
+ // Test auto-delete and auto-close together
+ tempFile = create_temp_file("01234567890123456789");
+ tempInputStream = new_partial_file_input_stream(tempFile, 5, 10,
+ Ci.nsIFileInputStream.CLOSE_ON_EOF |
+ Ci.nsIFileInputStream.DELETE_ON_CLOSE);
+ tempInputStream.QueryInterface(Ci.nsILineInputStream);
+ do_check_eq(read_line_stream(tempInputStream)[1], "5678901234");
+ do_check_false(tempFile.exists());
+}
+
+function test_binary_portion(start, length) {
+ let subFile = create_temp_file(test_file_data.substr(start, length));
+
+ let streamTests = [
+ test_4k_read,
+ test_max_read,
+ test_seek,
+ test_seek_then_read,
+ ];
+
+ for (var test of streamTests) {
+ let fileStream = new_file_input_stream(subFile);
+ let partialStream = new_partial_file_input_stream(do_get_file(binary_test_file_name),
+ start, length);
+ test(fileStream, partialStream, length);
+ fileStream.close();
+ partialStream.close();
+ }
+}
+
+function test_4k_read(fileStreamA, fileStreamB) {
+ fileStreamA.QueryInterface(Ci.nsISeekableStream);
+ fileStreamB.QueryInterface(Ci.nsISeekableStream);
+ let streamA = new BinaryInputStream(fileStreamA);
+ let streamB = new BinaryInputStream(fileStreamB);
+
+ while(1) {
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+
+ let availA = streamA.available();
+ let availB = streamB.available();
+ do_check_eq(availA, availB);
+ if (availA == 0)
+ return;
+
+ let readSize = availA > 4096 ? 4096 : availA;
+
+ do_check_true(streamA.readBytes(readSize) ==
+ streamB.readBytes(readSize));
+ }
+}
+
+function test_max_read(fileStreamA, fileStreamB) {
+ fileStreamA.QueryInterface(Ci.nsISeekableStream);
+ fileStreamB.QueryInterface(Ci.nsISeekableStream);
+ let streamA = new BinaryInputStream(fileStreamA);
+ let streamB = new BinaryInputStream(fileStreamB);
+
+ while(1) {
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+
+ let availA = streamA.available();
+ let availB = streamB.available();
+ do_check_eq(availA, availB);
+ if (availA == 0)
+ return;
+
+ do_check_true(streamA.readBytes(availA) ==
+ streamB.readBytes(availB));
+ }
+}
+
+const SET = Ci.nsISeekableStream.NS_SEEK_SET;
+const CUR = Ci.nsISeekableStream.NS_SEEK_CUR;
+const END = Ci.nsISeekableStream.NS_SEEK_END;
+function test_seek(dummy, partialFileStream, size) {
+ // We can't test the "real" filestream here as our existing file streams
+ // are very broken and allows searching past the end of the file.
+
+ partialFileStream.QueryInterface(Ci.nsISeekableStream);
+
+ var tests = [
+ [SET, 0],
+ [SET, 5],
+ [SET, 1000],
+ [SET, size-10],
+ [SET, size-5],
+ [SET, size-1],
+ [SET, size],
+ [SET, size+10],
+ [SET, 0],
+ [CUR, 5],
+ [CUR, -5],
+ [SET, 5000],
+ [CUR, -100],
+ [CUR, 200],
+ [CUR, -5000],
+ [CUR, 5000],
+ [CUR, size * 2],
+ [SET, 1],
+ [CUR, -1],
+ [CUR, -1],
+ [CUR, -1],
+ [CUR, -1],
+ [CUR, -1],
+ [SET, size-1],
+ [CUR, 1],
+ [CUR, 1],
+ [CUR, 1],
+ [CUR, 1],
+ [CUR, 1],
+ [END, 0],
+ [END, -1],
+ [END, -5],
+ [END, -1000],
+ [END, -size+10],
+ [END, -size+5],
+ [END, -size+1],
+ [END, -size],
+ [END, -size-10],
+ [END, 10],
+ [CUR, 10],
+ [CUR, 10],
+ [CUR, 100],
+ [CUR, 1000],
+ [END, -1000],
+ [CUR, 100],
+ [CUR, 900],
+ [CUR, 100],
+ [CUR, 100],
+ ];
+
+ let pos = 0;
+ for (var test of tests) {
+ let didThrow = false;
+ try {
+ partialFileStream.seek(test[0], test[1]);
+ }
+ catch (ex) {
+ didThrow = true;
+ }
+
+ let newPos = test[0] == SET ? test[1] :
+ test[0] == CUR ? pos + test[1] :
+ size + test[1];
+ if (newPos > size || newPos < 0) {
+ do_check_true(didThrow);
+ }
+ else {
+ do_check_false(didThrow);
+ pos = newPos;
+ }
+
+ do_check_eq(partialFileStream.tell(), pos);
+ do_check_eq(partialFileStream.available(), size - pos);
+ }
+}
+
+function test_seek_then_read(fileStreamA, fileStreamB, size) {
+ // For now we only test seeking inside the file since our existing file
+ // streams behave very strange when seeking to past the end of the file.
+ if (size < 20000) {
+ return;
+ }
+
+ fileStreamA.QueryInterface(Ci.nsISeekableStream);
+ fileStreamB.QueryInterface(Ci.nsISeekableStream);
+ let streamA = new BinaryInputStream(fileStreamA);
+ let streamB = new BinaryInputStream(fileStreamB);
+
+ let read = {};
+
+ var tests = [
+ [SET, 0],
+ [read, 1000],
+ [read, 1000],
+ [SET, 5],
+ [read, 1000],
+ [read, 5000],
+ [CUR, 100],
+ [read, 1000],
+ [read, 5000],
+ [CUR, -100],
+ [read, 1000],
+ [CUR, -100],
+ [read, 5000],
+ [END, -10],
+ [read, 10],
+ [END, -100],
+ [read, 101],
+ [CUR, -100],
+ [read, 10],
+ [SET, 0],
+ [read, 20000],
+ [read, 1],
+ [read, 100],
+ ];
+
+ for (var test of tests) {
+ if (test[0] === read) {
+
+ let didThrowA = false;
+ let didThrowB = false;
+
+ let bytesA, bytesB;
+ try {
+ bytesA = streamA.readBytes(test[1]);
+ }
+ catch (ex) {
+ didThrowA = true;
+ }
+ try {
+ bytesB = streamB.readBytes(test[1]);
+ }
+ catch (ex) {
+ didThrowB = true;
+ }
+
+ do_check_eq(didThrowA, didThrowB);
+ do_check_true(bytesA == bytesB);
+ }
+ else {
+ fileStreamA.seek(test[0], test[1]);
+ fileStreamB.seek(test[0], test[1]);
+ }
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+ do_check_eq(fileStreamA.available(), fileStreamB.available());
+ }
+}
+
+function test_text_portion(start, length) {
+ let subFile = create_temp_file(test_file_data.substr(start, length));
+
+ let streamTests = [
+ test_readline,
+ test_seek_then_readline,
+ ];
+
+ for (var test of streamTests) {
+ let fileStream = new_file_input_stream(subFile)
+ .QueryInterface(Ci.nsILineInputStream);
+ let partialStream = new_partial_file_input_stream(do_get_file(binary_test_file_name),
+ start, length)
+ .QueryInterface(Ci.nsILineInputStream);
+ test(fileStream, partialStream, length);
+ fileStream.close();
+ partialStream.close();
+ }
+}
+
+function test_readline(fileStreamA, fileStreamB)
+{
+ let moreA = true, moreB;
+ while(moreA) {
+ let lineA, lineB;
+ [moreA, lineA] = read_line_stream(fileStreamA);
+ [moreB, lineB] = read_line_stream(fileStreamB);
+ do_check_eq(moreA, moreB);
+ do_check_true(lineA.value == lineB.value);
+ }
+}
+
+function test_seek_then_readline(fileStreamA, fileStreamB, size) {
+ // For now we only test seeking inside the file since our existing file
+ // streams behave very strange when seeking to past the end of the file.
+ if (size < 100) {
+ return;
+ }
+
+ fileStreamA.QueryInterface(Ci.nsISeekableStream);
+ fileStreamB.QueryInterface(Ci.nsISeekableStream);
+
+ let read = {};
+
+ var tests = [
+ [SET, 0],
+ [read, 5],
+ [read, 5],
+ [SET, 5],
+ [read, 5],
+ [read, 15],
+ [CUR, 100],
+ [read, 5],
+ [read, 15],
+ [CUR, -100],
+ [read, 5],
+ [CUR, -100],
+ [read, 25],
+ [END, -10],
+ [read, 1],
+ [END, -50],
+ [read, 30],
+ [read, 1],
+ [read, 1],
+ [CUR, -100],
+ [read, 1],
+ [SET, 0],
+ [read, 10000],
+ [read, 1],
+ [read, 1],
+ [SET, 0],
+ [read, 1],
+ ];
+
+ for (var test of tests) {
+ if (test[0] === read) {
+
+ for (let i = 0; i < test[1]; ++i) {
+ let didThrowA = false;
+ let didThrowB = false;
+
+ let lineA, lineB, moreA, moreB;
+ try {
+ [moreA, lineA] = read_line_stream(fileStreamA);
+ }
+ catch (ex) {
+ didThrowA = true;
+ }
+ try {
+ [moreB, lineB] = read_line_stream(fileStreamB);
+ }
+ catch (ex) {
+ didThrowB = true;
+ }
+
+ do_check_eq(didThrowA, didThrowB);
+ do_check_eq(moreA, moreB);
+ do_check_true(lineA == lineB);
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+ do_check_eq(fileStreamA.available(), fileStreamB.available());
+ if (!moreA)
+ break;
+ }
+ }
+ else {
+ if (!(test[0] == CUR && (test[1] > fileStreamA.available() ||
+ test[1] < -fileStreamA.tell()))) {
+ fileStreamA.seek(test[0], test[1]);
+ fileStreamB.seek(test[0], test[1]);
+ do_check_eq(fileStreamA.tell(), fileStreamB.tell());
+ do_check_eq(fileStreamA.available(), fileStreamB.available());
+ }
+ }
+ }
+}
+
+function read_line_stream(stream) {
+ let line = {};
+ let more = stream.readLine(line);
+ return [more, line.value];
+}
+
+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.QueryInterface(Ci.nsIInputStream);
+}
+
+function new_partial_file_input_stream(file, start, length, flags) {
+ var stream =
+ Cc["@mozilla.org/network/partial-file-input-stream;1"]
+ .createInstance(Ci.nsIPartialFileInputStream);
+ stream.init(file, start, length, PR_RDONLY, 0, flags || 0);
+ return stream.QueryInterface(Ci.nsIInputStream);
+}
+
+function create_temp_file(data) {
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ file.append("fileinputstream-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, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ do_check_eq(ostream.write(data, data.length), data.length);
+ ostream.close();
+
+ return file;
+}
diff --git a/netwerk/test/unit/test_file_protocol.js b/netwerk/test/unit/test_file_protocol.js
new file mode 100644
index 0000000000..2f1c5eb7f1
--- /dev/null
+++ b/netwerk/test/unit/test_file_protocol.js
@@ -0,0 +1,251 @@
+/* run some tests on the file:// protocol handler */
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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_replace,
+ do_test_finished
+].forEach(add_test);
+
+function getFile(key) {
+ var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ return dirSvc.get(key, Components.interfaces.nsILocalFile);
+}
+
+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: function(request) {
+ request.QueryInterface(Ci.nsIFileChannel);
+ return request.file.isDirectory();
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ 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: function(request, context, 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: function(request, context, 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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(new FileStreamListener(on_upload_complete));
+}
+
+function test_load_replace() {
+ // lnk files should resolve to their targets
+ if (mozinfo.os == "win") {
+ dump("*** test_load_replace\n");
+ file = do_get_file("data/system_root.lnk", false);
+ var chan = new_file_channel(file);
+
+ // The LOAD_REPLACE flag should be set
+ do_check_eq(chan.loadFlags & chan.LOAD_REPLACE, chan.LOAD_REPLACE);
+
+ // The original URI path should differ from the URI path
+ do_check_neq(chan.URI.path, chan.originalURI.path);
+
+ // 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);
+ do_check_eq(chan.originalURI.path, ios.newFileURI(file).path);
+ }
+ run_next_test();
+}
+
+function run_test() {
+ 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..2736527a53
--- /dev/null
+++ b/netwerk/test/unit/test_filestreams.js
@@ -0,0 +1,298 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+// 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);
+ do_check_eq(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
+ do_check_true(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(aFile, -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).
+ do_check_true( (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();
+
+ do_check_eq(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);
+
+ do_check_eq(buffered.available(), 0);
+
+ // Now try reading from this stream
+ let string = {};
+ do_check_eq(cstream.readString(BUFFERSIZE, string), 0);
+ do_check_eq(string.value, "");
+
+ // Now check that available() throws
+ var exceptionThrown = false;
+ try {
+ do_check_eq(buffered.available(), 0);
+ } catch (e) {
+ exceptionThrown = true;
+ }
+ do_check_true(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 {
+ do_check_eq(buffered.available(), 0);
+ } catch (e) {
+ exceptionThrown = true;
+ }
+ do_check_false(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..c1403f517e
--- /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.
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, 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.asyncOpen2(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..78e84a8710
--- /dev/null
+++ b/netwerk/test/unit/test_getHost.js
@@ -0,0 +1,68 @@
+// Test getLocalHost/getLocalPort and getRemoteHost/getRemotePort.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+const PORT = httpserver.identity.primaryPort;
+
+var gotOnStartRequest = false;
+
+function CheckGetHostListener() {}
+
+CheckGetHostListener.prototype = {
+ onStartRequest: function(request, context) {
+ dump("*** listener onStartRequest\n");
+
+ gotOnStartRequest = true;
+
+ request.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
+ try {
+ do_check_eq(request.localAddress, "127.0.0.1");
+ do_check_eq(request.localPort > 0, true);
+ do_check_neq(request.localPort, PORT);
+ do_check_eq(request.remoteAddress, "127.0.0.1");
+ do_check_eq(request.remotePort, PORT);
+ } catch (e) {
+ do_check_true(0, "Get local/remote host/port throws an error!");
+ }
+ },
+
+ onStopRequest: function(request, context, statusCode) {
+ dump("*** listener onStopRequest\n");
+
+ do_check_eq(gotOnStartRequest, true);
+ httpserver.stop(do_test_finished);
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports)
+ )
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.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.asyncOpen2(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..3287fae664
--- /dev/null
+++ b/netwerk/test/unit/test_gre_resources.js
@@ -0,0 +1,31 @@
+// test that things that are expected to be in gre-resources are still there
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var ios = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService);
+
+function wrapInputStream(input)
+{
+ var nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+ var factory = Components.classes["@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.open2());
+ do_check_true(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..8e4eaa4c76
--- /dev/null
+++ b/netwerk/test/unit/test_gzipped_206.js
@@ -0,0 +1,94 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+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) {
+ do_check_true(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, body.length);
+ response.finish();
+}
+
+function continue_test(request, data) {
+ do_check_eq(17, data.length);
+ var chan = make_channel("http://localhost:" +
+ httpserver.identity.primaryPort + "/cached/test.gz");
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_GZIP));
+}
+
+var enforcePref;
+
+function finish_test(request, data, ctx) {
+ do_check_eq(request.status, 0);
+ do_check_eq(data.length, responseBody.length);
+ for (var i = 0; i < data.length; ++i) {
+ do_check_eq(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.asyncOpen2(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..058f47bcbf
--- /dev/null
+++ b/netwerk/test/unit/test_head.js
@@ -0,0 +1,150 @@
+//
+// HTTP headers test
+//
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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");
+ do_check_eq(setOK, "initial value");
+ channel.setRequestHeader("ReplaceMe", "replaced", false);
+ setOK = channel.getRequestHeader("ReplaceMe");
+ do_check_eq(setOK, "replaced");
+
+ channel.setRequestHeader("MergeMe", "foo1", true);
+ channel.setRequestHeader("MergeMe", "foo2", true);
+ channel.setRequestHeader("MergeMe", "foo3", true);
+ setOK = channel.getRequestHeader("MergeMe");
+ do_check_eq(setOK, "foo1, foo2, foo3");
+
+ channel.setEmptyRequestHeader("Empty");
+ setOK = channel.getRequestHeader("Empty");
+ do_check_eq(setOK, "");
+
+ channel.setRequestHeader("ReplaceWithEmpty", "initial value", true);
+ setOK = channel.getRequestHeader("ReplaceWithEmpty");
+ do_check_eq(setOK, "initial value");
+ channel.setEmptyRequestHeader("ReplaceWithEmpty");
+ setOK = channel.getRequestHeader("ReplaceWithEmpty");
+ do_check_eq(setOK, "");
+
+ channel.setEmptyRequestHeader("MergeWithEmpty");
+ setOK = channel.getRequestHeader("MergeWithEmpty");
+ do_check_eq(setOK, "");
+ channel.setRequestHeader("MergeWithEmpty", "foo", true);
+ setOK = channel.getRequestHeader("MergeWithEmpty");
+ do_check_eq(setOK, "foo");
+
+ var uri = NetUtil.newURI("http://foo1.invalid:80");
+ channel.referrer = uri;
+ do_check_true(channel.referrer.equals(uri));
+ setOK = channel.getRequestHeader("Referer");
+ do_check_eq(setOK, "http://foo1.invalid/");
+
+ uri = NetUtil.newURI("http://foo2.invalid:90/bar");
+ channel.referrer = uri;
+ setOK = channel.getRequestHeader("Referer");
+ do_check_eq(setOK, "http://foo2.invalid:90/bar");
+
+ // ChannelListener defined in head_channels.js
+ channel.asyncOpen2(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");
+ do_check_eq(setOK, "replaced");
+ setOK = metadata.getHeader("MergeMe");
+ do_check_eq(setOK, "foo1, foo2, foo3");
+ setOK = metadata.getHeader("Empty");
+ do_check_eq(setOK, "");
+ setOK = metadata.getHeader("ReplaceWithEmpty");
+ do_check_eq(setOK, "");
+ setOK = metadata.getHeader("MergeWithEmpty");
+ do_check_eq(setOK, "foo");
+ setOK = metadata.getHeader("Referer");
+ do_check_eq(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"); }
+
+ do_check_eq(channel.responseStatus, 200);
+ do_check_eq(channel.responseStatusText, "OK");
+ do_check_true(channel.requestSucceeded);
+
+ var response = channel.getResponseHeader("httpdMerge");
+ do_check_eq(response, "bar1,bar2,bar3");
+ channel.setResponseHeader("httpdMerge", "bar", true);
+ do_check_eq(channel.getResponseHeader("httpdMerge"), "bar1,bar2,bar3, bar");
+
+ response = channel.getResponseHeader("Proxy-Authenticate");
+ do_check_eq(response, "line 1\nline 2\nline 3");
+
+ channel.contentCharset = "UTF-8";
+ do_check_eq(channel.contentCharset, "UTF-8");
+ do_check_eq(channel.contentType, "text/plain");
+ do_check_eq(channel.contentLength, httpbody.length);
+ do_check_eq(data, httpbody);
+
+ httpserver.stop(do_test_finished);
+ if (dbg) { print("============== checkRequestResponse: out"); }
+}
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..63ea2552a3
--- /dev/null
+++ b/netwerk/test/unit/test_header_Accept-Language.js
@@ -0,0 +1,91 @@
+//
+// HTTP Accept-Language header test
+//
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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 acceptedLanguage, qualityValue;
+
+ try {
+ // The q-value must conform to the definition in HTTP/1.1 Section 3.9.
+ [_, acceptedLanguage, 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.
+ do_check_eq(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) {
+ do_check_true(qualityValue.length == 3);
+
+ decimalPlaces = 1;
+ } else {
+ do_check_true(qualityValue.length >= 3);
+ do_check_true(qualityValue.length <= 4);
+
+ decimalPlaces = 2;
+ }
+
+ // All the other languages should have an evenly-spaced quality value.
+ do_check_eq(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..3ed901ab56
--- /dev/null
+++ b/netwerk/test/unit/test_header_Accept-Language_case.js
@@ -0,0 +1,47 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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"],
+ ];
+
+ 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, "", null);
+ let chan = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
diff --git a/netwerk/test/unit/test_headers.js b/netwerk/test/unit/test_headers.js
new file mode 100644
index 0000000000..d9fecc11e9
--- /dev/null
+++ b/netwerk/test/unit/test_headers.js
@@ -0,0 +1,186 @@
+//
+// 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
+
+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
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+var nextTest = firstTest;
+var test_flags = new Array();
+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 (eval("handler" + nextTest) == undefined)
+ do_throw("handler" + nextTest + " undefined!");
+ if (eval("completeTest" + nextTest) == undefined)
+ do_throw("completeTest" + nextTest + " undefined!");
+
+ run_test_number(nextTest);
+}
+
+function run_test_number(num)
+{
+ testPath = testPathBase + num;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen2(new ChannelListener(eval("completeTest" + num),
+ channel, flags));
+}
+
+function setupChannel(url)
+{
+ var chan = NetUtil.newChannel({
+ uri: URL + url,
+ loadUsingSystemPrincipal: true
+ });
+ var httpChan = chan.QueryInterface(Components.interfaces.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);
+ var body = "foo";
+}
+
+function completeTest1(request, data, ctx)
+{
+ try {
+ var chan = request.QueryInterface(Ci.nsIChannel);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ do_check_eq(chan.contentDispositionFilename, "foo");
+ do_check_eq(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);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ do_check_eq(chan.contentDispositionHeader, "attachment");
+
+ filename = chan.contentDispositionFilename; // should barf
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ do_print("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);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+ do_check_eq(chan.contentDispositionHeader, "attachment; filename=");
+
+ filename = chan.contentDispositionFilename; // should barf
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ do_print("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);
+ do_check_eq(chan.contentDisposition, chan.DISPOSITION_INLINE);
+ do_check_eq(chan.contentDispositionHeader, "inline");
+
+ filename = chan.contentDispositionFilename; // should barf
+ do_throw("Should have failed getting Content-Disposition filename");
+ } catch (ex) {
+ do_print("correctly ate exception");
+ }
+ runNextTest();
+}
diff --git a/netwerk/test/unit/test_http2.js b/netwerk/test/unit/test_http2.js
new file mode 100644
index 0000000000..3fefe707e7
--- /dev/null
+++ b/netwerk/test/unit/test_http2.js
@@ -0,0 +1,1119 @@
+// test HTTP/2
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+// 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, ctx) {
+ 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!");
+ }
+
+ do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+ do_check_eq(request.requestSucceeded, this.shouldSucceed);
+ if (this.shouldSucceed) {
+ do_check_eq(request.responseStatus, 200);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.accum += cnt;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ if (this.expected != -1) {
+ do_check_eq(this.accum, this.expected);
+ }
+ if (this.shouldSucceed) {
+ do_check_true(Components.isSuccessCode(status));
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection == this.shouldBeHttp2);
+ } else {
+ do_check_false(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) {
+ do_check_neq(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, ctx, 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, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection);
+ do_check_true(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, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ var hvalue = request.getResponseHeader(this.name);
+ do_check_neq(hvalue, "");
+ this.callback(hvalue);
+ read_stream(stream, cnt);
+};
+
+var Http2PushListener = function() {};
+
+Http2PushListener.prototype = new Http2CheckListener();
+
+Http2PushListener.prototype.onDataAvailable = function(request, ctx, 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") {
+ do_check_eq(request.getResponseHeader("pushed"), "yes");
+ }
+ 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++) {
+ do_check_eq(getHeader(headerPrefix + 1), headerText);
+ }
+}
+
+var Http2ContinuedHeaderListener = function() {};
+
+Http2ContinuedHeaderListener.prototype = new Http2CheckListener();
+
+Http2ContinuedHeaderListener.prototype.onStopsLeft = 2;
+
+Http2ContinuedHeaderListener.prototype.QueryInterface = function (aIID) {
+ if (aIID.equals(Ci.nsIHttpPushListener) ||
+ aIID.equals(Ci.nsIStreamListener))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+};
+
+Http2ContinuedHeaderListener.prototype.getInterface = function(aIID) {
+ return this.QueryInterface(aIID);
+};
+
+Http2ContinuedHeaderListener.prototype.onDataAvailable = function (request, ctx, 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, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(Components.isSuccessCode(status));
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection);
+
+ --this.onStopsLeft;
+ if (this.onStopsLeft === 0) {
+ run_next_test();
+ do_test_finished();
+ }
+};
+
+Http2ContinuedHeaderListener.prototype.onPush = function(associatedChannel, pushChannel) {
+ do_check_eq(associatedChannel.originalURI.spec, "https://localhost:" + serverPort + "/continuedheaders");
+ do_check_eq(pushChannel.getRequestHeader("x-pushed-request"), "true");
+ checkContinuedHeaders(pushChannel.getRequestHeader, "X-Push-Test-Header-", pushHdrTxt);
+
+ pushChannel.asyncOpen2(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, ctx, 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
+ do_check_eq(bigListenerMD5, request.getResponseHeader("X-Expected-MD5"));
+};
+
+Http2BigListener.prototype.onStopRequest = function(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection);
+
+ // Don't want to flood output, so don't use do_check_eq
+ do_check_true(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, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.count += cnt;
+ read_stream(stream, cnt);
+};
+
+Http2HugeSuspendedListener.prototype.onStopRequest = function(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(this.isHttp2Connection);
+ do_check_eq(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, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ read_stream(stream, cnt);
+ do_check_eq(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, ctx) {
+ this.onStartRequestFired = true;
+ if (!Components.isSuccessCode(request.status))
+ do_throw("Channel should have a success code! (" + request.status + ")");
+
+ do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(request.requestSucceeded, true);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, ctx, status) {
+ do_check_true(this.onStartRequestFired);
+ do_check_true(Components.isSuccessCode(status));
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(listener);
+}
+
+// Support for making sure XHR works over SPDY
+function checkXhr(xhr) {
+ if (xhr.readyState != 4) {
+ return;
+ }
+
+ do_check_eq(xhr.status, 200);
+ do_check_eq(checkIsHttp2(xhr), true);
+ run_next_test();
+ do_test_finished();
+}
+
+// Fires off an XHR request over h2
+function test_http2_xhr() {
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ req.open("GET", "https://localhost:" + serverPort + "/", true);
+ req.addEventListener("readystatechange", function (evt) { checkXhr(req); },
+ false);
+ 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, ctx, status) {
+ this.count++;
+ do_check_true(this.isHttp2Connection);
+ if (this.recvdHdr > 0) {
+ do_check_eq(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].asyncOpen2(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].asyncOpen2(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.asyncOpen2(listener1);
+ chan2.asyncOpen2(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) {
+ do_check_eq(received_hvalue, hvalue);
+ });
+ chan.asyncOpen2(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();
+ do_check_eq(cookiesReceived.length, cookiesSent.length);
+ cookiesReceived.forEach(function(cookieReceived, index) {
+ do_check_eq(cookiesSent[index], cookieReceived)
+ });
+ });
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push1() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push2() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push3() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push2");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push4() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push2.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push5() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push5");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_push6() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push5.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener();
+ chan.asyncOpen2(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.asyncOpen2(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.asyncOpen2(listener);
+}
+
+function test_http2_huge_suspended() {
+ var chan = makeChan("https://localhost:" + serverPort + "/huge");
+ var listener = new Http2HugeSuspendedListener();
+ chan.asyncOpen2(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.asyncOpen2(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");
+}
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv = null;
+var httpserv2 = null;
+var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+var altsvcClientListener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ do_check_eq(request.status, Components.results.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, cx, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ var isHttp2Connection = checkIsHttp2(request.QueryInterface(Components.interfaces.nsIHttpChannel));
+ if (!isHttp2Connection) {
+ dump("/altsvc1 not over h2 yet - retry\n");
+ var chan = makeChan("http://foo.example.com:" + httpserv.identity.primaryPort + "/altsvc1")
+ .QueryInterface(Components.interfaces.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.asyncOpen2(altsvcClientListener);
+ } else {
+ do_check_true(isHttp2Connection);
+ var chan = makeChan("http://foo.example.com:" + httpserv2.identity.primaryPort + "/altsvc2")
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen2(altsvcClientListener2);
+ }
+ }
+};
+
+var altsvcClientListener2 = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ do_check_eq(request.status, Components.results.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, cx, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ var isHttp2Connection = checkIsHttp2(request.QueryInterface(Components.interfaces.nsIHttpChannel));
+ if (!isHttp2Connection) {
+ dump("/altsvc2 not over h2 yet - retry\n");
+ var chan = makeChan("http://foo.example.com:" + httpserv2.identity.primaryPort + "/altsvc2")
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen2(altsvcClientListener2);
+ } else {
+ do_check_true(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 + '": { "tls-ports": [' + serverPort + '] }}';
+ 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 + '": { "tls-ports": [' + serverPort + '] }}';
+ response.bodyOutputStream.write(body, body.length);
+}
+function test_http2_altsvc() {
+ var chan = makeChan("http://foo.example.com:" + httpserv.identity.primaryPort + "/altsvc1")
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.asyncOpen2(altsvcClientListener);
+}
+
+var Http2PushApiListener = function() {};
+
+Http2PushApiListener.prototype = {
+ checksPending: 9, // 4 onDataAvailable and 5 onStop
+
+ getInterface: function(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIHttpPushListener) ||
+ aIID.equals(Ci.nsIStreamListener))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIHttpPushListener
+ onPush: function onPush(associatedChannel, pushChannel) {
+ do_check_eq(associatedChannel.originalURI.spec, "https://localhost:" + serverPort + "/pushapi1");
+ do_check_eq (pushChannel.getRequestHeader("x-pushed-request"), "true");
+
+ pushChannel.asyncOpen2(this);
+ if (pushChannel.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1/2") {
+ pushChannel.cancel(Components.results.NS_ERROR_ABORT);
+ }
+ },
+
+ // normal Channel listeners
+ onStartRequest: function pushAPIOnStart(request, ctx) {
+ },
+
+ onDataAvailable: function pushAPIOnDataAvailable(request, ctx, stream, offset, cnt) {
+ do_check_neq(request.originalURI.spec, "https://localhost:" + serverPort + "/pushapi1/2");
+
+ var data = read_stream(stream, cnt);
+
+ if (request.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1") {
+ do_check_eq(data[0], '0');
+ --this.checksPending;
+ } else if (request.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1/1") {
+ do_check_eq(data[0], '1');
+ --this.checksPending; // twice
+ } else if (request.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1/3") {
+ do_check_eq(data[0], '3');
+ --this.checksPending;
+ } else {
+ do_check_eq(true, false);
+ }
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ if (request.originalURI.spec == "https://localhost:" + serverPort + "/pushapi1/2") {
+ do_check_eq(request.status, Components.results.NS_ERROR_ABORT);
+ } else {
+ do_check_eq(request.status, Components.results.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
+
+function test_http2_pushapi_1() {
+ var chan = makeChan("https://localhost:" + serverPort + "/pushapi1");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushApiListener();
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen2(listener);
+}
+
+var WrongSuiteListener = function() {};
+WrongSuiteListener.prototype = new Http2CheckListener();
+WrongSuiteListener.prototype.shouldBeHttp2 = false;
+WrongSuiteListener.prototype.onStopRequest = function(request, ctx, status) {
+ prefs.setBoolPref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", true);
+ Http2CheckListener.prototype.onStopRequest.call(this);
+};
+
+// test that we use h1 without the mandatory cipher suite available
+function test_http2_wrongsuite() {
+ 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();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_h11required_stream() {
+ var chan = makeChan("https://localhost:" + serverPort + "/h11required_stream");
+ var listener = new Http2CheckListener();
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen2(listener);
+}
+
+function H11RequiredSessionListener () { }
+H11RequiredSessionListener.prototype = new Http2CheckListener();
+
+H11RequiredSessionListener.prototype.onStopRequest = function (request, ctx, status) {
+ var streamReused = request.getResponseHeader("X-H11Required-Stream-Ok");
+ do_check_eq(streamReused, "yes");
+
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(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.asyncOpen2(listener);
+}
+
+function test_http2_retry_rst() {
+ var chan = makeChan("https://localhost:" + serverPort + "/rstonce");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_continuations() {
+ var chan = makeChan("https://localhost:" + serverPort + "/continuedheaders");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2ContinuedHeaderListener();
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen2(listener);
+}
+
+function Http2IllegalHpackValidationListener() { }
+Http2IllegalHpackValidationListener.prototype = new Http2CheckListener();
+Http2IllegalHpackValidationListener.prototype.shouldGoAway = false;
+
+Http2IllegalHpackValidationListener.prototype.onStopRequest = function (request, ctx, status) {
+ var wentAway = (request.getResponseHeader('X-Did-Goaway') === 'yes');
+ do_check_eq(wentAway, this.shouldGoAway);
+
+ do_check_true(this.onStartRequestFired);
+ do_check_true(this.onDataAvailableFired);
+ do_check_true(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, ctx, status) {
+ var chan = makeChan("https://localhost:" + serverPort + "/illegalhpack_validate");
+ var listener = new Http2IllegalHpackValidationListener();
+ listener.shouldGoAway = this.shouldGoAway;
+ chan.asyncOpen2(listener);
+};
+
+function test_http2_illegalhpacksoft() {
+ var chan = makeChan("https://localhost:" + serverPort + "/illegalhpacksoft");
+ var listener = new Http2IllegalHpackListener();
+ listener.shouldGoAway = false;
+ listener.shouldSucceed = false;
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_illegalhpackhard() {
+ var chan = makeChan("https://localhost:" + serverPort + "/illegalhpackhard");
+ var listener = new Http2IllegalHpackListener();
+ listener.shouldGoAway = true;
+ listener.shouldSucceed = false;
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_folded_header() {
+ var chan = makeChan("https://localhost:" + serverPort + "/foldedheader");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2CheckListener();
+ listener.shouldSucceed = false;
+ chan.asyncOpen2(listener);
+}
+
+function test_http2_empty_data() {
+ var chan = makeChan("https://localhost:" + serverPort + "/emptydata");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen2(listener);
+}
+
+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);
+}
+
+// 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
+ // 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
+
+ // 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();
+ }
+}
+
+// Support for making sure we can talk to the invalid cert the server presents
+var CertOverrideListener = function(host, port, bits) {
+ this.host = host;
+ if (port) {
+ this.port = port;
+ }
+ this.bits = bits;
+};
+
+CertOverrideListener.prototype = {
+ host: null,
+ port: -1,
+ bits: null,
+
+ getInterface: function(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIBadCertListener2) ||
+ aIID.equals(Ci.nsIInterfaceRequestor) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
+ var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+ var cos = Cc["@mozilla.org/security/certoverride;1"].
+ getService(Ci.nsICertOverrideService);
+ cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
+ dump("Certificate Override in place\n");
+ return true;
+ },
+};
+
+function addCertOverride(host, port, bits) {
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ try {
+ var url;
+ if (port) {
+ url = "https://" + host + ":" + port + "/";
+ } else {
+ url = "https://" + host + "/";
+ }
+ req.open("GET", url, false);
+ req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
+ req.send(null);
+ } catch (e) {
+ // This will fail since the server is not trusted yet
+ }
+}
+
+var prefs;
+var spdypref;
+var spdypush;
+var http2pref;
+var tlspref;
+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.spdy.enforce-tls-profile", tlspref);
+ prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
+ prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ serverPort = env.get("MOZHTTP2_PORT");
+ do_check_neq(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 CA.cert.der
+ // 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, "CA.cert.der", "CTu,u,u");
+
+ addCertOverride("localhost", serverPort,
+ Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH |
+ Ci.nsICertOverrideService.ERROR_TIME);
+
+ // 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");
+ tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+ 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.v3-1", true);
+ prefs.setBoolPref("network.http.spdy.allow-push", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+ 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");
+
+ 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();
+}
+
+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 der = readFile(certFile);
+ certdb.addCert(der, trustString, null);
+}
diff --git a/netwerk/test/unit/test_httpResponseTimeout.js b/netwerk/test/unit/test_httpResponseTimeout.js
new file mode 100644
index 0000000000..3a40b0a089
--- /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";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function (request, ctx) {
+ },
+
+ onDataAvailable: function (request, ctx, stream) {
+ },
+
+ onStopRequest: function (request, ctx, status) {
+ if (this.expectResponse) {
+ do_check_eq(status, Cr.NS_OK);
+ } else {
+ do_check_eq(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.asyncOpen2(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);
+ do_register_cleanup(function() {
+ prefService.setBoolPref(kShortLivedKeepalivePref, true);
+ });
+ }
+ if (prefService.getBoolPref(kLongLivedKeepalivePref)) {
+ prefService.setBoolPref(kLongLivedKeepalivePref, false);
+ do_register_cleanup(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 + "/";
+ do_print("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();
+ });
+ });
+ do_register_cleanup(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..586e064aac
--- /dev/null
+++ b/netwerk/test/unit/test_http_headers.js
@@ -0,0 +1,70 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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");
+ }
+ do_check_eq(chanValue, value);
+}
+
+function run_test() {
+
+ var chan = NetUtil.newChannel ({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.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_httpauth.js b/netwerk/test/unit/test_httpauth.js
new file mode 100644
index 0000000000..65846710e1
--- /dev/null
+++ b/netwerk/test/unit/test_httpauth.js
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure the HTTP authenticated sessions are correctly cleared
+// when entering and leaving the private browsing mode.
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+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);
+ do_check_eq(domain.value, kDomain);
+ do_check_eq(user.value, kUser);
+ do_check_eq(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) {
+ do_check_eq(domain.value, kEmpty);
+ do_check_eq(user.value, kEmpty);
+ do_check_eq(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);
+ do_check_eq(domain.value, kDomain);
+ do_check_eq(user.value, kUser2);
+ do_check_eq(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) {
+ do_check_eq(domain.value, kEmpty);
+ do_check_eq(user.value, kEmpty);
+ do_check_eq(pass.value, kEmpty);
+ }
+
+ // simulate leaving private browsing mode
+ Services.obs.notifyObservers(null, "last-pb-context-exited", null);
+
+ // 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) {
+ do_check_eq(domain.value, kEmpty);
+ do_check_eq(user.value, kEmpty);
+ do_check_eq(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) {
+ do_check_eq(domain.value, kEmpty);
+ do_check_eq(user.value, kEmpty);
+ do_check_eq(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..49188ca1ee
--- /dev/null
+++ b/netwerk/test/unit/test_httpcancel.js
@@ -0,0 +1,114 @@
+// 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:
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+var observer = {
+ QueryInterface: function eventsink_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIObserver))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(subject, topic, data) {
+ subject = subject.QueryInterface(Components.interfaces.nsIRequest);
+ subject.cancel(Components.results.NS_BINDING_ABORTED);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: setting values should still work
+ try {
+ subject.QueryInterface(Components.interfaces.nsIHttpChannel);
+ currentReferrer = subject.getRequestHeader("Referer");
+ do_check_eq(currentReferrer, "http://site1.com/");
+ var uri = ios.newURI("http://site2.com", null, null);
+ subject.referrer = uri;
+ } catch (ex) {
+ do_throw("Exception: " + ex);
+ }
+
+ var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
+ obs.removeObserver(observer, "http-on-modify-request");
+ }
+};
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ do_check_eq(request.status, Components.results.NS_BINDING_ABORTED);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: setting referrer should now fail
+ try {
+ request.QueryInterface(Components.interfaces.nsIHttpChannel);
+ currentReferrer = request.getRequestHeader("Referer");
+ do_check_eq(currentReferrer, "http://site2.com/");
+ var uri = ios.newURI("http://site3.com/", null, null);
+
+ // Need to set NECKO_ERRORS_ARE_FATAL=0 else we'll abort process
+ var env = Components.classes["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+ env.set("NECKO_ERRORS_ARE_FATAL", "0");
+ // we expect setting referrer to fail
+ try {
+ request.referrer = 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, ctx, status) {
+ httpserv.stop(do_test_finished);
+ }
+};
+
+function makeChan(url) {
+ var chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Components.interfaces.nsIHttpChannel);
+
+ // ENSURE_CALLED_BEFORE_CONNECT: set original value
+ var uri = ios.newURI("http://site1.com", null, null);
+ chan.referrer = uri;
+
+ return chan;
+}
+
+var httpserv = null;
+
+function execute_test() {
+ var chan = makeChan("http://localhost:" +
+ httpserv.identity.primaryPort + "/failtest");
+
+ var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
+ obs.addObserver(observer, "http-on-modify-request", false);
+
+ chan.asyncOpen2(listener);
+}
+
+function run_test() {
+ httpserv = new HttpServer();
+ httpserv.registerPathHandler("/failtest", failtest);
+ httpserv.start(-1);
+
+ execute_test();
+
+ do_test_pending();
+}
+
+// PATHS
+
+// /failtest
+function failtest(metadata, response) {
+ do_throw("This should not be reached");
+}
diff --git a/netwerk/test/unit/test_httpsuspend.js b/netwerk/test/unit/test_httpsuspend.js
new file mode 100644
index 0000000000..7d0b1326f6
--- /dev/null
+++ b/netwerk/test/unit/test_httpsuspend.js
@@ -0,0 +1,80 @@
+// This file ensures that suspending a channel directly after opening it
+// suspends future notifications correctly.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, ctx) {
+ 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: function(request, context, stream, offset, count) {
+ do_check_true(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: function(request, ctx, status) {
+ do_check_true(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.asyncOpen2(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..5ca0173bb2
--- /dev/null
+++ b/netwerk/test/unit/test_idn_blacklist.js
@@ -0,0 +1,170 @@
+// Test that URLs containing characters in the IDN blacklist are
+// always displayed as punycode
+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;
+ try {
+ oldWhitelistCom = pbi.getBoolPref("network.IDN.whitelist.com");
+ } catch(e) {
+ oldWhitelistCom = 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..06a53032b4
--- /dev/null
+++ b/netwerk/test/unit/test_idn_urls.js
@@ -0,0 +1,345 @@
+// Test algorithm for unicode display of IDNA URL (bug 722299)
+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]
+];
+
+
+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;
+ try {
+ oldWhitelistCom = pbi.getBoolPref("network.IDN.whitelist.com");
+ } catch(e) {
+ oldWhitelistCom = 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--") {
+ // test convertToDisplayIDN with a Unicode URL and with a
+ // Punycode URL if we have one
+ do_check_eq(escape(result),
+ expectedUnicode ? escape(URL) : escape(punycodeURL));
+
+ result = idnService.convertToDisplayIDN(punycodeURL, isASCII);
+ do_check_eq(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
+ do_check_eq(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..9221a0e605
--- /dev/null
+++ b/netwerk/test/unit/test_idna2008.js
@@ -0,0 +1,60 @@
+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\200cn",
+ // U+200D ZERO WIDTH JOINER in Latin script
+ "p\200dq",
+];
+
+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 = Components.classes["@mozilla.org/network/idn-service;1"]
+ .getService(Components.interfaces.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..e6d659857b
--- /dev/null
+++ b/netwerk/test/unit/test_idnservice.js
@@ -0,0 +1,25 @@
+// Tests nsIIDNService
+
+var reference = [
+ // The 3rd element indicates whether the second element
+ // is ACE-encoded
+ ["asciihost", "asciihost", false],
+ ["b\u00FCcher", "xn--bcher-kva", true]
+ ];
+
+function run_test() {
+ var idnService = Components.classes["@mozilla.org/network/idn-service;1"]
+ .getService(Components.interfaces.nsIIDNService);
+
+ 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)
+ do_check_eq(idnService.convertUTF8toACE(reference[i][0]), reference[i][1]);
+ do_check_eq(idnService.convertUTF8toACE(reference[i][1]), reference[i][1]);
+ do_check_eq(idnService.convertACEtoUTF8(reference[i][1]), reference[i][0]);
+ do_check_eq(idnService.isACE(reference[i][1]), reference[i][2]);
+ }
+}
diff --git a/netwerk/test/unit/test_immutable.js b/netwerk/test/unit/test_immutable.js
new file mode 100644
index 0000000000..d3438bbe11
--- /dev/null
+++ b/netwerk/test/unit/test_immutable.js
@@ -0,0 +1,180 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var prefs;
+var spdypref;
+var http2pref;
+var tlspref;
+var origin;
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ var h2Port = env.get("MOZHTTP2_PORT");
+ do_check_neq(h2Port, null);
+ do_check_neq(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");
+ tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+ prefs.setCharPref("network.dns.localDomains", "foo.example.com, bar.example.com");
+
+ // The moz-http2 cert is for foo.example.com and is signed by CA.cert.der
+ // 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, "CA.cert.der", "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.spdy.enforce-tls-profile", tlspref);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+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 der = readFile(certFile);
+ certdb.addCert(der, trustString, null);
+}
+
+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, ctx) {
+ do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+
+ if (expectPass) {
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+ do_check_eq(request.responseStatus, 200);
+ } else {
+ do_check_eq(Components.isSuccessCode(request.status), false);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, ctx, status) {
+ if (expectConditional) {
+ do_check_eq(request.getResponseHeader("x-conditional"), "true");
+ } else {
+ try { do_check_neq(request.getResponseHeader("x-conditional"), "true"); }
+ catch (e) { do_check_true(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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..7e81eb6968
--- /dev/null
+++ b/netwerk/test/unit/test_inhibit_caching.js
@@ -0,0 +1,76 @@
+Cu.import('resource://gre/modules/LoadContextInfo.jsm');
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(new ChannelListener(check_first_response, null));
+}
+
+// Checks that we got the appropriate response
+function check_first_response(request, buffer) {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(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.asyncOpen2(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);
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(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_large_port.js b/netwerk/test/unit/test_large_port.js
new file mode 100644
index 0000000000..d2480582f2
--- /dev/null
+++ b/netwerk/test/unit/test_large_port.js
@@ -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/. */
+
+// Ensure that non-16-bit URIs are rejected
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+const StandardURL = Components.Constructor("@mozilla.org/network/standard-url;1",
+ "nsIStandardURL",
+ "init");
+function run_test()
+{
+ // Bug 1301621 makes invalid ports throw
+ Assert.throws(() => {
+ new StandardURL(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 65536,
+ "http://localhost", "UTF-8", null)
+ }, "invalid port during creation");
+ let url = new StandardURL(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 65535,
+ "http://localhost", "UTF-8", null)
+ .QueryInterface(Ci.nsIStandardURL)
+
+ Assert.throws(() => {
+ url.setDefaultPort(65536);
+ }, "invalid port in setDefaultPort");
+ Assert.throws(() => {
+ url.port = 65536;
+ }, "invalid port in port setter");
+
+ do_check_eq(url.QueryInterface(Ci.nsIURI).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.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_localstreams.js b/netwerk/test/unit/test_localstreams.js
new file mode 100644
index 0000000000..8e926f5712
--- /dev/null
+++ b/netwerk/test/unit/test_localstreams.js
@@ -0,0 +1,87 @@
+// Tests bug 304414
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.
+ do_check_eq(stream.isNonBlocking(), false);
+
+ // Check that the stream is not buffered
+ do_check_eq(Components.classes["@mozilla.org/io-util;1"]
+ .getService(Components.interfaces.nsIIOUtil)
+ .inputStreamIsBuffered(stream),
+ false);
+
+ // Wrap it in a binary stream (to avoid wrong results that
+ // scriptablestream would produce with binary content)
+ var binstream = Components.classes['@mozilla.org/binaryinputstream;1']
+ .createInstance(Components.interfaces.nsIBinaryInputStream);
+ binstream.setInputStream(stream);
+
+ var numread = 0;
+ for (;;) {
+ do_check_eq(stream.available(), binstream.available());
+ var avail = stream.available();
+ do_check_neq(avail, -1);
+
+ // PR_UINT32_MAX and PR_INT32_MAX; the files we're testing with aren't that
+ // large.
+ do_check_neq(avail, Math.pow(2, 32) - 1);
+ do_check_neq(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
+ var data = binstream.readByteArray(avail);
+
+ numread += avail;
+ }
+ return numread;
+}
+
+function stream_for_file(file) {
+ var str = Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ str.init(file, PR_RDONLY, 0, 0);
+ return str;
+}
+
+function stream_from_channel(file) {
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var uri = ios.newFileURI(file);
+ return NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ }).open2();
+}
+
+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;
+ do_check_eq(test_stream(stream_for_file(file)), len);
+ do_check_eq(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..f675a123fb
--- /dev/null
+++ b/netwerk/test/unit/test_mismatch_last-modified.js
@@ -0,0 +1,154 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function test_onStartR(request, ctx) {},
+
+ onDataAvailable: function test_ODA(request, cx, inputStream,
+ offset, count) {
+ var data = new BinaryInputStream(inputStream).readByteArray(count);
+
+ do_check_eq(data[0], "B".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, 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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function test_onStartR(request, ctx) {},
+
+ onDataAvailable: function test_ODA(request, cx, 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
+
+ do_check_eq(data[0], "A".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ var channel = request.QueryInterface(Ci.nsIHttpChannel);
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/test1",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function test_onStartR(request, ctx) {},
+
+ onDataAvailable: function test_ODA(request, cx, inputStream,
+ offset, count) {
+ var data = new BinaryInputStream(inputStream).readByteArray(count);
+ do_check_eq(data[0], "A".charCodeAt(0));
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ var channel = request.QueryInterface(Ci.nsIHttpChannel);
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/test1",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(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.asyncOpen2(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..075323d7ca
--- /dev/null
+++ b/netwerk/test/unit/test_mozTXTToHTMLConv.js
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that mozITXTToHTMLConv works properly.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+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"
+ },
+ // -- RFC2396E
+ {
+ input: "RFC2396E: <http://mozilla.org/> then",
+ url: "http://mozilla.org/"
+ },
+ // -- abbreviated
+ {
+ input: "see www.mozilla.org maybe",
+ url: "http://www.mozilla.org"
+ },
+ // -- 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: "parenthesis: (http://localhost/) etc.",
+ url: "http://localhost/"
+ },
+ {
+ input: "(thunderbird)http://mozilla.org/thunderbird",
+ url: "http://mozilla.org/thunderbird"
+ },
+ {
+ 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"
+ }
+ ];
+
+ 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 + '"';
+ }
+
+ 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);
+ if (output.indexOf(link) == -1)
+ do_throw("Unexpected conversion by scanTXT: input=" + t.input +
+ ", output=" + output + ", link=" + link);
+ }
+
+ 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..367615fff9
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_byteranges.js
@@ -0,0 +1,113 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(buffer, testData[testNum].data);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type);
+ do_check_eq(request.QueryInterface(Ci.nsIByteRangeRequest).isByteRangeRequest,
+ testData[testNum].isByteRangeRequest);
+ do_check_eq(request.QueryInterface(Ci.nsIByteRangeRequest).startRange,
+ testData[testNum].startRange);
+ do_check_eq(request.QueryInterface(Ci.nsIByteRangeRequest).endRange,
+ testData[testNum].endRange);
+ if (++testNum == numTests)
+ httpserver.stop(do_test_finished);
+}
+
+var multipartListener = {
+ _buffer: "",
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, context, 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: function(request, context, 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.asyncOpen2(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..bab964ee8e
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js
@@ -0,0 +1,109 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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\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.0'?><root/>", type: "text/xml" }
+ ];
+
+function responseHandler(request, buffer)
+{
+ do_check_eq(buffer, testData[testNum].data);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type);
+ if (++testNum == numTests)
+ httpserver.stop(do_test_finished);
+}
+
+var multipartListener = {
+ _buffer: "",
+ _index: 0,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, context, 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: function(request, context, status) {
+ this._index++;
+ // Second part should be last part
+ do_check_eq(request.QueryInterface(Ci.nsIMultiPartChannel).isLastPart, this._index == 2);
+ 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.asyncOpen2(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..c35628faa4
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv.js
@@ -0,0 +1,93 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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\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)
+{
+ do_check_eq(buffer, testData[testNum].data);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type);
+ if (++testNum == numTests)
+ httpserver.stop(do_test_finished);
+}
+
+var multipartListener = {
+ _buffer: "",
+ _index: 0,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, context, 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: function(request, context, status) {
+ this._index++;
+ // Second part should be last part
+ do_check_eq(request.QueryInterface(Ci.nsIMultiPartChannel).isLastPart, this._index == 2);
+ 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.asyncOpen2(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..97924ccb16
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js
@@ -0,0 +1,89 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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\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)
+{
+ do_check_eq(buffer, testData[testNum].data);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,
+ testData[testNum].type);
+ if (++testNum == numTests)
+ httpserver.stop(do_test_finished);
+}
+
+var multipartListener = {
+ _buffer: "",
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ this._buffer = "";
+ },
+
+ onDataAvailable: function(request, context, 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: function(request, context, 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.asyncOpen2(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..58fff91c4f
--- /dev/null
+++ b/netwerk/test/unit/test_nestedabout_serialize.js
@@ -0,0 +1,35 @@
+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", null);
+
+ // 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);
+
+ do_check_eq(copy.asciiSpec, aboutLicense.asciiSpec);
+ do_check_true(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..732ecd42fc
--- /dev/null
+++ b/netwerk/test/unit/test_net_addr.js
@@ -0,0 +1,199 @@
+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;
+ do_print('server: listening on ' + this.port);
+ this.listener.asyncListen(this);
+}
+
+TestServer.prototype = {
+ onSocketAccepted: function(socket, trans) {
+ do_print('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: function(socket) {} ,
+
+ /**
+ * Called to close a connection and clean up properties.
+ */
+ reset: function() {
+ 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: function() {
+ this.reset();
+ try { this.listener.close(); } catch(ignore) {}
+ }
+};
+
+
+/**
+ * Helper function.
+ * Compares two nsINetAddr objects and ensures they are logically equivalent.
+ */
+function checkAddrEqual(lhs, rhs) {
+ do_check_eq(lhs.family, rhs.family);
+
+ if (lhs.family === Ci.nsINetAddr.FAMILY_INET) {
+ do_check_eq(lhs.address, rhs.address);
+ do_check_eq(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
+ do_check_eq(peerAddr.family, Ci.nsINetAddr.FAMILY_INET);
+ do_check_eq(peerAddr.port, testDataStore.transport.port);
+ do_check_eq(peerAddr.port, serv.port);
+ do_check_eq(peerAddr.address, "127.0.0.1");
+
+ // check selfAddr against expected values
+ do_check_eq(selfAddr.family, Ci.nsINetAddr.FAMILY_INET);
+ do_check_eq(selfAddr.address, "127.0.0.1");
+
+ // check that selfAddr = server.peerAddr and vice versa.
+ checkAddrEqual(selfAddr, serv.peerAddr);
+ checkAddrEqual(peerAddr, serv.selfAddr);
+
+ testDataStore = null;
+ do_execute_soon(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(null, 0, '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();
+
+ do_register_cleanup(function(){ serv.stop(); });
+
+ add_test(testIpv4);
+ /* TODO: testIpv6 */
+ /* TODO: testLocal */
+
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_nojsredir.js b/netwerk/test/unit/test_nojsredir.js
new file mode 100644
index 0000000000..d61c41a183
--- /dev/null
+++ b/netwerk/test/unit/test_nojsredir.js
@@ -0,0 +1,62 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(new ChannelListener(completeIter, channel, tests[index].flags));
+}
+
+function completeIter(request, data, ctx) {
+ do_check_true(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..72169cc243
--- /dev/null
+++ b/netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js
@@ -0,0 +1,179 @@
+function run_test() { run_next_test(); }
+
+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.
+ do_check_eq(pipe.outputStream.write('xy', 2), 1);
+ do_check_eq(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.
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(inPipe.outputStream.write('y', 1), 1);
+
+ do_check_eq(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.
+ do_check_eq(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.
+ do_check_eq(inPipe.outputStream.write('0123456789abcde', 15), 15);
+ do_check_eq(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.
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 11), 7);
+ do_check_eq(outPipe.inputStream.available(), 5);
+ do_check_eq(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.
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 5), 5);
+ do_check_eq(outPipe.inputStream.available(), 5);
+ do_check_eq(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.
+ do_check_eq(inPipe.inputStream.available(), 3);
+
+ // Push the rest of the data through, checking that it all came through.
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(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');
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(outPipeReadable.read(5), '56789');
+ buffered.flush();
+ do_check_eq(outPipeReadable.available(), 2);
+ do_check_eq(outPipeReadable.read(2), 'ab');
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 3), 3);
+ buffered.flush();
+ do_check_eq(outPipeReadable.available(), 3);
+ do_check_eq(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.
+ do_check_eq(inPipe.outputStream.write('0123456789abcde', 15), 15);
+ do_check_eq(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.
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 5), 5);
+ buffered.flush();
+ do_check_eq(outPipe.inputStream.available(), 5);
+ do_check_eq(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.
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 10), 7);
+ do_check_eq(outPipe.inputStream.available(), 5);
+ do_check_eq(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.
+ do_check_eq(inPipe.inputStream.available(), 3);
+
+ // Push the rest of the data through, checking that it all came through.
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(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');
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(outPipeReadable.read(5), '56789');
+ do_check_eq(buffered.writeFrom(inPipe.inputStream, 3), 3);
+ buffered.flush();
+ do_check_eq(outPipeReadable.available(), 5);
+ do_check_eq(outPipeReadable.read(5), 'abcde');
+
+ run_next_test();
+});
diff --git a/netwerk/test/unit/test_offline_status.js b/netwerk/test/unit/test_offline_status.js
new file mode 100644
index 0000000000..ac462a540c
--- /dev/null
+++ b/netwerk/test/unit/test_offline_status.js
@@ -0,0 +1,15 @@
+function run_test() {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ try {
+ var linkService = Components.classes["@mozilla.org/network/network-link-service;1"]
+ .getService(Components.interfaces.nsINetworkLinkService);
+
+ // The offline status should depends on the link status
+ do_check_neq(ioService.offline, linkService.isLinkUp);
+ } catch (e) {
+ // The network link service might not be available
+ do_check_eq(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..44e7957dc7
--- /dev/null
+++ b/netwerk/test/unit/test_offlinecache_custom-directory.js
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * 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
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+
+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, null, null);
+}
+
+// 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');
+ do_check_eq(indexSqlFile.exists(), true);
+
+ var file1 = offlineCacheDir.clone();
+ file1.append("2");
+ file1.append("E");
+ file1.append("2C99DE6E7289A5-0");
+ do_check_eq(file1.exists(), true);
+
+ var file2 = offlineCacheDir.clone();
+ file2.append("8");
+ file2.append("6");
+ file2.append("0B457F75198B29-0");
+ do_check_eq(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);
+ do_check_true(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.createCodebasePrincipal(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);
+ // Set this pref to mimic the default browser behavior.
+ ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, 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: function(update, state)
+ {
+ do_check_eq(state, expectedStates.shift());
+
+ if (state == Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED)
+ finish_test(customDir);
+ },
+
+ applicationCacheAvailable: function(appCache)
+ {
+ }
+ }, false);
+
+ do_test_pending();
+}
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..c4d02d5d22
--- /dev/null
+++ b/netwerk/test/unit/test_original_sent_received_head.js
@@ -0,0 +1,220 @@
+//
+// 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
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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 = Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
+
+ // ChannelListener defined in head_channels.js
+ channel1.asyncOpen2(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 = Components.interfaces.nsIRequest.LOAD_FROM_CACHE;
+
+ // ChannelListener defined in head_channels.js
+ channel2.asyncOpen2(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(Components.interfaces.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(Components.interfaces.nsIHttpChannel);
+ do_check_eq(request.responseStatus, 200);
+ do_check_eq(request.responseStatusText, "OK");
+ do_check_true(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++;
+ do_check_eq(aValue, "value1, value2");
+ }
+ if (aName == "Location") {
+ locationHeaderFound++;
+ do_check_eq(aValue, "loc");
+ }
+ }
+ });
+ do_check_eq(linkHeaderFound, 1);
+ do_check_eq(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) {
+ do_check_eq(aValue, "");
+ } else if (linkOrgHeaderFound == 1 ) {
+ do_check_eq(aValue, "value1");
+ } else {
+ do_check_eq(aValue, "value2");
+ }
+ linkOrgHeaderFound++;
+ }
+ if (aName == "Location") {
+ locationOrgHeaderFound++;
+ do_check_eq(aValue, "loc");
+ }
+ }
+ });
+ do_check_eq(linkOrgHeaderFound, 3);
+ do_check_eq(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;
+ }
+ }
+ });
+ do_check_false(linkHeaderFound2, "There should be no link header");
+ do_check_false(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) {
+ do_check_eq(aValue, "");
+ } else if (linkOrgHeaderFound2 == 1 ) {
+ do_check_eq(aValue, "value1");
+ } else {
+ do_check_eq(aValue, "value2");
+ }
+ linkOrgHeaderFound2++;
+ }
+ if (aName == "Location") {
+ locationOrgHeaderFound2++;
+ do_check_eq(aValue, "loc");
+ }
+ }
+ });
+ do_check_true(linkOrgHeaderFound2 == 3,
+ "Original link header still here.");
+ do_check_true(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) {
+ do_check_eq(aValue, "");
+ } else if (linkOrgHeaderFound3 == 1 ) {
+ do_check_eq(aValue, "value1");
+ } else {
+ do_check_eq(aValue, "value2");
+ }
+ linkOrgHeaderFound3++;
+ }
+ });
+ do_check_true(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..d804be673b
--- /dev/null
+++ b/netwerk/test/unit/test_parse_content_type.js
@@ -0,0 +1,200 @@
+/* -*- 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/. */
+
+var charset = {};
+var hadCharset = {};
+var type;
+
+function reset() {
+ delete charset.value;
+ delete hadCharset.value;
+ type = undefined;
+}
+
+function check(aType, aCharset, aHadCharset) {
+ do_check_eq(type, aType);
+ do_check_eq(aCharset, charset.value);
+ do_check_eq(aHadCharset, hadCharset.value);
+ reset();
+}
+
+function run_test() {
+ var netutil = Components.classes["@mozilla.org/network/util;1"]
+ .getService(Components.interfaces.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..fed8094834
--- /dev/null
+++ b/netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js
@@ -0,0 +1,84 @@
+/*
+
+ 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.
+
+*/
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+ }
+}
+
+var enforcePref;
+
+function run_test()
+{
+ enforcePref = Services.prefs.getBoolPref("network.http.enforce-framing.soft");
+ Services.prefs.setBoolPref("network.http.enforce-framing.soft", false);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var chan = make_channel(URL + "/content");
+ chan.asyncOpen2(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.asyncOpen2(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ Services.prefs.setBoolPref("network.http.enforce-framing.soft", enforcePref);
+ 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..0e735fc91c
--- /dev/null
+++ b/netwerk/test/unit/test_permmgr.js
@@ -0,0 +1,119 @@
+// tests nsIPermissionManager
+
+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() {
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Components.interfaces.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], null, null);
+ let principal = secMan.createCodebasePrincipal(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], null, null);
+ let principal = secMan.createCodebasePrincipal(uri, {});
+
+ do_check_eq(pm.testPermissionFromPrincipal(principal, results[i][1]), results[i][2]);
+ do_check_eq(pm.testExactPermissionFromPrincipal(principal, results[i][1]), results[i][3]);
+ }
+
+ // test the enumerator ...
+ var j = 0;
+ var perms = new Array();
+ var enumerator = pm.enumerator;
+ while (enumerator.hasMoreElements()) {
+ perms[j] = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ ++j;
+ }
+ do_check_eq(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], null, null), true) &&
+ hosts[i][1] == perms[j].type &&
+ hosts[i][2] == perms[j].capability) {
+ perms.splice(j, 1);
+ break;
+ }
+ }
+ }
+ do_check_eq(perms.length, 0);
+
+ // ... and check the permmgr's empty
+ do_check_eq(pm.enumerator.hasMoreElements(), false);
+
+ // 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 uri = ioService.newURI("http://" + utf8, null, null);
+ pm.add(uri, "utf8", 1);
+ var enumerator = pm.enumerator;
+ do_check_eq(enumerator.hasMoreElements(), true);
+ var ace = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ do_check_eq(ace.principal.URI.asciiHost, aceref);
+ do_check_eq(enumerator.hasMoreElements(), false);
+
+ // test removeAll()
+ pm.removeAll();
+ do_check_eq(pm.enumerator.hasMoreElements(), false);
+
+ uri = ioService.newURI("https://www.example.com", null, null);
+ pm.add(uri, "offline-app", pm.ALLOW_ACTION);
+ principal = secMan.createCodebasePrincipalFromOrigin("https://www.example.com");
+ // Remove existing entry.
+ 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);
+ do_check_eq(pm.enumerator.hasMoreElements(), false);
+}
diff --git a/netwerk/test/unit/test_ping_aboutnetworking.js b/netwerk/test/unit/test_ping_aboutnetworking.js
new file mode 100644
index 0000000000..6db46cb933
--- /dev/null
+++ b/netwerk/test/unit/test_ping_aboutnetworking.js
@@ -0,0 +1,86 @@
+/* -*- 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/. */
+
+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) {
+ do_test_pending();
+ gDashboard.requestSockets(function(data) {
+ let index = -1;
+ do_print("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;
+ }
+ }
+ do_check_neq(index, -1);
+ do_check_eq(data.sockets[index].port, serverSocket.port);
+ do_check_eq(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);
+
+ do_register_cleanup(function() {
+ ps.clearUserPref("network.notify.changed");
+ });
+
+ let serverSocket = Components.classes["@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;
+ do_print("requestDNSInfo: " + JSON.stringify(data.entries));
+ for (let i = 0; i < data.entries.length; i++) {
+ if (data.entries[i].hostname == "localhost") {
+ found = true;
+ break;
+ }
+ }
+ do_check_eq(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..39b1c764a2
--- /dev/null
+++ b/netwerk/test/unit/test_pinned_app_cache.js
@@ -0,0 +1,277 @@
+/* -*- 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)
+ *
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+
+// const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+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) {
+ do_print("manifest1\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest1);
+}
+
+function manifest2_handler(metadata, response) {
+ do_print("manifest2\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest2);
+}
+
+function app_handler(metadata, response) {
+ do_print("app_handler\n");
+ response.setHeader("content-type", "text/html");
+
+ response.write("<html></html>");
+}
+
+function datafile_handler(metadata, response) {
+ do_print("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.setComplexValue("browser.cache.offline.parent_directory",
+ Ci.nsILocalFile, 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 (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(Components.interfaces.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);
+
+ Services.perms.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: function QueryInterface(iftype) {
+ return this;
+ },
+
+ updateStateChanged: stateChangeHandler,
+ applicationCacheAvailable: cacheAvailHandler
+ };
+ update.addObserver(observer, false);
+
+ 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, null, null),
+ ioService.newURI(pageURL, null, null),
+ 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() {
+ do_print("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 (appcahe) {
+ do_print("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() {
+ let error_count = [0];
+ do_print("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];
+ do_print("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:
+ do_check_true(error_count[0] == 0,
+ "Do not discard app1?");
+ httpServer.stop(do_test_finished);
+ break;
+
+ case STATE_ERROR:
+ do_print("STATE_ERROR\n");
+ error_count[0]++;
+ break;
+ }
+ },
+ function (appcahe) {
+ do_print("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..9955a86b9e
--- /dev/null
+++ b/netwerk/test/unit/test_plaintext_sniff.js
@@ -0,0 +1,194 @@
+// Test the plaintext-or-binary sniffer
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// 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(Components.interfaces.nsIHttpChannel);
+
+ chan.loadFlags |=
+ Components.interfaces.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+
+ return chan;
+}
+
+function makeListener(headerIdx, bodyIdx) {
+ var listener = {
+ onStartRequest : function test_onStartR(request, ctx) {
+ try {
+ var chan = request.QueryInterface(Components.interfaces.nsIChannel);
+
+ do_check_eq(chan.status, Components.results.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] +
+ "].");
+ }
+ do_check_eq(expectedType, type);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+
+ throw Components.results.NS_ERROR_ABORT;
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, 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.asyncOpen2(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 again for everything for now (causes sporatic oranges)
+ return;
+
+ // 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") {
+ return;
+ }
+
+ 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_post.js b/netwerk/test/unit/test_post.js
new file mode 100644
index 0000000000..934719e7df
--- /dev/null
+++ b/netwerk/test/unit/test_post.js
@@ -0,0 +1,120 @@
+//
+// POST test
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function (iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIProgressEventSink))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function (iid) {
+ if (iid.equals(Ci.nsIProgressEventSink))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onProgress: function (request, context, progress, progressMax) {
+ // this works because the response is 0 bytes and does not trigger onprogress
+ if (progress === progressMax) {
+ correctOnProgress = true;
+ }
+ },
+
+ onStatus: function (request, context, 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);
+ mime.addContentLength = true;
+
+ 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.asyncOpen2(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) {
+ do_check_eq(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);
+
+ do_check_eq(teststring1 +
+ read_stream(testfile_stream, testfile_stream.available()) +
+ teststring2,
+ data);
+}
+
+function checkRequest(request, data, context) {
+ do_check_true(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..2f4f580f44
--- /dev/null
+++ b/netwerk/test/unit/test_predictor.js
@@ -0,0 +1,596 @@
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+var Cc = Components.classes;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+
+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 LoadContext = function _loadContext() {
+};
+
+LoadContext.prototype = {
+ usePrivateBrowsing: false,
+
+ getInterface: function loadContext_getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ QueryInterface: function loadContext_QueryInterface(iid) {
+ if (iid.equals(Ci.nsINetworkPredictorVerifier) ||
+ iid.equals(Ci.nsILoadContext)) {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ originAttributes: {}
+};
+
+var load_context = new LoadContext();
+
+var ValidityChecker = function(verifier, httpStatus) {
+ this.verifier = verifier;
+ this.httpStatus = httpStatus;
+};
+
+ValidityChecker.prototype = {
+ verifier: null,
+ httpStatus: 0,
+
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsICacheEntryOpenCallback)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onCacheEntryCheck: function(entry, appCache)
+ {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable: function(entry, isnew, appCache, status)
+ {
+ // Check if forced valid
+ do_check_eq(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: function verifier_QueryInterface(iid) {
+ if (iid.equals(Ci.nsINetworkPredictorVerifier) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ 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;
+ do_check_true(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) {
+ do_check_true(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, 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) {
+ do_check_true(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) {
+ do_check_true(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, null, null);
+}
+
+var prepListener = {
+ numEntriesToOpen: 0,
+ numEntriesOpened: 0,
+ continueCallback: null,
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsICacheEntryOpenCallback)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ init: function (entriesToOpen, cb) {
+ this.numEntriesOpened = 0;
+ this.numEntriesToOpen = entriesToOpen;
+ this.continueCallback = cb;
+ },
+
+ onCacheEntryCheck: function (entry, appCache) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable: function (entry, isNew, appCache, result) {
+ do_check_eq(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(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, load_context, 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, load_context);
+ var preconns = [];
+ for (var i = 0; i < subresources.length; i++) {
+ var sruri = newURI(subresources[i]);
+ predictor.learn(sruri, pageload_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+ preconns.push(extract_origin(sruri));
+ }
+
+ var verifier = new Verifier("pageload", [], preconns, []);
+ predictor.predict(pageload_toplevel, null, predictor.PREDICT_LOAD, load_context, 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_redrect() {
+ 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, load_context);
+ predictor.learn(redirect_targeturi, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+ predictor.learn(redirect_targeturi, redirect_inituri, predictor.LEARN_LOAD_REDIRECT, load_context);
+
+ var preconns = [];
+ preconns.push(extract_origin(redirect_targeturi));
+ for (var i = 0; i < subresources.length; i++) {
+ var sruri = newURI(subresources[i]);
+ predictor.learn(sruri, redirect_targeturi, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+ preconns.push(extract_origin(sruri));
+ }
+
+ var verifier = new Verifier("redirect", [], preconns, []);
+ predictor.predict(redirect_inituri, null, predictor.PREDICT_LOAD, load_context, 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 = [];
+ for (var i = 0; i < uris.length; i++) {
+ var uri = newURI(uris[i]);
+ predictor.learn(uri, null, predictor.LEARN_STARTUP, load_context);
+ preconns.push(extract_origin(uri));
+ }
+
+ var verifier = new Verifier("startup", [], preconns, []);
+ predictor.predict(null, null, predictor.PREDICT_STARTUP, load_context, 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, load_context);
+ var sruri = newURI(subresource);
+ predictor.learn(sruri, dns_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+
+ var preresolves = [extract_origin(sruri)];
+ var verifier = new Verifier("dns", [], [], preresolves);
+ predictor.predict(dns_toplevel, null, predictor.PREDICT_LOAD, load_context, 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, load_context);
+ var preconns = [];
+ for (var i = 0; i < subresources.length; i++) {
+ var sruri = newURI(subresources[i]);
+ predictor.learn(sruri, origin_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+ var origin = extract_origin(sruri);
+ if (preconns.indexOf(origin) === -1) {
+ preconns.push(origin);
+ }
+ }
+
+ var loaduri = newURI("http://localhost:4444/anotherpage.html");
+ var verifier = new Verifier("origin", [], preconns, []);
+ predictor.predict(loaduri, null, predictor.PREDICT_LOAD, load_context, 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: function(request, ctx) {
+ do_check_eq(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable: function(request, cx, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function(request, ctx, 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, load_context);
+ predictor.learn(prefetch_sruri, prefetch_tluri, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+ } else {
+ sendCommand("predictor.learn(prefetch_tluri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);");
+ sendCommand("predictor.learn(prefetch_sruri, prefetch_tluri, predictor.LEARN_LOAD_SUBRESOURCE, load_context);");
+ }
+
+ // 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.referrer = prefetch_tluri;
+ channel.asyncOpen2(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, load_context, 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: function (iid) {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function (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", false);
+}
+
+function unregisterObserver() {
+ Services.obs.removeObserver(observer, "predictor-reset-complete");
+}
+
+function run_test_real() {
+ tests.forEach(add_test);
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.predictor.enabled", true);
+ Services.prefs.setBoolPref("network.predictor.cleaned-up", true);
+ Services.prefs.setBoolPref("browser.cache.use_new_backend_temp", true);
+ Services.prefs.setIntPref("browser.cache.use_new_backend", 1);
+ Services.prefs.setBoolPref("network.predictor.doing-tests", true);
+
+ predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);
+
+ registerObserver();
+
+ do_register_cleanup(() => {
+ Services.prefs.clearUserPref("network.predictor.preconnect-min-confidence");
+ Services.prefs.clearUserPref("network.predictor.enabled");
+ Services.prefs.clearUserPref("network.predictor.cleaned-up");
+ Services.prefs.clearUserPref("browser.cache.use_new_backend_temp");
+ Services.prefs.clearUserPref("browser.cache.use_new_backend");
+ 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..6c6f31de47
--- /dev/null
+++ b/netwerk/test/unit/test_private_cookie_changed.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function makeChan(uri, isPrivate) {
+ var chan = NetUtil.newChannel ({
+ uri: uri.spec,
+ loadUsingSystemPrincipal: true
+ }).QueryInterface(Components.interfaces.nsIHttpChannel);
+
+ chan.QueryInterface(Ci.nsIPrivateBrowsingChannel).setPrivate(isPrivate);
+ return chan;
+}
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ let publicNotifications = 0;
+ let privateNotifications = 0;
+ Services.obs.addObserver(function() {publicNotifications++;}, "cookie-changed", false);
+ Services.obs.addObserver(function() {privateNotifications++;}, "private-cookie-changed", false);
+
+ let uri = NetUtil.newURI("http://foo.com/");
+ let publicChan = makeChan(uri, false);
+ let svc = Services.cookies.QueryInterface(Ci.nsICookieService);
+ svc.setCookieString(uri, null, "oh=hai", publicChan);
+ let privateChan = makeChan(uri, true);
+ svc.setCookieString(uri, null, "oh=hai", privateChan);
+ do_check_eq(publicNotifications, 1);
+ do_check_eq(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..530d4e7e66
--- /dev/null
+++ b/netwerk/test/unit/test_private_necko_channel.js
@@ -0,0 +1,53 @@
+//
+// Private channel test
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(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) {
+ do_check_eq(count, 0)
+ get_device_entry_count("disk", LoadContextInfo.private, function(count) {
+ do_check_eq(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..e2dae9c09b
--- /dev/null
+++ b/netwerk/test/unit/test_progress.js
@@ -0,0 +1,128 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function (iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIProgressEventSink) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function (iid) {
+ if (iid.equals(Ci.nsIProgressEventSink) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ do_check_eq(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, context);
+ },
+
+ onDataAvailable: function(request, context, data, offset, count) {
+ do_check_eq(this._last_callback_handled, TYPE_ONPROGRESS);
+ this._last_callback_handled = TYPE_ONDATAAVAILABLE;
+
+ this._listener.onDataAvailable(request, context, data, offset, count);
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(this._last_callback_handled, TYPE_ONDATAAVAILABLE);
+ do_check_true(this._got_onstatus_after_onstartrequest);
+ this._last_callback_handled = TYPE_ONSTOPREQUEST;
+
+ this._listener.onStopRequest(request, context, status);
+ delete this._listener;
+ },
+
+ onProgress: function (request, context, progress, progressMax) {
+ do_check_eq(this._last_callback_handled, TYPE_ONSTATUS);
+ this._last_callback_handled = TYPE_ONPROGRESS;
+
+ do_check_eq(mStatus, STATUS_RECEIVING_FROM);
+ last = progress;
+ max = progressMax;
+ },
+
+ onStatus: function (request, context, status, statusArg) {
+ if (!this._got_onstartrequest) {
+ // Ensure that all messages before onStartRequest are onStatus
+ if (this._last_callback_handled)
+ do_check_eq(this._last_callback_handled, TYPE_ONSTATUS);
+ } else if (this._last_callback_handled == TYPE_ONSTARTREQUEST) {
+ this._got_onstatus_after_onstartrequest = true;
+ } else {
+ do_check_eq(this._last_callback_handled, TYPE_ONDATAAVAILABLE);
+ }
+ this._last_callback_handled = TYPE_ONSTATUS;
+
+ do_check_eq(statusArg, "localhost");
+ mStatus = status;
+ },
+
+ mStatus: 0,
+};
+
+function run_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+ var channel = setupChannel(testpath);
+ channel.asyncOpen2(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) {
+ do_check_eq(last, httpbody.length*LOOPS);
+ do_check_eq(max, httpbody.length*LOOPS);
+ httpserver.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_protocolproxyservice.js b/netwerk/test/unit/test_protocolproxyservice.js
new file mode 100644
index 0000000000..ff44fb4d83
--- /dev/null
+++ b/netwerk/test/unit/test_protocolproxyservice.js
@@ -0,0 +1,958 @@
+/* -*- 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();
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService();
+var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+/**
+ * Test nsIProtocolHandler that allows proxying, but doesn't allow HTTP
+ * proxying.
+ */
+function TestProtocolHandler() {
+}
+TestProtocolHandler.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolHandler) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ scheme: "moz-test",
+ defaultPort: -1,
+ protocolFlags: Components.interfaces.nsIProtocolHandler.URI_NOAUTH |
+ Components.interfaces.nsIProtocolHandler.URI_NORELATIVE |
+ Components.interfaces.nsIProtocolHandler.ALLOWS_PROXY |
+ Components.interfaces.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
+ newURI: function(spec, originCharset, baseURI) {
+ var uri = Components.classes["@mozilla.org/network/simple-uri;1"]
+ .createInstance(Components.interfaces.nsIURI);
+ uri.spec = spec;
+ return uri;
+ },
+ newChannel2: function(uri, aLoadInfo) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ newChannel: function(uri) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ allowPort: function(port, scheme) {
+ return true;
+ }
+};
+
+function TestProtocolHandlerFactory() {
+}
+TestProtocolHandlerFactory.prototype = {
+ createInstance: function(delegate, iid) {
+ return new TestProtocolHandler().QueryInterface(iid);
+ },
+ lockFactory: function(lock) {
+ }
+};
+
+function register_test_protocol_handler() {
+ var reg = Components.manager.QueryInterface(
+ Components.interfaces.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) {
+ do_check_neq(pi, null);
+ do_check_eq(pi.type, type);
+ do_check_eq(pi.host, host);
+ do_check_eq(pi.port, port);
+ if (flags != -1)
+ do_check_eq(pi.flags, flags);
+ if (timeout != -1)
+ do_check_eq(pi.failoverTimeout, timeout);
+ if (hasNext)
+ do_check_neq(pi.failoverProxy, null);
+ else
+ do_check_eq(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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyFilter) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ applyFilter: function(pps, uri, pi) {
+ 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;
+ return pi;
+ }
+};
+
+function BasicFilter() {}
+BasicFilter.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyFilter) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ applyFilter: function(pps, uri, pi) {
+ return pps.newProxyInfo("http", "localhost", 8080, 0, 10,
+ pps.newProxyInfo("direct", "", -1, 0, 0, null));
+ }
+};
+
+function BasicChannelFilter() {}
+BasicChannelFilter.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyChannelFilter) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ applyFilter: function(pps, channel, pi) {
+ return pps.newProxyInfo("http", channel.URI.host, 7777, 0, 10,
+ pps.newProxyInfo("direct", "", -1, 0, 0, null));
+ }
+};
+
+function resolveCallback() { }
+resolveCallback.prototype = {
+ nextFunction: null,
+
+ QueryInterface : function (iid) {
+ const interfaces = [Components.interfaces.nsIProtocolProxyCallback,
+ Components.interfaces.nsISupports];
+ if (!interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+ onProxyAvailable : function (req, uri, 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;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+var filter01;
+var filter02;
+
+function filter_test0_1(pi) {
+ do_check_eq(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
+ });
+ var req = 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
+ });
+ var req = 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
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+var filter03;
+
+function filter_test0_4(pi)
+{
+ do_check_eq(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
+ });
+ var req = 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/", null, null);
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_1(pi) {
+ do_check_eq(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/", null, null);
+ 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/", null, null);
+ 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/", null, null);
+ pps.asyncResolve(uri, 0, cb);
+}
+
+function filter_test_uri0_4(pi)
+{
+ do_check_eq(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
+ });
+ var req = 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
+ });
+ var req = 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
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function filter_test1_3(pi) {
+ do_check_eq(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;
+ var req = 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;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_1(pi)
+{
+ do_check_eq(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
+ });
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function pref_test1_2(pi)
+{
+ // nothing yet configured
+ do_check_eq(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
+ });
+ var req = 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
+ });
+ var req = 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)
+{
+ do_check_eq(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:
+ function TestResolveCallback_QueryInterface(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyCallback) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onProxyAvailable:
+ function TestResolveCallback_onProxyAvailable(req, uri, pi, status) {
+ dump("*** uri=" + uri.spec + ", status=" + status + "\n");
+
+ if (this.type == null) {
+ do_check_eq(pi, null);
+ } else {
+ do_check_neq(req, null);
+ do_check_neq(uri, null);
+ do_check_eq(status, 0);
+ do_check_neq(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);
+ var req = 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);
+
+ var req = 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);
+
+ var req = 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);
+ var req = 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:
+ function TestResolveCallback_QueryInterface(iid) {
+ if (iid.equals(Components.interfaces.nsIProtocolProxyCallback) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onProxyAvailable:
+ function TestResolveCancelationCallback_onProxyAvailable(req, uri, pi, status) {
+ dump("*** uri=" + uri.spec + ", status=" + status + "\n");
+
+ do_check_neq(req, null);
+ do_check_neq(uri, null);
+ do_check_eq(status, Components.results.NS_ERROR_ABORT);
+ do_check_eq(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(Components.results.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) {
+ var uri;
+ 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;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function host_filter_cb(proxy)
+{
+ if (bShouldBeFiltered) {
+ do_check_eq(proxy, null);
+ } else {
+ do_check_neq(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;
+
+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);
+ do_check_eq(prefs.getCharPref("network.proxy.no_proxies_on"), hostFilterList);
+
+ var rv;
+ // 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>");
+ do_check_eq(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", "");
+ do_check_eq(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;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function myipaddress_callback(pi)
+{
+ do_check_neq(pi, null);
+ do_check_eq(pi.type, "http");
+ do_check_eq(pi.port, 1234);
+
+ // make sure we didn't return localhost
+ do_check_neq(pi.host, null);
+ do_check_neq(pi.host, "127.0.0.1");
+ do_check_neq(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;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function myipaddress2_callback(pi)
+{
+ do_check_neq(pi, null);
+ do_check_eq(pi.type, "http");
+ do_check_eq(pi.port, 5678);
+
+ // make sure we didn't return localhost
+ do_check_neq(pi.host, null);
+ do_check_neq(pi.host, "127.0.0.1");
+ do_check_neq(pi.host, "::1");
+
+ run_failed_script_test();
+}
+
+function run_failed_script_test()
+{
+ // test to make sure we go direct with invalid PAC
+ 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;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+var directFilter;
+
+function failed_script_callback(pi)
+{
+ // we should go direct
+ do_check_eq(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 = Components.classes["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
+ obs.addObserver(directFilterListener, "http-on-modify-request", false);
+
+ var chan = NetUtil.newChannel({
+ uri: "http://127.0.0.1:7247",
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(directFilterListener);
+}
+
+var directFilterListener = {
+ onModifyRequestCalled : false,
+
+ onStartRequest: function test_onStart(request, ctx) { },
+ onDataAvailable: function test_OnData() { },
+
+ onStopRequest: function test_onStop(request, ctx, status) {
+ // check on the PI from the channel itself
+ request.QueryInterface(Components.interfaces.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
+ do_check_true(this.onModifyRequestCalled);
+ var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
+ obs.removeObserver(this, "http-on-modify-request");
+
+ run_isresolvable_test();
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic === "http-on-modify-request" &&
+ subject instanceof Components.interfaces.nsIHttpChannel &&
+ subject instanceof Components.interfaces.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;
+ var req = pps.asyncResolve(channel, 0, cb);
+}
+
+function isresolvable_callback(pi)
+{
+ do_check_neq(pi, null);
+ do_check_eq(pi.type, "http");
+ do_check_eq(pi.port, 1234);
+ do_check_eq(pi.host, "127.0.0.1");
+
+ 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..6c81d093cc
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-failover_canceled.js
@@ -0,0 +1,53 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(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("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.asyncOpen2(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..b2bf198dd7
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-failover_passing.js
@@ -0,0 +1,43 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(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.asyncOpen2(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..d25bfd2c1b
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-replace_canceled.js
@@ -0,0 +1,55 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(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.asyncOpen2(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..e3447fedaa
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-replace_passing.js
@@ -0,0 +1,43 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(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.asyncOpen2(new ChannelListener(finish_test, null));
+ 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..251ffa6210
--- /dev/null
+++ b/netwerk/test/unit/test_psl.js
@@ -0,0 +1,36 @@
+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);
+
+var Cr = Components.results;
+
+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, "utf-8");
+}
+
+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) {
+ }
+ // 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);
+ }
+ do_check_eq(actualSuffix, expectedSuffix);
+}
diff --git a/netwerk/test/unit/test_range_requests.js b/netwerk/test/unit/test_range_requests.js
new file mode 100644
index 0000000000..1850ade438
--- /dev/null
+++ b/netwerk/test/unit/test_range_requests.js
@@ -0,0 +1,434 @@
+//
+// 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.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onStartRequest: function(request, context) { },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ request.QueryInterface(Ci.nsIChannel)
+ .cancel(Components.results.NS_BINDING_ABORTED);
+ },
+ onStopRequest: function(request, context, status) {
+ do_check_eq(status, Components.results.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: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onStartRequest: function(request, context) { this._buffer = ""; },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ this._buffer = this._buffer.concat(read_stream(stream, count));
+ },
+ onStopRequest: function(request, context, status) {
+ this.continueFn(request, this._buffer);
+ }
+};
+
+var case_8_range_request = false;
+function FailedChannelListener(continueFn) {
+ this.continueFn = continueFn;
+}
+FailedChannelListener.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onStartRequest: function(request, context) { },
+
+ onDataAvailable: function(request, context, stream, offset, count) { },
+
+ onStopRequest: function(request, context, status) {
+ if (case_8_range_request)
+ do_check_eq(status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
+ this.continueFn(request, null);
+ }
+};
+
+function received_cleartext(request, data) {
+ do_check_eq(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);
+ do_check_false(metadata.hasHeader("Range"));
+ response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
+}
+function received_partial_2(request, data) {
+ do_check_eq(data, undefined);
+ var chan = make_channel("http://localhost:" + port + "/test_2");
+ chan.asyncOpen2(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:
+ do_check_false(metadata.hasHeader("Range"));
+ body = body.slice(0, partial_data_length);
+ response.processAsync();
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ do_check_false(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) {
+ do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_3");
+ chan.asyncOpen2(new ChannelListener(received_cleartext, null));
+}
+
+var case_4_request_no = 0;
+function handler_4(metadata, response) {
+ switch (case_4_request_no) {
+ case 0:
+ do_check_false(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, body.length);
+ response.finish();
+ break;
+ case 1:
+ do_check_false(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.asyncOpen2(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:
+ do_check_false(metadata.hasHeader("Range"));
+ body = body.slice(0, partial_data_length);
+ response.processAsync();
+ response.bodyOutputStream.write(body, body.length);
+ response.finish();
+ break;
+ case 1:
+ do_check_false(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) {
+ do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_5");
+ chan.setRequestHeader("If-Match", "Some eTag", false);
+ chan.asyncOpen2(new ChannelListener(received_cleartext, null));
+}
+
+var case_6_request_no = 0;
+function handler_6(metadata, response) {
+ switch (case_6_request_no) {
+ case 0:
+ do_check_false(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:
+ do_check_false(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
+ do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_6");
+ chan.asyncOpen2(new ChannelListener(received_cleartext, null));
+}
+
+const simpleBody = "0123456789";
+
+function received_simple(request, data) {
+ do_check_eq(simpleBody, data);
+ testFinished();
+}
+
+var case_7_request_no = 0;
+function handler_7(metadata, response) {
+ switch (case_7_request_no) {
+ case 0:
+ do_check_false(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")) {
+ do_check_true(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
+ do_check_eq(4, data.length);
+ // do it again to get the rest
+ var chan = make_channel("http://localhost:" + port + "/test_7");
+ chan.asyncOpen2(new ChannelListener(received_simple, null));
+}
+
+var case_8_request_no = 0;
+function handler_8(metadata, response) {
+ switch (case_8_request_no) {
+ case 0:
+ do_check_false(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")) {
+ do_check_true(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
+ do_check_eq(4, data.length);
+ // do it again to get the rest
+ var chan = make_channel("http://localhost:" + port + "/test_8");
+ chan.asyncOpen2(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:
+ do_check_false(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:
+ do_check_false(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) {
+ do_check_eq(partial_data_length, data.length);
+ var chan = make_channel("http://localhost:" + port + "/test_9");
+ chan.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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.asyncOpen2(new MyListener(received_partial_4));
+
+ // Case 5: conditional request-header set by client
+ var chan = make_channel("http://localhost:" + port + "/test_5");
+ chan.asyncOpen2(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.asyncOpen2(new MyListener(received_partial_6));
+
+ // Case 7: a basic positive test
+ var chan = make_channel("http://localhost:" + port + "/test_7");
+ chan.asyncOpen2(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.asyncOpen2(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.asyncOpen2(new MyListener(received_partial_9));
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_readline.js b/netwerk/test/unit/test_readline.js
new file mode 100644
index 0000000000..798b2c2a78
--- /dev/null
+++ b/netwerk/test/unit/test_readline.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+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..2371078654
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_canceled.js
@@ -0,0 +1,68 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+ return;
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(secondTimeThrough, null));
+}
+
+function secondTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(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.asyncOpen2(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..0e88c0d099
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_failure.js
@@ -0,0 +1,55 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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", "httpx://localhost:" +
+ httpServer.identity.primaryPort + "/content", false);
+ response.setHeader("Cache-control", "max-age=1000", false);
+}
+
+function makeSureNotInCache(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNKNOWN_PROTOCOL);
+
+ // It's very unlikely that we'd somehow succeed when we try again from cache.
+ // Can't hurt to test though.
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_ONLY_FROM_CACHE;
+ chan.asyncOpen2(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNKNOWN_PROTOCOL);
+ do_check_eq(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.asyncOpen2(new ChannelListener(makeSureNotInCache, 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..e1d3ebe8fa
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_passing.js
@@ -0,0 +1,59 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+ return;
+}
+
+function contentHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+function firstTimeThrough(request, buffer)
+{
+ do_check_eq(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(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.asyncOpen2(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..aa1ce7dc42
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_baduri.js
@@ -0,0 +1,42 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+/*
+ * 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)
+{
+ do_check_eq(request.status, Components.results.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.asyncOpen2(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..cd2a948eef
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_canceled.js
@@ -0,0 +1,51 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(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.asyncOpen2(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..73aea57ca1
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_different-protocol.js
@@ -0,0 +1,51 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(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.asyncOpen2(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..b74a9102f4
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_failure.js
@@ -0,0 +1,45 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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", "httpx://localhost:" +
+ httpServer.identity.primaryPort + "/content", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_UNKNOWN_PROTOCOL);
+
+ do_check_eq(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.asyncOpen2(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..d296ab0d0b
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_from_script.js
@@ -0,0 +1,258 @@
+/*
+ * 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 nsIXMLHttpRequest objects.
+ *
+ * testViaAsyncOpen() checks that internal redirects occur correctly when made
+ * with nsIHTTPChannel.asyncOpen2().
+ *
+ * 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.
+ *
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// the topic we observe to use the API. http-on-opening-request might also
+// work for some purposes.
+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: function()
+ {
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService).
+ addObserver(this, redirectHook, true);
+ },
+
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ observe: function(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, null, null);
+ 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.asyncOpen2()
+
+ // 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);
+ do_check_eq(httpChannel.getResponseHeader(testHeaderName), headerValue);
+ do_check_eq(buffer, redirectedText);
+ nextTask();
+ };
+
+ // Produce a function to run an asyncOpen2 test using the above verifier
+ var test = function()
+ {
+ var chan = make_channel(uri);
+ chan.asyncOpen2(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 xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
+
+ var req = xhr.createInstance(Ci.nsIXMLHttpRequest);
+ req.open("GET", uri, false);
+ req.send();
+ do_check_eq(req.getResponseHeader(testHeaderName), headerValue);
+ do_check_eq(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..195508490c
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_from_script_after-open_passing.js
@@ -0,0 +1,258 @@
+/*
+ * 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 nsIXMLHttpRequest objects.
+ *
+ * testViaAsyncOpen() checks that internal redirects occur correctly when made
+ * with nsIHTTPChannel.asyncOpen2().
+ *
+ * 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.
+ *
+ */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// the topic we observe to use the API. http-on-opening-request might also
+// work for some purposes.
+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: function()
+ {
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService).
+ addObserver(this, redirectHook, true);
+ },
+
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ observe: function(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, null, null);
+ 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.asyncOpen2()
+
+ // 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);
+ do_check_eq(httpChannel.getResponseHeader(testHeaderName), headerValue);
+ do_check_eq(buffer, redirectedText);
+ nextTask();
+ };
+
+ // Produce a function to run an asyncOpen2 test using the above verifier
+ var test = function()
+ {
+ var chan = make_channel(uri);
+ chan.asyncOpen2(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 xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
+
+ var req = xhr.createInstance(Ci.nsIXMLHttpRequest);
+ req.open("GET", uri, false);
+ req.send();
+ do_check_eq(req.getResponseHeader(testHeaderName), headerValue);
+ do_check_eq(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..07da064787
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_history.js
@@ -0,0 +1,64 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(buffer, responseBody);
+ let chan = request.QueryInterface(Ci.nsIChannel);
+ let redirectChain = chan.loadInfo.redirectChain;
+
+ do_check_eq(numRedirects - 1, redirectChain.length);
+ for (let i = 0; i < numRedirects - 1; ++i) {
+ let principal = redirectChain[i];
+ do_check_eq(URL + redirects[i], principal.URI.spec);
+ }
+ 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]);
+ chan.asyncOpen2(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..9efcecadb8
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_loop.js
@@ -0,0 +1,86 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+/*
+ * 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)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_REDIRECT_LOOP);
+
+ var chan = make_channel(relativeLoopURI);
+ chan.asyncOpen2(new ChannelListener(testRelativeLoop, null, CL_EXPECT_FAILURE));
+}
+
+function testRelativeLoop(request, buffer)
+{
+ do_check_eq(request.status, Components.results.NS_ERROR_REDIRECT_LOOP);
+
+ var chan = make_channel(emptyLoopURI);
+ chan.asyncOpen2(new ChannelListener(testEmptyLoop, null, CL_EXPECT_FAILURE));
+}
+
+function testEmptyLoop(request, buffer)
+{
+ do_check_eq(request.status, Components.results.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.asyncOpen2(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..a9a515b5eb
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_passing.js
@@ -0,0 +1,57 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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)
+{
+ do_check_eq(buffer, responseBody);
+ var chan = make_channel(randomURI);
+ chan.asyncOpen2(new ChannelListener(finish_test, null));
+}
+
+function finish_test(request, buffer)
+{
+ do_check_eq(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.asyncOpen2(new ChannelListener(firstTimeThrough, null));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_reentrancy.js b/netwerk/test/unit/test_reentrancy.js
new file mode 100644
index 0000000000..2f8eec30f5
--- /dev/null
+++ b/netwerk/test/unit/test_reentrancy.js
@@ -0,0 +1,105 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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 = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open("GET", URL + testpath, false);
+ xhr.send(null);
+}
+
+const MAX_TESTS = 2;
+
+var listener = {
+ _done_onStart: false,
+ _done_onData: false,
+ _test: 0,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, ctx) {
+ switch(this._test) {
+ case 0:
+ request.suspend();
+ syncXHR();
+ request.resume();
+ break;
+ case 1:
+ request.suspend();
+ syncXHR();
+ do_execute_soon(function() { request.resume(); });
+ break;
+ case 2:
+ do_execute_soon(function() { request.suspend(); });
+ do_execute_soon(function() { request.resume(); });
+ syncXHR();
+ break;
+ }
+
+ this._done_onStart = true;
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_check_true(this._done_onStart);
+ read_stream(stream, count);
+ this._done_onData = true;
+ },
+
+ onStopRequest: function(request, ctx, status) {
+ do_check_true(this._done_onData);
+ this._reset();
+ if (this._test <= MAX_TESTS)
+ next_test();
+ else
+ httpserver.stop(do_test_finished);
+ },
+
+ _reset: function() {
+ 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.asyncOpen2(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..5b9fc1c28d
--- /dev/null
+++ b/netwerk/test/unit/test_referrer.js
@@ -0,0 +1,109 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function getTestReferrer(server_uri, referer_uri) {
+ var uri = NetUtil.newURI(server_uri, "", null)
+ let referrer = NetUtil.newURI(referer_uri, null, null);
+ let triggeringPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(referrer, {});
+ var chan = NetUtil.newChannel({
+ uri: uri,
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ triggeringPrincipal: triggeringPrincipal,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ });
+
+ chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.referrer = 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(Components.interfaces.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);
+ do_check_null(getTestReferrer(server_uri, referer_uri));
+ prefs.setIntPref("network.http.sendRefererHeader", 2);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // test that https ref is not sent to http
+ do_check_null(getTestReferrer(server_uri_2, referer_uri_https));
+
+ // tests for referer.spoofSource
+ prefs.setBoolPref("network.http.referer.spoofSource", true);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), server_uri);
+ prefs.setBoolPref("network.http.referer.spoofSource", false);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // tests for referer.XOriginPolicy
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 2);
+ do_check_null(getTestReferrer(server_uri_2, referer_uri));
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), referer_uri_2);
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 1);
+ do_check_eq(getTestReferrer(server_uri_2, referer_uri), referer_uri);
+ do_check_null(getTestReferrer(server_uri, referer_uri));
+ // https test
+ do_check_eq(getTestReferrer(server_uri_https, referer_uri_https), referer_uri_https);
+ prefs.setIntPref("network.http.referer.XOriginPolicy", 0);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), referer_uri);
+
+ // tests for referer.trimmingPolicy
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/path");
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 2);
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/");
+ // https test
+ do_check_eq(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
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2_anchor), referer_uri_2);
+
+ // tests for referer.XOriginTrimmingPolicy
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 1);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), "http://foo.example.com/path");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/path");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3?q=blah");
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3");
+ prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 2);
+ do_check_eq(getTestReferrer(server_uri, referer_uri), "http://foo.example.com/");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/");
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3");
+ prefs.setIntPref("network.http.referer.trimmingPolicy", 0);
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3?q=blah");
+ // https tests
+ do_check_eq(getTestReferrer(server_uri_https, referer_uri_https), "https://bar.example.com/path3?q=blah");
+ do_check_eq(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
+ do_check_eq(getTestReferrer(server_uri, referer_uri_2_anchor), referer_uri_2);
+
+ // 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);
+ do_check_eq(getTestReferrer(dest_uri, combo_referer_uri), "http://blah.foo.com:9999/spoofedpath");
+ do_check_null(getTestReferrer(dest_uri, "http://gah.foo.com/anotherpath"));
+}
diff --git a/netwerk/test/unit/test_referrer_policy.js b/netwerk/test/unit/test_referrer_policy.js
new file mode 100644
index 0000000000..f1f9dfd5a3
--- /dev/null
+++ b/netwerk/test/unit/test_referrer_policy.js
@@ -0,0 +1,95 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function test_policy(test) {
+ do_print("Running test: " + test.toSource());
+
+ var uri = NetUtil.newURI(test.url, "", null)
+ var chan = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+
+ var referrer = NetUtil.newURI(test.referrer, "", null);
+ chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+ chan.setReferrerWithPolicy(referrer, test.policy);
+ if (test.expectedReferrerSpec === undefined) {
+ try {
+ chan.getRequestHeader("Referer");
+ do_throw("Should not find a Referer header!");
+ } catch(e) {
+ }
+ do_check_eq(chan.referrer, null);
+ } else {
+ var header = chan.getRequestHeader("Referer");
+ do_check_eq(header, test.expectedReferrerSpec);
+ do_check_eq(chan.referrer.asciiSpec, test.expectedReferrerSpec);
+ }
+}
+
+const nsIHttpChannel = Ci.nsIHttpChannel;
+var gTests = [
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ url: "http://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: undefined
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_NO_REFERRER,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: undefined
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_ORIGIN,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_ORIGIN,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL,
+ url: "https://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL,
+ url: "https://sub1.\xe4lt.example/foo",
+ referrer: "https://sub1.\xe4lt.example/referrer",
+ expectedReferrerSpec: "https://sub1.xn--lt-uia.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL,
+ url: "http://test.example/foo",
+ referrer: "https://test.example/referrer",
+ expectedReferrerSpec: "https://test.example/referrer"
+ },
+ {
+ policy: nsIHttpChannel.REFERRER_POLICY_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..b3744dfc53
--- /dev/null
+++ b/netwerk/test/unit/test_reopen.js
@@ -0,0 +1,141 @@
+// This testcase verifies that channels can't be reopened
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=372486
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(add_test);
+
+// 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) {
+ do_check_neq(error.indexOf(e.result), -1);
+ } else {
+ do_check_eq(e.result, error);
+ }
+ thrown = true;
+ }
+ do_check_true(thrown);
+}
+
+function check_open_throws(error) {
+ check_throws(function() {
+ chan.open2(listener);
+ }, error);
+}
+
+function check_async_open_throws(error) {
+ check_throws(function() {
+ chan.asyncOpen2(listener);
+ }, error);
+}
+
+var listener = {
+ onStartRequest: function test_onStartR(request, ctx) {
+ check_async_open_throws(NS_ERROR_IN_PROGRESS);
+ },
+
+ onDataAvailable: function test_ODA(request, cx, inputStream,
+ offset, count) {
+ new BinaryInputStream(inputStream).readByteArray(count); // required by API
+ check_async_open_throws(NS_ERROR_IN_PROGRESS);
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, 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();
+ var inputStream = chan.open2();
+ 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.asyncOpen2(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..7fda87209c
--- /dev/null
+++ b/netwerk/test/unit/test_reply_without_content_type.js
@@ -0,0 +1,91 @@
+//
+// tests HTTP replies that lack content-type (where we try to sniff content-type).
+//
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(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.asyncOpen2(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, httpbodyGZip.length);
+ if (dbg) { print("============== serverHandler GZip: out"); }
+}
+
+function checkRequest(request, data, context) {
+ if (dbg) { print("============== checkRequest: in"); }
+ do_check_eq(data, httpbody);
+ do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,"text/html");
+ httpserver.stop(do_test_finished);
+ run_next_test();
+ if (dbg) { print("============== checkRequest: out"); }
+}
+
+function run_test() {
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_resumable_channel.js b/netwerk/test/unit/test_resumable_channel.js
new file mode 100644
index 0000000000..57a4481b96
--- /dev/null
+++ b/netwerk/test/unit/test_resumable_channel.js
@@ -0,0 +1,402 @@
+/* Tests various aspects of nsIResumableChannel in combination with HTTP */
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function authprompt2_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIAuthPrompt2))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ 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: function requestor_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIInterfaceRequestor))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Components.interfaces.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt2)
+ this.prompt2 = new AuthPrompt2();
+ return this.prompt2;
+ }
+
+ throw Components.results.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");
+ do_check_true(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.asyncOpen2(new ChannelListener(try_resume, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_resume(request, data, ctx) {
+ dump("*** try_resume()\n");
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ // Try a successful resume
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1, entityID);
+ chan.asyncOpen2(new ChannelListener(try_resume_zero, null));
+ }
+
+ function try_resume_zero(request, data, ctx) {
+ dump("*** try_resume_zero()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(try_no_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_no_range(request, data, ctx) {
+ dump("*** try_no_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(try_bytes_range, null));
+ }
+
+ function try_bytes_range(request, data, ctx) {
+ dump("*** try_bytes_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(try_foo_bar_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_foo_bar_range(request, data, ctx) {
+ dump("*** try_foo_bar_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(try_foobar_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_foobar_range(request, data, ctx) {
+ dump("*** try_foobar_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(try_bytes_foobar_range, null));
+ }
+
+ function try_bytes_foobar_range(request, data, ctx) {
+ dump("*** try_bytes_foobar_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(try_bytesfoo_bar_range, null, CL_EXPECT_FAILURE));
+ }
+
+ function try_bytesfoo_bar_range(request, data, ctx) {
+ dump("*** try_bytesfoo_bar_range()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(try_no_accept_ranges, null));
+ }
+
+ function try_no_accept_ranges(request, data, ctx) {
+ dump("*** try_no_accept_ranges()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody);
+
+ // Try a successful suspend/resume from 0
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.asyncOpen2(new ChannelListener(try_suspend_resume, null,
+ CL_SUSPEND | CL_EXPECT_3S_DELAY));
+ }
+
+ function try_suspend_resume(request, data, ctx) {
+ dump("*** try_suspend_resume()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody);
+
+ // Try a successful resume from 0
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(0, entityID);
+ chan.asyncOpen2(new ChannelListener(success, null));
+ }
+
+ function success(request, data, ctx) {
+ dump("*** success()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(test_auth_nopw, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_auth_nopw(request, data, ctx) {
+ dump("*** test_auth_nopw()\n");
+ do_check_false(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(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.asyncOpen2(new ChannelListener(test_auth, null, CL_EXPECT_FAILURE));
+ }
+ function test_auth(request, data, ctx) {
+ dump("*** test_auth()\n");
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+ do_check_true(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.asyncOpen2(new ChannelListener(test_auth_resume, null));
+ }
+
+ function test_auth_resume(request, data, ctx) {
+ dump("*** test_auth_resume()\n");
+ do_check_eq(data, rangeBody.substring(1));
+ do_check_true(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.asyncOpen2(new ChannelListener(test_404, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_404(request, data, ctx) {
+ dump("*** test_404()\n");
+ do_check_eq(request.status, NS_ERROR_ENTITY_CHANGED);
+ do_check_eq(request.nsIHttpChannel.responseStatus, 404);
+
+ // 416 Requested Range Not Satisfiable
+ var chan = make_channel(URL + "/range");
+ chan.nsIResumableChannel.resumeAt(1000, entityID);
+ chan.asyncOpen2(new ChannelListener(test_416, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_416(request, data, ctx) {
+ dump("*** test_416()\n");
+ do_check_eq(request.status, NS_ERROR_ENTITY_CHANGED);
+ do_check_eq(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.asyncOpen2(new ChannelListener(test_redir_resume, null));
+ }
+
+ function test_redir_resume(request, data, ctx) {
+ dump("*** test_redir_resume()\n");
+ do_check_true(request.nsIHttpChannel.requestSucceeded);
+ do_check_eq(data, rangeBody.substring(1));
+ do_check_eq(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.asyncOpen2(new ChannelListener(test_redir_noresume, null, CL_EXPECT_FAILURE));
+ }
+
+ function test_redir_noresume(request, data, ctx) {
+ dump("*** test_redir_noresume()\n");
+ do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
+
+ httpserver.stop(do_test_finished);
+ }
+
+ httpserver.start(-1);
+ var chan = make_channel(URL + "/range");
+ chan.asyncOpen2(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=";
+
+ 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);
+
+ return true;
+ }
+ else
+ {
+ // 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..c23a91b714
--- /dev/null
+++ b/netwerk/test/unit/test_resumable_truncate.js
@@ -0,0 +1,88 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function(iid) {
+ if (iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ request.QueryInterface(Ci.nsIChannel)
+ .cancel(Components.results.NS_BINDING_ABORTED);
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(status, Components.results.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.asyncOpen2(new ChannelListener(finish_test, null));
+}
+
+function start_canceler() {
+ var chan = make_channel("http://localhost:" +
+ httpserver.identity.primaryPort + "/cached/test.gz");
+ chan.asyncOpen2(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.asyncOpen2(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..3e41fcbb02
--- /dev/null
+++ b/netwerk/test/unit/test_safeoutputstream.js
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+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);
+
+ do_check_eq(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..a7abb06e65
--- /dev/null
+++ b/netwerk/test/unit/test_safeoutputstream_append.js
@@ -0,0 +1,42 @@
+/* atomic-file-output-stream and safe-file-output-stream should throw and
+ * exception if PR_APPEND is explicity specified without PR_TRUNCATE. */
+
+const PR_WRONLY = 0x02;
+const PR_CREATE_FILE = 0x08;
+const PR_APPEND = 0x10;
+const PR_TRUNCATE = 0x20;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+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_separate_connections.js b/netwerk/test/unit/test_separate_connections.js
new file mode 100644
index 0000000000..d49b2885fd
--- /dev/null
+++ b/netwerk/test/unit/test_separate_connections.js
@@ -0,0 +1,99 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+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: userContextId };
+ return chan;
+}
+
+let previousHashKeys = [];
+
+function Listener(userContextId) {
+ this.userContextId = userContextId;
+}
+
+let gTestsRun = 0;
+Listener.prototype = {
+ onStartRequest: function(request, context) {
+ request.QueryInterface(Ci.nsIHttpChannel)
+ .QueryInterface(Ci.nsIHttpChannelInternal);
+
+ do_check_eq(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) {
+ do_check_eq(hashKey, previousHashKeys[userContextId]);
+ } else {
+ do_check_neq(hashKey, previousHashKeys[userContextId]);
+ }
+ }
+ } else {
+ // Set the hash keys in the first round.
+ previousHashKeys[this.userContextId] = hashKey;
+ }
+ },
+ onDataAvailable: function(request, ctx, stream, off, cnt) {
+ read_stream(stream, cnt);
+ },
+ onStopRequest: function() {
+ 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.asyncOpen2(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..7db93f55b4
--- /dev/null
+++ b/netwerk/test/unit/test_signature_extraction.js
@@ -0,0 +1,206 @@
+/* -*- 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
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.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. If the file is then created, it
+ * will be removed when tests in this file finish.
+ */
+function getTempFile(aLeafName) {
+ let file = FileUtils.getFile("TmpD", [aLeafName]);
+ do_register_cleanup(function GTF_cleanup() {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ });
+ return file;
+}
+
+/**
+ * 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) {
+ let deferred = Promise.defer();
+ aSaver.observer = {
+ onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget)
+ {
+ if (aOnTargetChangeFn) {
+ aOnTargetChangeFn(aTarget);
+ }
+ },
+ onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus)
+ {
+ if (Components.isSuccessCode(aStatus)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception("Saver failed.", aStatus));
+ }
+ },
+ };
+ return deferred.promise;
+}
+
+/**
+ * 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) {
+ let deferred = Promise.defer();
+ 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: function () { },
+ onStopRequest: function (aRequest, aContext, aStatusCode)
+ {
+ if (Components.isSuccessCode(aStatusCode)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(new Components.Exception(aResult));
+ }
+ },
+ }, null);
+ return deferred.promise;
+}
+
+var gStillRunning = true;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+function run_test()
+{
+ run_next_test();
+}
+
+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(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 {
+ let signatureInfo = saver.signatureInfo;
+ do_throw("Can't get signature before saver is complete.");
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+
+ saver.enableSignatureInfo();
+ saver.setTarget(destFile, false);
+ yield promiseCopyToSaver(data, saver, true);
+
+ saver.finish(Cr.NS_OK);
+ yield completionPromise;
+
+ // There's only one nsIX509CertList in the signature array.
+ do_check_eq(1, saver.signatureInfo.length);
+ let certLists = saver.signatureInfo.enumerate();
+ do_check_true(certLists.hasMoreElements());
+ let certList = certLists.getNext().QueryInterface(Ci.nsIX509CertList);
+ do_check_false(certLists.hasMoreElements());
+
+ // Check that it has 3 certs.
+ let certs = certList.getEnumerator();
+ do_check_true(certs.hasMoreElements());
+ let signer = certs.getNext().QueryInterface(Ci.nsIX509Cert);
+ do_check_true(certs.hasMoreElements());
+ let issuer = certs.getNext().QueryInterface(Ci.nsIX509Cert);
+ do_check_true(certs.hasMoreElements());
+ let root = certs.getNext().QueryInterface(Ci.nsIX509Cert);
+ do_check_false(certs.hasMoreElements());
+
+ // Check that the certs have expected strings attached.
+ let organization = "Microsoft Corporation";
+ do_check_eq("Microsoft Corporation", signer.commonName);
+ do_check_eq(organization, signer.organization);
+ do_check_eq("Copyright (c) 2002 Microsoft Corp.", signer.organizationalUnit);
+
+ do_check_eq("Microsoft Code Signing PCA", issuer.commonName);
+ do_check_eq(organization, issuer.organization);
+ do_check_eq("Copyright (c) 2000 Microsoft Corp.", issuer.organizationalUnit);
+
+ do_check_eq("Microsoft Root Authority", root.commonName);
+ do_check_false(root.organization);
+ do_check_eq("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..12ec717799
--- /dev/null
+++ b/netwerk/test/unit/test_simple.js
@@ -0,0 +1,56 @@
+//
+// Simple HTTP test: fetches page
+//
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(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"); }
+ do_check_eq(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..bfb64e3248
--- /dev/null
+++ b/netwerk/test/unit/test_sockettransportsvc_available.js
@@ -0,0 +1,8 @@
+function run_test() {
+ try {
+ var sts = Components.classes["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Components.interfaces.nsISocketTransportService);
+ } catch(e) {}
+
+ do_check_true(!!sts);
+}
diff --git a/netwerk/test/unit/test_socks.js b/netwerk/test/unit/test_socks.js
new file mode 100644
index 0000000000..57c947979d
--- /dev/null
+++ b/netwerk/test/unit/test_socks.js
@@ -0,0 +1,525 @@
+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.nsILocalFile);
+ 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;
+ } else {
+ 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: function(input)
+ {
+ var len = getAvailableBytes(input);
+
+ if (len == 0) {
+ print('server: client closed!');
+ do_check_eq(this.state, STATE_GOT_PONG);
+ 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: function(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: function(input)
+ {
+ input.asyncWait(this, 0, 0, currentThread);
+ },
+
+ waitWrite: function(output)
+ {
+ output.asyncWait(this, 0, 0, currentThread);
+ },
+
+ write: function(buf)
+ {
+ this.outbuf += buf;
+ this.waitWrite(this.client_out);
+ },
+
+ checkSocksGreeting: function()
+ {
+ 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: function()
+ {
+ if (this.inbuf.length < 8)
+ return;
+
+ do_check_eq(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: function()
+ {
+ 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: function()
+ {
+ 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: function()
+ {
+ var str = this.readString();
+
+ if (str == null)
+ return;
+
+ this.dest_name = str;
+ this.sendSocks4Response();
+ },
+
+ sendSocks4Response: function()
+ {
+ this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00';
+ this.sendPing();
+ },
+
+ checkSocks5Greeting: function()
+ {
+ if (this.inbuf.length < 2)
+ return;
+ var nmethods = this.inbuf[1];
+ if (this.inbuf.length < 2 + nmethods)
+ return;
+
+ do_check_true(nmethods >= 1);
+ var methods = this.inbuf.slice(2, 2 + nmethods);
+ do_check_true(0 in methods);
+
+ this.inbuf = [];
+ this.state = STATE_WAIT_SOCKS5_REQUEST;
+ this.write('\x05\x00');
+ },
+
+ checkSocks5Request: function()
+ {
+ if (this.inbuf.length < 4)
+ return;
+
+ do_check_eq(this.inbuf[0], 0x05);
+ do_check_eq(this.inbuf[1], 0x01);
+ do_check_eq(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;
+
+ 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: function()
+ {
+ 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: function()
+ {
+ print('server: sending ping');
+ this.state = STATE_WAIT_PONG;
+ this.outbuf += "PING!";
+ this.inbuf = [];
+ this.waitWrite(this.client_out);
+ this.waitRead(this.client_in);
+ },
+
+ checkPong: function()
+ {
+ var pong = buf2str(this.inbuf);
+ do_check_eq(pong, "PONG!");
+ this.state = STATE_GOT_PONG;
+ this.waitRead(this.client_in);
+ },
+
+ close: function()
+ {
+ 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: function(test)
+ {
+ test.finished = false;
+ test.port = this.test_port_id++;
+ this.test_cases.push(test);
+ },
+
+ pickTest: function(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: function(client)
+ {
+ var port_id = buf2int(client.dest_port);
+ var test = this.pickTest(port_id);
+
+ print('server: test finished', test.port);
+ do_check_true(test != null);
+ do_check_eq(test.expectedType || test.type, client.type);
+ do_check_eq(test.port, port_id);
+
+ if (test.remote_dns)
+ do_check_eq(test.host, client.dest_name);
+ else
+ do_check_eq(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: function()
+ {
+ 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: function(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: function(socket)
+ {
+ },
+
+ close: function()
+ {
+ if (this.client_subprocess) {
+ try {
+ this.client_subprocess.kill();
+ } catch (x) {
+ do_note_exception(x, 'Killing subprocess failed');
+ }
+ this.client_subprocess = null;
+ }
+ for (var client of this.client_connections)
+ client.close();
+ this.client_connections = [];
+ if (this.listener) {
+ this.listener.close();
+ this.listener = null;
+ }
+ }
+};
+
+function test_timeout()
+{
+ socks_test_server.close();
+ do_throw("SOCKS test took too long!");
+}
+
+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_timeout(120 * 1000, test_timeout);
+ 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..51ae824882
--- /dev/null
+++ b/netwerk/test/unit/test_speculative_connect.js
@@ -0,0 +1,333 @@
+/* -*- 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/. */
+
+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: function(iid) {
+ if (iid.equals(Ci.nsIServerSocket) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ onSocketAccepted: function(socket, trans) {
+ try { this.listener.close(); } catch(e) {}
+ do_check_true(true);
+ next_test();
+ },
+
+ onStopListening: function(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";
+}
+
+TestFailedStreamCallback.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIInputStreamCallback) ||
+ iid.equals(Ci.nsIOutputStreamCallback) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ processException: function(e) {
+ do_check_instanceof(e, Ci.nsIException);
+ // A refusal to connect speculatively should throw an error.
+ do_check_eq(e.result, Cr.NS_ERROR_CONNECTION_REFUSED);
+ this.transport.close(Cr.NS_BINDING_ABORTED);
+ return true;
+ },
+ onOutputStreamReady: function(outstream) {
+ do_print("outputstream handler.");
+ do_check_neq(typeof(outstream), undefined);
+ try {
+ outstream.write(this.dummyContent, this.dummyContent.length);
+ } catch (e) {
+ this.processException(e);
+ this.next();
+ return;
+ }
+ do_print("no exception on write. Wait for read.");
+ },
+ onInputStreamReady: function(instream) {
+ do_print("inputstream handler.");
+ do_check_neq(typeof(instream), undefined);
+ try {
+ instream.available();
+ } catch (e) {
+ this.processException(e);
+ this.next();
+ 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 URI = ios.newURI("http://localhost:" + serv.listener.port + "/just/a/test", null, null);
+ ios.QueryInterface(Ci.nsISpeculativeConnect)
+ .speculativeConnect(URI, 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) {
+ do_print(host);
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+ do_check_neq(typeof(sts), undefined);
+ var transport = sts.createTransport(null, 0, host, 80, null);
+ do_check_neq(typeof(transport), undefined);
+
+ transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
+ do_check_eq(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
+
+ var outStream = transport.openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED,0,0);
+ var inStream = transport.openInputStream(0,0,0);
+ do_check_neq(typeof(outStream), undefined);
+ do_check_neq(typeof(inStream), undefined);
+
+ var callback = new TestFailedStreamCallback(transport, host, next);
+ do_check_neq(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) {
+ do_print("Proxy: " + proxyHost);
+ var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+ do_check_neq(typeof(sts), undefined);
+ var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService();
+ do_check_neq(typeof(pps), undefined);
+
+ var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, 0, 1, null);
+ do_check_neq(typeof(proxyInfo), undefined);
+
+ var transport = sts.createTransport(null, 0, "dummyHost", 80, proxyInfo);
+ do_check_neq(typeof(transport), undefined);
+
+ transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
+
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
+ do_check_eq(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);
+ do_check_neq(typeof(outStream), undefined);
+ do_check_neq(typeof(inStream), undefined);
+
+ var callback = new TestFailedStreamCallback(transport, proxyHost, next);
+ do_check_neq(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;
+ }
+ do_print("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);
+
+ do_test_pending();
+ next_test();
+}
+
diff --git a/netwerk/test/unit/test_standardurl.js b/netwerk/test/unit/test_standardurl.js
new file mode 100644
index 0000000000..c4d44f41ff
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl.js
@@ -0,0 +1,455 @@
+"use strict";
+
+const StandardURL = Components.Constructor("@mozilla.org/network/standard-url;1",
+ "nsIStandardURL",
+ "init");
+const nsIStandardURL = Components.interfaces.nsIStandardURL;
+
+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", "path", "filePath", "query",
+ "ref", "directory", "fileName", "fileBaseName", "fileExtension"]
+ .map(function(prop) {
+ dump("Testing '"+ prop + "'\n");
+ do_check_eq(a[prop], b[prop]);
+ });
+ } else {
+ do_check_neq(a.spec, b.spec);
+ }
+ do_check_eq(expect, a.equals(b));
+ do_check_eq(expect, b.equals(a));
+}
+
+function stringToURL(str) {
+ return (new StandardURL(nsIStandardURL.URLTYPE_AUTHORITY, 80,
+ str, "UTF-8", null))
+ .QueryInterface(Components.interfaces.nsIURL);
+}
+
+function pairToURLs(pair) {
+ do_check_eq(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.path = "";
+ target.path = "";
+
+ do_check_eq(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.query = "foo";
+
+ do_check_eq(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.query = "foo";
+ symmetricEquality(false, provided, target);
+
+ var newProvided = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI("#bar", null, provided)
+ .QueryInterface(Components.interfaces.nsIURL);
+
+ do_check_eq(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.ref = ref;
+ var b = stringToURL(result);
+
+ do_check_eq(a.spec, b.spec);
+ do_check_eq(ref, b.ref);
+ symmetricEquality(true, a, b);
+
+ /* Test2: starting with non-empty */
+ a.ref = "yyyy";
+ var c = stringToURL(before);
+ c.ref = "yyyy";
+ symmetricEquality(true, a, c);
+
+ /* Test3: reset the ref */
+ a.ref = "";
+ symmetricEquality(true, a, stringToURL(before));
+
+ /* Test4: verify again after reset */
+ a.ref = ref;
+ 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.host = "[2001::1]";
+ do_check_eq(url.host, "2001::1");
+
+ url = stringToURL("http://example.com");
+ url.hostPort = "[2001::1]:30";
+ do_check_eq(url.host, "2001::1");
+ do_check_eq(url.port, 30);
+ do_check_eq(url.hostPort, "[2001::1]:30");
+
+ url = stringToURL("http://example.com");
+ url.hostPort = "2001:1";
+ do_check_eq(url.host, "0.0.7.209");
+ do_check_eq(url.port, 1);
+ do_check_eq(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.host = "2001::1"; }, "missing brackets");
+ Assert.throws(() => { url.host = "[2001::1]:20"; }, "url.host with port");
+ Assert.throws(() => { url.host = "[2001::1"; }, "missing last bracket");
+ Assert.throws(() => { url.host = "2001::1]"; }, "missing first bracket");
+ Assert.throws(() => { url.host = "2001[::1]"; }, "bad bracket position");
+ Assert.throws(() => { url.host = "[]"; }, "empty IPv6 address");
+ Assert.throws(() => { url.host = "[hello]"; }, "bad IPv6 address");
+ Assert.throws(() => { url.host = "[192.168.1.1]"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "2001::1"; }, "missing brackets");
+ Assert.throws(() => { url.hostPort = "[2001::1]30"; }, "missing : after IP");
+ Assert.throws(() => { url.hostPort = "[2001:1]"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "[2001:1]10"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "[2001:1]10:20"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "[2001:1]:10:20"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "[2001:1"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "2001]:1"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = "2001:1]"; }, "bad IPv6 address");
+ Assert.throws(() => { url.hostPort = ""; }, "Empty hostPort should fail");
+ Assert.throws(() => { url.hostPort = "[2001::1]:"; }, "missing port number");
+ Assert.throws(() => { url.hostPort = "[2001::1]:bad"; }, "bad port number");
+ run_next_test();
+});
+
+add_test(function test_clearedSpec()
+{
+ var url = stringToURL("http://example.com/path");
+ Assert.throws(() => { url.spec = "http: example"; }, "set bad spec");
+ Assert.throws(() => { url.spec = ""; }, "set empty spec");
+ do_check_eq(url.spec, "http://example.com/path");
+ url.host = "allizom.org";
+
+ 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");
+ do_check_eq(url.spec, "http://example.com/?a[x]=1");
+
+ url = stringToURL("http://example.com/?a%5Bx%5D=1");
+ do_check_eq(url.spec, "http://example.com/?a%5Bx%5D=1");
+
+ url = stringToURL("http://[2001::1]/?a[x]=1");
+ do_check_eq(url.spec, "http://[2001::1]/?a[x]=1");
+
+ url = stringToURL("http://[2001::1]/?a%5Bx%5D=1");
+ do_check_eq(url.spec, "http://[2001::1]/?a%5Bx%5D=1");
+
+ // Path
+ url = stringToURL("http://example.com/brackets[x]/test");
+ do_check_eq(url.spec, "http://example.com/brackets[x]/test");
+
+ url = stringToURL("http://example.com/a%5Bx%5D/test");
+ do_check_eq(url.spec, "http://example.com/a%5Bx%5D/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'");
+ do_check_eq(url.spec, "http://example.com/dir'/file'.ext'");
+ run_next_test();
+});
+
+add_test(function test_accentEncoding()
+{
+ var url = stringToURL("http://example.com/?hello=`");
+ do_check_eq(url.spec, "http://example.com/?hello=`");
+ do_check_eq(url.query, "hello=`");
+
+ url = stringToURL("http://example.com/?hello=%2C");
+ do_check_eq(url.spec, "http://example.com/?hello=%2C");
+ do_check_eq(url.query, "hello=%2C");
+ run_next_test();
+});
+
+add_test(function test_percentDecoding()
+{
+ var url = stringToURL("http://%70%61%73%74%65%62%69%6E.com");
+ do_check_eq(url.spec, "http://pastebin.com/");
+
+ // We shouldn't unescape characters that are not allowed in the hostname.
+ url = stringToURL("http://example.com%0a%23.google.com/");
+ do_check_eq(url.spec, "http://example.com%0a%23.google.com/");
+ 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 properties = ["spec", "scheme", "userPass", "username",
+ "password", "hostPort", "host", "path", "ref",
+ "query", "fileName", "filePath", "fileBaseName", "fileExtension"];
+ for (let prop of properties) {
+ Assert.throws(() => url[prop] = hugeString,
+ /NS_ERROR_MALFORMED_URI/,
+ `Passing a huge string to "${prop}" 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 ");
+ do_check_eq(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.filePath = "pa\r\n\tth";
+ do_check_eq(url.spec, "http://test.com/pa%0D%0A%09th?query#hash");
+ url.query = "qu\r\n\tery";
+ do_check_eq(url.spec, "http://test.com/pa%0D%0A%09th?qu%0D%0A%09ery#hash");
+ url.ref = "ha\r\n\tsh";
+ do_check_eq(url.spec, "http://test.com/pa%0D%0A%09th?qu%0D%0A%09ery#ha%0D%0A%09sh");
+ url.fileName = "fi\r\n\tle.name";
+ do_check_eq(url.spec, "http://test.com/fi%0D%0A%09le.name?qu%0D%0A%09ery#ha%0D%0A%09sh");
+
+ run_next_test();
+});
+
+add_test(function test_backslashReplacement()
+{
+ var url = stringToURL("http:\\\\test.com\\path/to\\file?query\\backslash#hash\\");
+ do_check_eq(url.spec, "http://test.com/path/to/file?query\\backslash#hash\\");
+
+ url = stringToURL("http:\\\\test.com\\example.org/path\\to/file");
+ do_check_eq(url.spec, "http://test.com/example.org/path/to/file");
+ do_check_eq(url.host, "test.com");
+ do_check_eq(url.path, "/example.org/path/to/file");
+
+ 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 ");
+ do_check_eq(url.spec, "http://example.com/");
+ url.spec = "\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 ";
+ do_check_eq(url.spec, "http://test.com/");
+ Assert.throws(() => { url.spec = "\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 "; }, "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");
+ do_check_eq(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.filePath = "pa\0th";
+ do_check_eq(url.spec, "http://example.com/pa%00th?query#hash");
+ url.query = "qu\0ery";
+ do_check_eq(url.spec, "http://example.com/pa%00th?qu%00ery#hash");
+ url.ref = "ha\0sh";
+ do_check_eq(url.spec, "http://example.com/pa%00th?qu%00ery#ha%00sh");
+ url.fileName = "fi\0le.name";
+ do_check_eq(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) {
+ do_check_eq(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);
+ do_check_eq(url.spec, spec);
+ }
+
+ var url = stringToURL("resource://path/to/resource/");
+ url.host = "123";
+ do_check_eq(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.host = "a" + String.fromCharCode(i) + "b"; }, "Trying to set hostname containing char code: " + i);
+ }
+ for (let c of "@[]*<>|:\"") {
+ Assert.throws(() => { url.host = "a" + c; }, "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();
+});
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..12c6191430
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl_default_port.js
@@ -0,0 +1,51 @@
+/* -*- 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";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+const StandardURL = Components.Constructor("@mozilla.org/network/standard-url;1",
+ "nsIStandardURL",
+ "init");
+function run_test() {
+ function stringToURL(str) {
+ return (new StandardURL(Ci.nsIStandardURL.URLTYPE_AUTHORITY, 80,
+ str, "UTF-8", null))
+ .QueryInterface(Ci.nsIStandardURL);
+ }
+
+ // Create a nsStandardURL:
+ var origUrlStr = "http://foo.com/";
+ var stdUrl = stringToURL(origUrlStr);
+ var stdUrlAsUri = stdUrl.QueryInterface(Ci.nsIURI);
+ do_check_eq(-1, stdUrlAsUri.port);
+
+ // Changing default port shouldn't adjust the value returned by "port",
+ // or the string representation.
+ stdUrl.setDefaultPort(100);
+ do_check_eq(-1, stdUrlAsUri.port);
+ do_check_eq(stdUrlAsUri.spec, origUrlStr);
+
+ // Changing port directly should update .port and .spec, though:
+ stdUrlAsUri.port = "200";
+ do_check_eq(200, stdUrlAsUri.port);
+ do_check_eq(stdUrlAsUri.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:
+ stdUrl.setDefaultPort(200);
+ do_check_eq(-1, stdUrlAsUri.port);
+ do_check_eq(stdUrlAsUri.spec, origUrlStr);
+
+ // And further changes to default port should not make custom port reappear.
+ stdUrl.setDefaultPort(300);
+ do_check_eq(-1, stdUrlAsUri.port);
+ do_check_eq(stdUrlAsUri.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..cc0016964c
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl_port.js
@@ -0,0 +1,56 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function run_test() {
+ function makeURI(aURLSpec, aCharset) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newURI(aURLSpec, aCharset, null);
+ }
+
+ var httpURI = makeURI("http://foo.com");
+ do_check_eq(-1, httpURI.port);
+
+ // Setting to default shouldn't cause a change
+ httpURI.port = 80;
+ do_check_eq(-1, httpURI.port);
+
+ // Setting to default after setting to non-default shouldn't cause a change (bug 403480)
+ httpURI.port = 123;
+ do_check_eq(123, httpURI.port);
+ httpURI.port = 80;
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ // URL parsers shouldn't set ports to default value (bug 407538)
+ httpURI.spec = "http://foo.com:81";
+ do_check_eq(81, httpURI.port);
+ httpURI.spec = "http://foo.com:80";
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com:80");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ httpURI = makeURI("http://foo.com:80");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/80/.test(httpURI.spec));
+
+ httpURI = makeURI("https://foo.com");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/443/.test(httpURI.spec));
+
+ httpURI = makeURI("https://foo.com:443");
+ do_check_eq(-1, httpURI.port);
+ do_check_false(/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..6354be5d28
--- /dev/null
+++ b/netwerk/test/unit/test_streamcopier.js
@@ -0,0 +1,53 @@
+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.
+ do_check_true(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: function(aRequest, aContext) {
+ do_check_eq(aContext.wrappedJSObject, ctx);
+ },
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ do_check_eq(aStatusCode, 0);
+ do_check_eq(aContext.wrappedJSObject, ctx);
+ 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) {
+ do_check_eq(e.result, Components.results.NS_BASE_STREAM_CLOSED);
+ }
+ do_check_eq(result, testStr);
+ do_test_finished();
+ }
+ };
+
+ streamCopier.asyncCopy(observer, ctx);
+ do_test_pending();
+}
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..f41932a467
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_before_connect.js
@@ -0,0 +1,102 @@
+
+var CC = Components.Constructor;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.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: function(socket, trans) {
+ do_check_true(false, "Socket should not have tried to connect!");
+ },
+
+ onStopListening: function(socket) {
+ },
+
+ stop: function() {
+ try { this.listener.close(); } catch(ignore) {}
+ }
+}
+
+var requestListenerObserver = {
+
+ QueryInterface: function queryinterface(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIObserver))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(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, ctx) {
+ },
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, ctx, status) {
+ do_execute_soon(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() {
+
+ serv = new TestServer();
+
+ obs.addObserver(requestListenerObserver, "http-on-modify-request", false);
+ var chan = NetUtil.newChannel({
+ uri:"http://localhost:" + serv.port,
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(listener);
+
+ do_register_cleanup(function(){ serv.stop(); });
+});
+
+function run_test() {
+ run_next_test();
+}
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..a4f7c221e8
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_modified.js
@@ -0,0 +1,175 @@
+// This file tests async handling of a channel suspended in http-on-modify-request.
+
+var CC = Components.Constructor;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var obs = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+
+var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.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);
+ do_check_true(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);
+ do_check_true(true, "Received expected request.");
+}
+
+function onModifyListener(callback) {
+ obs.addObserver({
+ observe: function(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", false);
+}
+
+function startChannelRequest(baseUrl, flags, expectedResponse=null) {
+ var chan = NetUtil.newChannel({
+ uri: baseUrl,
+ loadUsingSystemPrincipal: true
+ });
+ chan.asyncOpen2(new ChannelListener((request, data, context) => {
+ if (expectedResponse) {
+ do_check_eq(data, expectedResponse);
+ } else {
+ do_check_true(!!!data, "no response");
+ }
+ do_execute_soon(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();
+
+ do_register_cleanup(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..bad8047fe0
--- /dev/null
+++ b/netwerk/test/unit/test_synthesized_response.js
@@ -0,0 +1,243 @@
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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, null, null);
+}
+
+// ensure the cache service is prepped when running the test
+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: XPCOMUtils.generateQI([Ci.nsINetworkInterceptController,
+ Ci.nsIInterfaceRequestor,
+ Ci.nsIProgressEventSink]),
+ getInterface: function(iid) {
+ return this.QueryInterface(iid);
+ },
+ onProgress: function(request, context, progress, progressMax) {
+ gotOnProgress = true;
+ },
+ onStatus: function(request, context, status, statusArg) {
+ gotOnStatus = true;
+ },
+ shouldPrepareForIntercept: function() {
+ do_check_eq(this.numChecks, 0);
+ this.numChecks++;
+ return true;
+ },
+ channelIntercepted: function(channel) {
+ channel.QueryInterface(Ci.nsIInterceptedChannel);
+ if (body) {
+ var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ synthesized.data = body;
+
+ NetUtil.asyncCopy(synthesized, channel.responseBody, function() {
+ channel.finishSynthesizedResponse('');
+ });
+ }
+ if (cb) {
+ cb(channel);
+ }
+ return {
+ dispatch: function() { }
+ };
+ },
+ };
+ 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) {
+ do_check_eq(buffer, NON_REMOTE_BODY);
+ do_check_true(gotOnStatus);
+ do_check_true(gotOnProgress);
+ run_next_test();
+}
+
+function handle_synthesized_response_2(request, buffer) {
+ do_check_eq(buffer, NON_REMOTE_BODY_2);
+ do_check_true(gotOnStatus);
+ do_check_true(gotOnProgress);
+ run_next_test();
+}
+
+function handle_remote_response(request, buffer) {
+ do_check_eq(buffer, REMOTE_BODY);
+ do_check_true(gotOnStatus);
+ do_check_true(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.asyncOpen2(new ChannelListener(handle_remote_response, null));
+});
+
+// synthesize a response
+add_test(function() {
+ var chan = make_channel(URL + '/body', NON_REMOTE_BODY);
+ chan.asyncOpen2(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.asyncOpen2(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.asyncOpen2(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;
+ NetUtil.asyncCopy(synthesized, channel.responseBody, function() {
+ channel.synthesizeHeader("Content-Length", NON_REMOTE_BODY.length);
+ channel.finishSynthesizedResponse('');
+ });
+ });
+ });
+ chan.asyncOpen2(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.asyncOpen2(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;
+
+ NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
+ // 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.finishSynthesizedResponse('');
+ });
+ });
+ chan.asyncOpen2(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.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ chan.asyncOpen2(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.cancel();
+ } catch (x) {
+ gotexception = true;
+ }
+ do_check_true(gotexception);
+ });
+ });
+ chan.asyncOpen2(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;
+
+ NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
+ let channel = intercepted.channel;
+ intercepted.finishSynthesizedResponse('');
+ channel.cancel(Cr.NS_BINDING_ABORTED);
+ });
+ });
+ chan.asyncOpen2(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;
+
+ NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
+ intercepted.channel.cancel(Cr.NS_BINDING_ABORTED);
+ intercepted.finishSynthesizedResponse('');
+ });
+ });
+ chan.asyncOpen2(new ChannelListener(run_next_test, null,
+ CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL));
+});
+
+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..97c119b994
--- /dev/null
+++ b/netwerk/test/unit/test_throttlechannel.js
@@ -0,0 +1,41 @@
+// Test nsIThrottledInputChannel interface.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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(Components.interfaces.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.asyncOpen2(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..fdfa80d1b0
--- /dev/null
+++ b/netwerk/test/unit/test_throttlequeue.js
@@ -0,0 +1,23 @@
+// Test ThrottleQueue initialization.
+
+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..afb8278949
--- /dev/null
+++ b/netwerk/test/unit/test_throttling.js
@@ -0,0 +1,57 @@
+// Test nsIThrottledInputChannel interface.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+ mime.addContentLength = true;
+
+ 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.asyncOpen2(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..6f3170af02
--- /dev/null
+++ b/netwerk/test/unit/test_tldservice_nextsubdomain.js
@@ -0,0 +1,28 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+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);
+ do_check_eq(r, test.result);
+ } catch (e) {
+ do_check_true(test.throw);
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_tls_server.js b/netwerk/test/unit/test_tls_server.js
new file mode 100644
index 0000000000..d805359c7b
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server.js
@@ -0,0 +1,237 @@
+/* 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 { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Promise: promise } =
+ Cu.import("resource://gre/modules/Promise.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 run_test() {
+ run_next_test();
+}
+
+function getCert() {
+ let deferred = promise.defer();
+ certService.getOrCreateCert("tls-test", {
+ handleCert: function(c, rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve(c);
+ }
+ });
+ return deferred.promise;
+}
+
+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: function(socket, transport) {
+ do_print("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: function(socket, status) {
+ do_print("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: function(input) {
+ NetUtil.asyncCopy(input, output);
+ }
+ }, 0, 0, Services.tm.currentThread);
+ },
+ onStopListening: function() {}
+ };
+
+ tlsServer.setSessionCache(false);
+ tlsServer.setSessionTickets(false);
+ tlsServer.setRequestClientCertificate(clientCertificateConfig);
+
+ 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, cert, expectingBadCertAlert) {
+ let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
+ let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
+ let transport =
+ socketTransportService.createTransport(["ssl"], 1, "127.0.0.1", port, null);
+ let input;
+ let output;
+
+ let inputDeferred = promise.defer();
+ let outputDeferred = promise.defer();
+
+ let handler = {
+
+ onTransportStatus: function(transport, status) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ onInputStreamReady: function(input) {
+ try {
+ let data = NetUtil.readInputStreamToString(input, input.available());
+ equal(data, "HELLO", "Echoed data received");
+ input.close();
+ output.close();
+ ok(!expectingBadCertAlert, "No bad cert alert expected");
+ inputDeferred.resolve();
+ } catch (e) {
+ let errorCode = -1 * (e.result & 0xFFFF);
+ if (expectingBadCertAlert && errorCode == SSL_ERROR_BAD_CERT_ALERT) {
+ inputDeferred.resolve();
+ } else {
+ inputDeferred.reject(e);
+ }
+ }
+ },
+
+ onOutputStreamReady: function(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);
+ do_print("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) {
+ do_print("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,
+ expectingBadCertAlert: false
+}, {
+ expectingPeerCert: true,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS,
+ sendClientCert: false,
+ expectingBadCertAlert: true
+}, {
+ expectingPeerCert: true,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS,
+ sendClientCert: true,
+ expectingBadCertAlert: false
+}, {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS,
+ sendClientCert: false,
+ expectingBadCertAlert: false
+}, {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER,
+ sendClientCert: true,
+ expectingBadCertAlert: false
+}, {
+ expectingPeerCert: false,
+ clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER,
+ sendClientCert: false,
+ expectingBadCertAlert: 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(function*() {
+ let cert = yield 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 port = startServer(cert,
+ t.expectingPeerCert,
+ t.clientCertificateConfig,
+ v.version,
+ v.versionStr);
+ storeCertOverride(port, cert);
+ yield startClient(port, t.sendClientCert ? cert : null, t.expectingBadCertAlert);
+ }
+ }
+});
+
+do_register_cleanup(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..b63c0189bd
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server_multiple_clients.js
@@ -0,0 +1,141 @@
+/* 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 { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Promise: promise } =
+ Cu.import("resource://gre/modules/Promise.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 run_test() {
+ run_next_test();
+}
+
+function getCert() {
+ let deferred = promise.defer();
+ certService.getOrCreateCert("tls-test", {
+ handleCert: function(c, rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve(c);
+ }
+ });
+ return deferred.promise;
+}
+
+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: function(socket, transport) {
+ do_print("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: function(socket, status) {
+ do_print("TLS handshake done");
+
+ input.asyncWait({
+ onInputStreamReady: function(input) {
+ NetUtil.asyncCopy(input, output);
+ }
+ }, 0, 0, Services.tm.currentThread);
+ },
+ onStopListening: function() {}
+ };
+
+ tlsServer.setSessionCache(true);
+ 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"], 1, "127.0.0.1", port, null);
+ let input;
+ let output;
+
+ let inputDeferred = promise.defer();
+ let outputDeferred = promise.defer();
+
+ let handler = {
+
+ onTransportStatus: function(transport, status) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ onInputStreamReady: function(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: function(output) {
+ try {
+ output.write("HELLO", 5);
+ do_print("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(function*() {
+ let cert = yield getCert();
+ ok(!!cert, "Got self-signed cert");
+ let port = startServer(cert);
+ storeCertOverride(port, cert);
+ yield startClient(port);
+ yield 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..00ccbb1277
--- /dev/null
+++ b/netwerk/test/unit/test_traceable_channel.js
@@ -0,0 +1,150 @@
+// 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.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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: function(request, context) {
+ dump("*** tracing listener onStartRequest\n");
+
+ gotOnStartRequest = true;
+
+ request.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
+
+// local/remote addresses broken in e10s: disable for now
+ do_check_eq(request.localAddress, "127.0.0.1");
+ do_check_eq(request.localPort > 0, true);
+ do_check_neq(request.localPort, PORT);
+ do_check_eq(request.remoteAddress, "127.0.0.1");
+ do_check_eq(request.remotePort, PORT);
+
+ // Make sure listener can't be replaced after OnStartRequest was called.
+ request.QueryInterface(Components.interfaces.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: function(request, context, statusCode) {
+ dump("*** tracing listener onStopRequest\n");
+
+ do_check_eq(gotOnStartRequest, true);
+
+ try {
+ var sin = Components.classes["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+
+ streamSink.close();
+ var input = pipe.inputStream;
+ sin.init(input);
+ do_check_eq(sin.available(), originalBody.length);
+
+ var result = sin.read(originalBody.length);
+ do_check_eq(result, originalBody);
+
+ input.close();
+ } catch (e) {
+ dump("TracingListener.onStopRequest swallowing exception: " + e + "\n");
+ } finally {
+ httpserver.stop(do_test_finished);
+ }
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports)
+ )
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ listener: null
+}
+
+
+function HttpResponseExaminer() {}
+
+HttpResponseExaminer.prototype = {
+ register: function() {
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Components.interfaces.nsIObserverService).
+ addObserver(this, "http-on-examine-response", true);
+ dump("Did HttpResponseExaminer.register\n");
+ },
+
+ // Replace channel's listener.
+ observe: function(subject, topic, data) {
+ dump("In HttpResponseExaminer.observe\n");
+ try {
+ subject.QueryInterface(Components.interfaces.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: function(iid) {
+ if (iid.equals(Components.interfaces.nsIObserver) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+}
+
+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(Components.interfaces.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.asyncOpen2(new ChannelListener(channel_finished));
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_udp_multicast.js b/netwerk/test/unit/test_udp_multicast.js
new file mode 100644
index 0000000000..0afa9c5b22
--- /dev/null
+++ b/netwerk/test/unit/test_udp_multicast.js
@@ -0,0 +1,114 @@
+// Bug 960397: UDP multicast options
+
+var { Constructor: CC } = Components;
+
+const UDPSocket = CC("@mozilla.org/network/udp-socket;1",
+ "nsIUDPSocket",
+ "init");
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+Cu.import("resource://gre/modules/Services.jsm");
+
+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;
+const isWinXP = ua.indexOf("Windows NT 5.1") != -1;
+
+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);
+
+ let deferred = promise.defer();
+
+ socket.asyncListen({
+ onPacketReceived: function(s, message) {
+ do_print("Received on port " + socket.port);
+ do_check_eq(message.data, ping);
+ socket.close();
+ deferred.resolve(message.data);
+ },
+ onStopListening: function(socket, status) {}
+ });
+
+ do_print("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();
+ deferred.reject();
+ }, TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
+
+ return deferred.promise;
+}
+
+add_test(() => {
+ do_print("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(() => {
+ do_print("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
+ );
+});
+
+// The following multicast interface test doesn't work on Windows XP, as it
+// appears to allow packets no matter what address is given, so we'll skip the
+// test there.
+if (!isWinXP) {
+ add_test(() => {
+ do_print("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(() => {
+ do_print("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..c96be003ab
--- /dev/null
+++ b/netwerk/test/unit/test_udpsocket.js
@@ -0,0 +1,63 @@
+/* -*- 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";
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const HELLO_WORLD = "Hello World";
+
+add_test(function test_udp_message_raw_data() {
+ do_print("test for nsIUDPMessage.rawData");
+
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(Ci.nsIUDPSocket);
+
+ socket.init(-1, true, Services.scriptSecurityManager.getSystemPrincipal());
+ do_print("Port assigned : " + socket.port);
+ socket.asyncListen({
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIUDPSocketListener]),
+ onPacketReceived : function(aSocket, aMessage){
+ let recv_data = String.fromCharCode.apply(null, aMessage.rawData);
+ do_check_eq(recv_data, HELLO_WORLD);
+ do_check_eq(recv_data, aMessage.data);
+ socket.close();
+ run_next_test();
+ },
+ onStopListening: function(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, rawData.length);
+ do_check_eq(written, HELLO_WORLD.length);
+});
+
+add_test(function test_udp_send_stream() {
+ do_print("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 : XPCOMUtils.generateQI([Ci.nsIUDPSocketListener]),
+ onPacketReceived : function(aSocket, aMessage){
+ let recv_data = String.fromCharCode.apply(null, aMessage.rawData);
+ do_check_eq(recv_data, HELLO_WORLD);
+ socket.close();
+ run_next_test();
+ },
+ onStopListening: function(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);
+});
+
+function run_test(){
+ 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..d1e9494cd7
--- /dev/null
+++ b/netwerk/test/unit/test_unescapestring.js
@@ -0,0 +1,31 @@
+const ONLY_NONASCII = Components.interfaces.nsINetUtil.ESCAPE_URL_ONLY_NONASCII;
+const SKIP_CONTROL = Components.interfaces.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 = Components.classes["@mozilla.org/network/util;1"]
+ .getService(Components.interfaces.nsINetUtil);
+
+ for (var i = 0; i < tests.length; ++i) {
+ dump("Test " + i + " (" + tests[i][0] + ", " + tests[i][2] + ")\n");
+ do_check_eq(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..5dda0c8644
--- /dev/null
+++ b/netwerk/test/unit/test_unix_domain.js
@@ -0,0 +1,545 @@
+// Exercise Unix domain sockets.
+
+var CC = Components.Constructor;
+
+const UnixServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initWithFilename");
+
+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;
+ }
+
+ 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);
+
+ 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');
+ do_print("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.
+ do_print("creating socket: " + socketName.path);
+ let server = new UnixServerSocket(socketName, allPermissions, -1);
+ server.asyncListen({
+ onSocketAccepted: function(aServ, aTransport) {
+ do_print("called test_echo's onSocketAccepted");
+ log += 'a';
+
+ do_check_eq(aServ, server);
+
+ let connection = aTransport;
+
+ // Check the server socket's self address.
+ let connectionSelfAddr = connection.getScriptableSelfAddr();
+ do_check_eq(connectionSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ do_check_eq(connectionSelfAddr.address, socketName.path);
+
+ // The client socket is anonymous, so the server transport should
+ // have an empty peer address.
+ do_check_eq(connection.host, '');
+ do_check_eq(connection.port, 0);
+ let connectionPeerAddr = connection.getScriptablePeerAddr();
+ do_check_eq(connectionPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ do_check_eq(connectionPeerAddr.address, '');
+
+ let serverAsyncInput = connection.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
+ let serverOutput = connection.openOutputStream(0, 0, 0);
+
+ serverAsyncInput.asyncWait(function (aStream) {
+ do_print("called test_echo's server's onInputStreamReady");
+ let serverScriptableInput = new ScriptableInputStream(aStream);
+
+ // Receive data from the client, and send back a response.
+ do_check_eq(serverScriptableInput.readBytes(17), "Mervyn Murgatroyd");
+ do_print("server has read message from client");
+ serverOutput.write("Ruthven Murgatroyd", 18);
+ do_print("server has written to client");
+ }, 0, 0, threadManager.currentThread);
+ },
+
+ onStopListening: function(aServ, aStatus) {
+ do_print("called test_echo's onStopListening");
+ log += 's';
+
+ do_check_eq(aServ, server);
+ do_check_eq(log, 'acs');
+
+ run_next_test();
+ }
+ });
+
+ // Create a client socket, and connect to the server.
+ let client = socketTransportService.createUnixDomainTransport(socketName);
+ do_check_eq(client.host, socketName.path);
+ do_check_eq(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);
+ do_print("client has written to server");
+
+ clientAsyncInput.asyncWait(function (aStream) {
+ do_print("called test_echo's client's onInputStreamReady");
+ log += 'c';
+
+ do_check_eq(aStream, clientAsyncInput);
+
+ // Now that the connection has been established, we can check the
+ // transport's self and peer addresses.
+ let clientSelfAddr = client.getScriptableSelfAddr();
+ do_check_eq(clientSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ do_check_eq(clientSelfAddr.address, '');
+
+ do_check_eq(client.host, socketName.path); // re-check, but hey
+ let clientPeerAddr = client.getScriptablePeerAddr();
+ do_check_eq(clientPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
+ do_check_eq(clientPeerAddr.address, socketName.path);
+
+ do_check_eq(clientInput.readBytes(18), "Ruthven Murgatroyd");
+ do_print("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) {
+ do_print("called test_no_such_socket's onInputStreamReady");
+
+ do_check_eq(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.
+ let server = 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) {
+ do_print("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) {
+ do_print("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) {
+ do_print("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) {
+ do_print("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);
+ do_check_eq(serverScriptableInput.readBytes(8), "Hanratty");
+ serverOutput.write("Ferlingatti", 11);
+ }, 0, 0, threadManager.currentThread);
+ }
+
+ function client3InputStreamReady(aStream) {
+ do_print("called client3's onInputStreamReady");
+ log += '3';
+
+ let client3Input = new ScriptableInputStream(aStream);
+
+ do_check_eq(client3Input.readBytes(11), "Ferlingatti");
+
+ client3.close(Cr.NS_OK);
+ server.close();
+ }
+
+ function stopListening(aServ, aStatus) {
+ do_print("called test_connect_permission's server's stopListening");
+ log += 's';
+
+ do_check_eq(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: 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) {
+ do_print("test_keep_when_offline: onAccepted called");
+ log += 'a';
+ do_check_eq(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';
+ do_print("test_keep_when_offline: clientReady called: " + log);
+ do_check_eq(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);
+ do_check_eq(ex.result, Cr.NS_BASE_STREAM_CLOSED);
+
+ do_print("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.
+ do_check_eq(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';
+ do_print("test_keep_when_offline: serverReady called: " + log);
+ do_check_eq(aStream, serverInput);
+
+ // Check the message from the client.
+ do_check_eq(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.
+ do_print("closing server transport");
+ server.close(Cr.NS_OK);
+ log += '(';
+ }
+ }
+
+ // We have stopped listening.
+ function onStopListening(aServ, aStatus) {
+ do_print("test_keep_when_offline: onStopListening called");
+ log += 'L';
+ do_check_eq(log, 'acscscscscsocscscscscs(c)L');
+
+ do_check_eq(aServ, listener);
+ do_check_eq(aStatus, Cr.NS_BINDING_ABORTED);
+
+ run_next_test();
+ }
+}
diff --git a/netwerk/test/unit/test_websocket_offline.js b/netwerk/test/unit/test_websocket_offline.js
new file mode 100644
index 0000000000..f44360221f
--- /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/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// 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: function(aContext, aSize) {},
+ onBinaryMessageAvailable: function(aContext, aMsg) {},
+ onMessageAvailable: function(aContext, aMsg) {},
+ onServerClose: function(aContext, aCode, aReason) {},
+ onStart: function(aContext)
+ {
+ // onStart is not called when a connection fails
+ do_check_true(false);
+ },
+ onStop: function(aContext, aStatusCode)
+ {
+ do_check_neq(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(Components.interfaces.nsIWebSocketChannel);
+ chan.initLoadInfo(null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_WEBSOCKET);
+
+ var uri = Services.io.newURI(url, null, null);
+ 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..1f49a1aec0
--- /dev/null
+++ b/netwerk/test/unit/test_xmlhttprequest.js
@@ -0,0 +1,54 @@
+
+Cu.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 = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open("GET", "http://localhost:" +
+ httpserver.identity.primaryPort + testpath, async);
+ return xhr;
+}
+
+function checkResults(xhr)
+{
+ if (xhr.readyState != 4)
+ return false;
+
+ do_check_eq(xhr.status, 200);
+ do_check_eq(xhr.responseText, httpbody);
+
+ var root_node = xhr.responseXML.getElementsByTagName('root').item(0);
+ do_check_eq(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);
+ }, false);
+ 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..0cbcbc423d
--- /dev/null
+++ b/netwerk/test/unit/xpcshell.ini
@@ -0,0 +1,369 @@
+[DEFAULT]
+head = head_channels.js head_cache.js head_cache2.js
+tail =
+support-files =
+ CA.cert.der
+ client_cert_chooser.js
+ client_cert_chooser.manifest
+ 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
+ ../../dns/effective_tld_names.dat
+
+[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]
+# GC, that this patch is dependent on, doesn't work well on Android.
+skip-if = os == "android"
+[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_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_assoc.js]
+[test_auth_jar.js]
+[test_auth_proxy.js]
+[test_authentication.js]
+[test_authpromptwrapper.js]
+[test_auth_dialog_permission.js]
+[test_backgroundfilesaver.js]
+# Runs for a long time, causing intermittent time-outs on Android, bug 995686
+requesttimeoutfactor = 2
+[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_bug376660.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_bug455598.js]
+[test_bug468426.js]
+[test_bug468594.js]
+[test_bug470716.js]
+[test_bug477578.js]
+[test_bug479413.js]
+[test_bug479485.js]
+[test_bug482601.js]
+[test_bug484684.js]
+[test_bug490095.js]
+# Bug 675039: intermittent fail on Android-armv6
+skip-if = os == "android"
+[test_bug504014.js]
+[test_bug510359.js]
+[test_bug515583.js]
+[test_bug528292.js]
+[test_bug536324_64bit_content_length.js]
+[test_bug540566.js]
+[test_bug543805.js]
+[test_bug553970.js]
+[test_bug561042.js]
+# Bug 675039: test fails on Android 4.0
+skip-if = os == "android"
+[test_bug561276.js]
+[test_bug580508.js]
+[test_bug586908.js]
+[test_bug596443.js]
+[test_bug618835.js]
+[test_bug633743.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_bug667907.js]
+[test_bug667818.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_bug1218029.js]
+[test_udpsocket.js]
+[test_doomentry.js]
+[test_cacheflags.js]
+[test_cache_jar.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_dns_cancel.js]
+[test_dns_per_interface.js]
+[test_data_protocol.js]
+[test_dns_service.js]
+[test_dns_offline.js]
+[test_dns_onion.js]
+[test_dns_localredirect.js]
+[test_dns_proxy_bypass.js]
+[test_duplicate_headers.js]
+[test_chunked_responses.js]
+[test_content_length_underrun.js]
+[test_event_sink.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_partial_inputstream.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_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]
+# IDNA2008 depends on ICU, not available on android
+skip-if = os == "android"
+[test_immutable.js]
+skip-if = !hasNode
+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_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_original_sent_received_head.js]
+[test_parse_content_type.js]
+[test_permmgr.js]
+[test_plaintext_sniff.js]
+[test_post.js]
+[test_private_necko_channel.js]
+[test_private_cookie_changed.js]
+[test_progress.js]
+[test_protocolproxyservice.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]
+# Bug 675039: test fails consistently on Android
+fail-if = os == "android"
+[test_redirect-caching_passing.js]
+[test_redirect_canceled.js]
+[test_redirect_failure.js]
+# Bug 675039: test fails consistently on Android
+fail-if = os == "android"
+[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_reentrancy.js]
+[test_reopen.js]
+[test_resumable_channel.js]
+[test_resumable_truncate.js]
+[test_safeoutputstream.js]
+[test_simple.js]
+[test_sockettransportsvc_available.js]
+[test_socks.js]
+# 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]
+skip-if = !hasNode
+run-sequentially = node server exceptions dont replay well
+[test_altsvc.js]
+skip-if = !hasNode
+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]
+# The xpcshell temp directory on Android doesn't seem to let us create
+# Unix domain sockets. (Perhaps it's a FAT filesystem?)
+skip-if = os == "android"
+[test_addr_in_use_error.js]
+[test_about_networking.js]
+[test_ping_aboutnetworking.js]
+[test_referrer.js]
+[test_referrer_policy.js]
+[test_predictor.js]
+# Android version detection w/in gecko does not work right on infra, so we just
+# disable this test on all android versions, even though it's enabled on 2.3+ in
+# the wild.
+skip-if = os == "android"
+[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]
+# The local cert service used by this test is not currently shipped on Android
+# Disabled on XP in bug 1190674 for intermittent failures
+skip-if = os == "android" || (os == "win" && (os_version == "5.1" || os_version == "5.2"))
+reason = bug 1190674
+firefox-appdir = browser
+[test_tls_server.js]
+# The local cert service used by this test is not currently shipped on Android
+# Disabled on XP in bug 1190674 for intermittent failures
+skip-if = os == "android" || (os == "win" && (os_version == "5.1" || os_version == "5.2"))
+reason = bug 1190674
+firefox-appdir = browser
+[test_tls_server_multiple_clients.js]
+# The local cert service used by this test is not currently shipped on Android
+skip-if = os == "android"
+[test_1073747.js]
+[test_safeoutputstream_append.js]
+[test_suspend_channel_before_connect.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]
+[test_bug464591.js]
+[test_alt-data_simple.js]
+[test_alt-data_stream.js]
+[test_cache-control_request.js]
+[test_bug1279246.js]
+[test_throttlequeue.js]
+[test_throttlechannel.js]
+[test_throttling.js]
+[test_separate_connections.js]
diff --git a/netwerk/test/unit_ipc/child_app_offline_notifications.js b/netwerk/test/unit_ipc/child_app_offline_notifications.js
new file mode 100644
index 0000000000..870c22b397
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_app_offline_notifications.js
@@ -0,0 +1,43 @@
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function is_app_offline(appId) {
+ let ioservice = Cc['@mozilla.org/network/io-service;1'].
+ getService(Ci.nsIIOService);
+ return ioservice.isAppOffline(appId);
+}
+
+var events_observed_no = 0;
+
+// Holds the last observed app-offline event
+var info = null;
+function observer(aSubject, aTopic, aData) {
+ events_observed_no++;
+ info = aSubject.QueryInterface(Ci.nsIAppOfflineInfo);
+ dump("ChildObserver - subject: {" + aSubject.appId + ", " + aSubject.mode + "} ");
+}
+
+// Add observer for the app-offline notification
+function run_test() {
+ Services.obs.addObserver(observer, "network:app-offline-status-changed", false);
+}
+
+// Chech that the app has the proper offline status
+function check_status(appId, status)
+{
+ do_check_eq(is_app_offline(appId), status == Ci.nsIAppOfflineInfo.OFFLINE);
+}
+
+// Check that the app has the proper offline status
+// and that the correct notification has been received
+function check_notification_and_status(appId, status) {
+ do_check_eq(info.appId, appId);
+ do_check_eq(info.mode, status);
+ do_check_eq(is_app_offline(appId), status == Ci.nsIAppOfflineInfo.OFFLINE);
+}
+
+// Remove the observer from the child process
+function finished() {
+ Services.obs.removeObserver(observer, "network:app-offline-status-changed");
+ do_check_eq(events_observed_no, 2);
+}
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..55cb2fd145
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_channel_id.js
@@ -0,0 +1,45 @@
+/**
+ * Send HTTP requests and notify the parent about their channelId
+ */
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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.asyncOpen2(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/head_cc.js b/netwerk/test/unit_ipc/head_cc.js
new file mode 100644
index 0000000000..2765f95f2a
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_cc.js
@@ -0,0 +1,4 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
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..f5eb45cbb8
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_channels_clone.js
@@ -0,0 +1,8 @@
+//
+// Load standard base class for network tests into child process
+//
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+
+load("../unit/head_channels.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_simple_wrap.js b/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js
new file mode 100644
index 0000000000..04441c22a4
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ 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_bug248970_cookie_wrap.js b/netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js
new file mode 100644
index 0000000000..bb8b288150
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_bug248970_cookie_wrap.js
@@ -0,0 +1,7 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_bug248970_cookie.js");
+} \ No newline at end of file
diff --git a/netwerk/test/unit_ipc/test_bug528292_wrap.js b/netwerk/test/unit_ipc/test_bug528292_wrap.js
new file mode 100644
index 0000000000..117ada7484
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_bug528292_wrap.js
@@ -0,0 +1,6 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+ run_test_in_child("../unit/test_bug528292.js");
+} \ No newline at end of file
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..d82e5f5df6
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_channel_id.js
@@ -0,0 +1,109 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.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();
+ do_check_true(!!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", false);
+ 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_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_wrap.js b/netwerk/test/unit_ipc/test_cookie_header_wrap.js
new file mode 100644
index 0000000000..3a071a6c15
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookie_header_wrap.js
@@ -0,0 +1,11 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_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..dc3ea7d9fc
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookiejars_wrap.js
@@ -0,0 +1,7 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ run_test_in_child("../unit/test_cookiejars.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_per_interface_wrap.js b/netwerk/test/unit_ipc/test_dns_per_interface_wrap.js
new file mode 100644
index 0000000000..25e7217b0e
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_per_interface_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_per_interface.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_httpsuspend_wrap.js b/netwerk/test/unit_ipc/test_httpsuspend_wrap.js
new file mode 100644
index 0000000000..348541283a
--- /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");
+} \ No newline at end of file
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..de4ddc174e
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_predictor_wrap.js
@@ -0,0 +1,36 @@
+// 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).
+//
+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..a1b8adfb9b
--- /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..4fea2fdf65
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect-caching_failure_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_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..8519501865
--- /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..546e051410
--- /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..fbe27697f0
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_failure_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_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..43d72dd059
--- /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");
+} \ No newline at end of file
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_synthesized_response_wrap.js b/netwerk/test/unit_ipc/test_synthesized_response_wrap.js
new file mode 100644
index 0000000000..337646df52
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_synthesized_response_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+ run_test_in_child("../unit/test_synthesized_response.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..ec7a13df9f
--- /dev/null
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -0,0 +1,99 @@
+[DEFAULT]
+head = head_channels_clone.js head_cc.js
+tail =
+skip-if = toolkit == 'android'
+support-files =
+ child_channel_id.js
+ !/netwerk/test/unit/test_XHR_redirects.js
+ !/netwerk/test/unit/test_bug248970_cookie.js
+ !/netwerk/test/unit/test_bug528292.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_cookie_header.js
+ !/netwerk/test/unit/test_cookiejars.js
+ !/netwerk/test/unit/test_dns_cancel.js
+ !/netwerk/test/unit/test_dns_per_interface.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_synthesized_response.js
+ !/netwerk/test/unit/test_xmlhttprequest.js
+ !/netwerk/test/unit/head_channels.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
+
+[test_bug528292_wrap.js]
+[test_bug248970_cookie_wrap.js]
+[test_cacheflags_wrap.js]
+[test_cache_jar_wrap.js]
+[test_channel_close_wrap.js]
+[test_cookie_header_wrap.js]
+[test_cookiejars_wrap.js]
+[test_dns_cancel_wrap.js]
+[test_dns_per_interface_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_synthesized_response_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_original_sent_received_head_wrap.js]
+[test_channel_id.js]
diff --git a/netwerk/test/urlparse.dat b/netwerk/test/urlparse.dat
new file mode 100644
index 0000000000..3b607d4e60
--- /dev/null
+++ b/netwerk/test/urlparse.dat
@@ -0,0 +1,103 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output for the standard URL
+# parser and one line for the case when the URL is really
+# created. Then run urltest with the -std option and without it
+# on this file and hopefully the expected output should match
+# the one from the program.
+# - Gagan Saksena 03/28/00
+#
+
+http://username:password@hostname.com:80/pathname/./more/stuff/../path
+http,username,password,hostname.com,80,/pathname/more/,path,,,,,http://username:password@hostname.com:80/pathname/more/path
+http,username,password,hostname.com,80,/pathname/more/,path,,,,,http://username:password@hostname.com/pathname/more/path
+
+username@host:8080/path
+,username,,host,8080,/,path,,,,,username@host:8080/path
+Can not create URL
+
+http://gagan/
+http,,,gagan,-1,/,,,,,,http://gagan/
+http,,,gagan,-1,/,,,,,,http://gagan/
+
+scheme:host/netlib
+scheme,,,host,-1,/,netlib,,,,,scheme://host/netlib
+Can not create URL
+
+mailbox:///foo
+mailbox,,,,-1,/,foo,,,,,mailbox:///foo
+mailbox,,,,-1,/,foo,,,,,mailbox:///foo
+
+scheme:user@hostname.edu:80/pathname
+scheme,user,,hostname.edu,80,/,pathname,,,,,scheme://user@hostname.edu:80/pathname
+Can not create URL
+
+http://username:password@hostname:80/pathname
+http,username,password,hostname,80,/,pathname,,,,,http://username:password@hostname:80/pathname
+http,username,password,hostname,80,/,pathname,,,,,http://username:password@hostname/pathname
+
+http://username:password@hostname:8080/path/filebasename.fileextension;param?query#ref
+http,username,password,hostname,8080,/path/,filebasename,fileextension,param,query,ref,http://username:password@hostname:8080/path/filebasename.fileextension;param?query#ref
+http,username,password,hostname,8080,/path/,filebasename,fileextension,param,query,ref,http://username:password@hostname:8080/path/filebasename.fileextension;param?query#ref
+
+resource:/pathname
+resource,,,,-1,/,pathname,,,,,resource:///pathname
+resource,,,,-1,/,pathname,,,,,resource:///pathname
+
+ftp://uname%here.com:pwd@there.com/aPath/a.html
+ftp,uname%here.com,pwd,there.com,-1,/aPath/,a,html,,,,ftp://uname%here.com:pwd@there.com/aPath/a.html
+ftp,uname%here.com,pwd,there.com,-1,/aPath/,a,html,,,,ftp://uname%here.com:pwd@there.com/aPath/a.html
+
+http://www.inf.bme.hu?foo=bar
+http,,,www.inf.bme.hu,-1,/,,,,foo=bar,,http://www.inf.bme.hu/?foo=bar
+http,,,www.inf.bme.hu,-1,/,,,,foo=bar,,http://www.inf.bme.hu/?foo=bar
+
+http://test.com/aPath/a.html#/1/2
+http,,,test.com,-1,/aPath/,a,html,,,/1/2,http://test.com/aPath/a.html#/1/2
+http,,,test.com,-1,/aPath/,a,html,,,/1/2,http://test.com/aPath/a.html#/1/2
+
+http://user:pass@ipaddres:2/get?foo/something
+http,user,pass,ipaddres,2,/,get,,,foo/something,,http://user:pass@ipaddres:2/get?foo/something
+http,user,pass,ipaddres,2,/,get,,,foo/something,,http://user:pass@ipaddres:2/get?foo/something
+
+# testing different versions of http urls
+http:www.mozilla.org
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+
+http:/www.mozilla.org
+http,,,,-1,/,www.mozilla,org,,,,http:///www.mozilla.org
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+
+# testing cap letters (23927)
+HtTp://wWw.mozilLa.org
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+http,,,www.mozilla.org,-1,/,,,,,,http://www.mozilla.org/
+
+# testing spaces (15150)
+go.com.au?mozilla bug reports
+,,,go.com.au,-1,/,,,,mozilla%20bug%20reports,,go.com.au/?mozilla%20bug%20reports
+Can not create URL
+
+http://go.com.au?mozilla bug reports
+http,,,go.com.au,-1,/,,,,mozilla%20bug%20reports,,http://go.com.au/?mozilla%20bug%20reports
+http,,,go.com.au,-1,/,,,,mozilla%20bug%20reports,,http://go.com.au/?mozilla%20bug%20reports
+
+# testing for multiple params (14801)
+http://ad.doubleclick.net/ad/cbsmw.button.com/SIDEBAR_BUTTONS;sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414
+http,,,ad.doubleclick.net,-1,/ad/cbsmw.button.com/,SIDEBAR_BUTTONS,,sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414,,,http://ad.doubleclick.net/ad/cbsmw.button.com/SIDEBAR_BUTTONS;sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414
+http,,,ad.doubleclick.net,-1,/ad/cbsmw.button.com/,SIDEBAR_BUTTONS,,sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414,,,http://ad.doubleclick.net/ad/cbsmw.button.com/SIDEBAR_BUTTONS;sz=88x31;kw=DBCC;tile=4;ord=1864641213378545414
+
+fxqn:/us/va/reston/cnri/ietf/24/asdf%*.fred
+fxqn,,,,-1,/us/va/reston/cnri/ietf/24/,asdf%*,fred,,,,fxqn:///us/va/reston/cnri/ietf/24/asdf%*.fred
+Can not create URL
+
+news:3B5C133C.2080505@foobar.net
+news,3B5C133C.2080505,,foobar.net,-1,/,,,,,,news://3B5C133C.2080505@foobar.net/
+news,3B5C133C.2080505,,foobar.net,-1,/,,,,,,news://3B5C133C.2080505@foobar.net/
+
+http://host/path/%2E%2E/file%2Ehtml
+http,,,host,-1,/,file%2Ehtml,,,,,http://host/file%2Ehtml
+http,,,host,-1,/,file%2Ehtml,,,,,http://host/file%2Ehtml
+
diff --git a/netwerk/test/urlparse_mac.dat b/netwerk/test/urlparse_mac.dat
new file mode 100644
index 0000000000..f9209d0a51
--- /dev/null
+++ b/netwerk/test/urlparse_mac.dat
@@ -0,0 +1,14 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output for the standard URL
+# parser and one line for the case when the URL is really
+# created. Then run urltest with the -std option and without it
+# on this file and hopefully the expected output should match
+# the one from the program.
+# - Gagan Saksena 03/28/00
+#
+# This version is specifically for the Mac platform.
+#
+
+# testing different versions of file urls
diff --git a/netwerk/test/urlparse_unx.dat b/netwerk/test/urlparse_unx.dat
new file mode 100644
index 0000000000..909d1a9a03
--- /dev/null
+++ b/netwerk/test/urlparse_unx.dat
@@ -0,0 +1,34 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output for the standard URL
+# parser and one line for the case when the URL is really
+# created. Then run urltest with the -std option and without it
+# on this file and hopefully the expected output should match
+# the one from the program.
+# - Gagan Saksena 03/28/00
+#
+# This version is specifically *not* for PC platforms like Windows or OS/2.
+# It's testcases for the file protocol target a unix-like filesystem.
+
+# testing different versions of file urls
+file:home
+file,,,home,-1,/,,,,,,file://home/
+file,,,,-1,/,home,,,,,file:///home
+
+file:/home
+file,,,,-1,/,home,,,,,file:///home
+file,,,,-1,/,home,,,,,file:///home
+
+file://home
+file,,,home,-1,/,,,,,,file://home/
+file,,,home,-1,/,,,,,,file://home/
+
+file:///home
+file,,,,-1,/,home,,,,,file:///home
+file,,,,-1,/,home,,,,,file:///home
+
+# testing UNC filepaths
+file:////server/path
+file,,,,-1,//server/,path,,,,,file:////server/path
+file,,,,-1,//server/,path,,,,,file:////server/path
diff --git a/netwerk/test/urlparse_win.dat b/netwerk/test/urlparse_win.dat
new file mode 100644
index 0000000000..cf418fefb0
--- /dev/null
+++ b/netwerk/test/urlparse_win.dat
@@ -0,0 +1,60 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output for the standard URL
+# parser and one line for the case when the URL is really
+# created. Then run urltest with the -std option and without it
+# on this file and hopefully the expected output should match
+# the one from the program.
+# - Gagan Saksena 03/28/00
+#
+# This version is specifically for PC platforms like Windows or OS/2.
+# It has testcases for the file protocol targeting typical
+# drive:/path filesystems
+#
+
+# testing different versions of file urls
+file:c:
+file,,,,-1,/,c:,,,,,file:///c%3A
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+file:c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+file:/c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+file://c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+file,,,,-1,/c:/,,,,,,file:///c:/
+
+# testing UNC filepaths
+file:server/path
+file,,,server,-1,/,path,,,,,file://server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+
+file:/server/path
+file,,,,-1,/server/,path,,,,,file:///server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+
+file://server/path
+file,,,server,-1,/,path,,,,,file://server/path
+file,,,server,-1,///,path,,,,,file://server///path
+
+file:///server/path
+file,,,,-1,/server/,path,,,,,file:///server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+
+file:////server/path
+file,,,,-1,//server/,path,,,,,file:////server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+
+file://///server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
+file,,,,-1,///server/,path,,,,,file://///server/path
diff --git a/netwerk/test/urltest.cpp b/netwerk/test/urltest.cpp
new file mode 100644
index 0000000000..df6f3f301f
--- /dev/null
+++ b/netwerk/test/urltest.cpp
@@ -0,0 +1,461 @@
+/* -*- 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/. */
+
+/*
+ A test file to check default URL parsing.
+ -Gagan Saksena 03/25/99
+*/
+
+#include <stdio.h>
+
+#include "TestCommon.h"
+#include "plstr.h"
+#include "nsIServiceManager.h"
+#include "nsIIOService.h"
+#include "nsIURL.h"
+#include "nsCOMPtr.h"
+#include "nsStringAPI.h"
+#include "nsNetCID.h"
+#include "nsIComponentRegistrar.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXPCOM.h"
+#include "prprf.h"
+#include "mozilla/Sprintf.h"
+
+// Define CIDs...
+static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
+static NS_DEFINE_CID(kStdURLCID, NS_STANDARDURL_CID);
+
+char* gFileIO = 0;
+
+enum {
+ URL_FACTORY_DEFAULT,
+ URL_FACTORY_STDURL
+};
+
+nsresult writeoutto(const char* i_pURL, char** o_Result, int32_t urlFactory = URL_FACTORY_DEFAULT)
+{
+ if (!o_Result || !i_pURL)
+ return NS_ERROR_FAILURE;
+ *o_Result = 0;
+ nsCOMPtr<nsIURI> pURL;
+ nsresult result = NS_OK;
+
+ switch (urlFactory) {
+ case URL_FACTORY_STDURL: {
+ nsIURI* url;
+ result = CallCreateInstance(kStdURLCID, &url);
+ if (NS_FAILED(result))
+ {
+ printf("CreateInstance failed\n");
+ return NS_ERROR_FAILURE;
+ }
+ pURL = url;
+ result = pURL->SetSpec(nsDependentCString(i_pURL));
+ if (NS_FAILED(result))
+ {
+ printf("SetSpec failed\n");
+ return NS_ERROR_FAILURE;
+ }
+ break;
+ }
+ case URL_FACTORY_DEFAULT: {
+ nsCOMPtr<nsIIOService> pService =
+ do_GetService(kIOServiceCID, &result);
+ if (NS_FAILED(result))
+ {
+ printf("Service failed!\n");
+ return NS_ERROR_FAILURE;
+ }
+ result = pService->NewURI(nsDependentCString(i_pURL), nullptr, nullptr, getter_AddRefs(pURL));
+ }
+ }
+
+ nsCString output;
+ if (NS_SUCCEEDED(result))
+ {
+ nsCOMPtr<nsIURL> tURL = do_QueryInterface(pURL);
+ nsAutoCString temp;
+ int32_t port;
+ nsresult rv;
+
+#define RESULT() NS_SUCCEEDED(rv) ? temp.get() : ""
+
+ rv = tURL->GetScheme(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetUsername(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetPassword(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetHost(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetPort(&port);
+ char portbuffer[40];
+ SprintfLiteral(portbuffer, "%d", port);
+ output.Append(portbuffer);
+ output += ',';
+ rv = tURL->GetDirectory(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetFileBaseName(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetFileExtension(temp);
+ output += RESULT();
+ output += ',';
+ // removed with https://bugzilla.mozilla.org/show_bug.cgi?id=665706
+ // rv = tURL->GetParam(temp);
+ // output += RESULT();
+ output += ',';
+ rv = tURL->GetQuery(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetRef(temp);
+ output += RESULT();
+ output += ',';
+ rv = tURL->GetSpec(temp);
+ output += RESULT();
+ *o_Result = ToNewCString(output);
+ } else {
+ output = "Can not create URL";
+ *o_Result = ToNewCString(output);
+ }
+ return NS_OK;
+}
+
+nsresult writeout(const char* i_pURL, int32_t urlFactory = URL_FACTORY_DEFAULT)
+{
+ if (!i_pURL) return NS_ERROR_FAILURE;
+ nsCString temp;
+ nsresult rv = writeoutto(i_pURL, getter_Copies(temp), urlFactory);
+ printf("%s\n%s\n", i_pURL, temp.get());
+ return rv;
+}
+
+/* construct a url and print out its elements separated by commas and
+ the whole spec */
+nsresult testURL(const char* i_pURL, int32_t urlFactory = URL_FACTORY_DEFAULT)
+{
+
+ if (i_pURL)
+ return writeout(i_pURL, urlFactory);
+
+ if (!gFileIO)
+ return NS_ERROR_FAILURE;
+
+ FILE *testfile = fopen(gFileIO, "rt");
+ if (!testfile)
+ {
+ fprintf(stderr, "Cannot open testfile: %s\n", gFileIO);
+ return NS_ERROR_FAILURE;
+ }
+
+ char temp[512];
+ int count=0;
+ int failed=0;
+ nsCString prevResult;
+ nsCString tempurl;
+
+ while (fgets(temp,512,testfile))
+ {
+ if (*temp == '#' || !*temp)
+ continue;
+
+ if (0 == count%3)
+ {
+ printf("Testing: %s\n", temp);
+ writeoutto(temp, getter_Copies(prevResult), urlFactory);
+ }
+ else if (1 == count%3) {
+ tempurl.Assign(temp);
+ } else {
+ if (prevResult.IsEmpty())
+ printf("no results to compare to!\n");
+ else
+ {
+ int32_t res;
+ printf("Result: %s\n", prevResult.get());
+ if (urlFactory != URL_FACTORY_DEFAULT) {
+ printf("Expected: %s\n", tempurl.get());
+ res = PL_strcmp(tempurl.get(), prevResult.get());
+ } else {
+ printf("Expected: %s\n", temp);
+ res = PL_strcmp(temp, prevResult.get());
+ }
+
+ if (res == 0)
+ printf("\tPASSED\n\n");
+ else
+ {
+ printf("\tFAILED\n\n");
+ failed++;
+ }
+ }
+ }
+ count++;
+ }
+ if (failed>0) {
+ printf("%d tests FAILED out of %d\n", failed, count/3);
+ return NS_ERROR_FAILURE;
+ } else {
+ printf("All %d tests PASSED.\n", count/3);
+ return NS_OK;
+ }
+}
+
+nsresult makeAbsTest(const char* i_BaseURI, const char* relativePortion,
+ const char* expectedResult)
+{
+ if (!i_BaseURI)
+ return NS_ERROR_FAILURE;
+
+ // build up the base URL
+ nsresult status;
+ nsCOMPtr<nsIURI> baseURL = do_CreateInstance(kStdURLCID, &status);
+ if (NS_FAILED(status))
+ {
+ printf("CreateInstance failed\n");
+ return status;
+ }
+ status = baseURL->SetSpec(nsDependentCString(i_BaseURI));
+ if (NS_FAILED(status)) return status;
+
+
+ // get the new spec
+ nsAutoCString newURL;
+ status = baseURL->Resolve(nsDependentCString(relativePortion), newURL);
+ if (NS_FAILED(status)) return status;
+
+ printf("Analyzing %s\n", baseURL->GetSpecOrDefault().get());
+ printf("With %s\n", relativePortion);
+
+ printf("Got %s\n", newURL.get());
+ if (expectedResult) {
+ printf("Expect %s\n", expectedResult);
+ int res = PL_strcmp(newURL.get(), expectedResult);
+ if (res == 0) {
+ printf("\tPASSED\n\n");
+ return NS_OK;
+ } else {
+ printf("\tFAILED\n\n");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult doMakeAbsTest(const char* i_URL = 0, const char* i_relativePortion=0)
+{
+ if (i_URL && i_relativePortion)
+ {
+ return makeAbsTest(i_URL, i_relativePortion, nullptr);
+ }
+
+ // Run standard tests. These tests are based on the ones described in
+ // rfc2396 with the exception of the handling of ?y which is wrong as
+ // notified by on of the RFC authors.
+
+ /* Section C.1. Normal Examples
+
+ g:h = <URL:g:h>
+ g = <URL:http://a/b/c/g>
+ ./g = <URL:http://a/b/c/g>
+ g/ = <URL:http://a/b/c/g/>
+ /g = <URL:http://a/g>
+ //g = <URL:http://g>
+ ?y = <URL:http://a/b/c/d;p?y>
+ g?y = <URL:http://a/b/c/g?y>
+ g?y/./x = <URL:http://a/b/c/g?y/./x>
+ #s = <URL:http://a/b/c/d;p?q#s>
+ g#s = <URL:http://a/b/c/g#s>
+ g#s/./x = <URL:http://a/b/c/g#s/./x>
+ g?y#s = <URL:http://a/b/c/g?y#s>
+ ;x = <URL:http://a/b/c/;x>
+ g;x = <URL:http://a/b/c/g;x>
+ g;x?y#s = <URL:http://a/b/c/g;x?y#s>
+ . = <URL:http://a/b/c/>
+ ./ = <URL:http://a/b/c/>
+ .. = <URL:http://a/b/>
+ ../ = <URL:http://a/b/>
+ ../g = <URL:http://a/b/g>
+ ../.. = <URL:http://a/>
+ ../../ = <URL:http://a/>
+ ../../g = <URL:http://a/g>
+ */
+
+ struct test {
+ const char* baseURL;
+ const char* relativeURL;
+ const char* expectedResult;
+ };
+
+ test tests[] = {
+ // Tests from rfc2396, section C.1 with the exception of the
+ // handling of ?y
+ { "http://a/b/c/d;p?q#f", "g:h", "g:h" },
+ { "http://a/b/c/d;p?q#f", "g", "http://a/b/c/g" },
+ { "http://a/b/c/d;p?q#f", "./g", "http://a/b/c/g" },
+ { "http://a/b/c/d;p?q#f", "g/", "http://a/b/c/g/" },
+ { "http://a/b/c/d;p?q#f", "/g", "http://a/g" },
+ { "http://a/b/c/d;p?q#f", "//g", "http://g" },
+ { "http://a/b/c/d;p?q#f", "?y", "http://a/b/c/d;p?y" },
+ { "http://a/b/c/d;p?q#f", "g?y", "http://a/b/c/g?y" },
+ { "http://a/b/c/d;p?q#f", "g?y/./x", "http://a/b/c/g?y/./x" },
+ { "http://a/b/c/d;p?q#f", "#s", "http://a/b/c/d;p?q#s" },
+ { "http://a/b/c/d;p?q#f", "g#s", "http://a/b/c/g#s" },
+ { "http://a/b/c/d;p?q#f", "g#s/./x", "http://a/b/c/g#s/./x" },
+ { "http://a/b/c/d;p?q#f", "g?y#s", "http://a/b/c/g?y#s" },
+ { "http://a/b/c/d;p?q#f", ";x", "http://a/b/c/;x" },
+ { "http://a/b/c/d;p?q#f", "g;x", "http://a/b/c/g;x" },
+ { "http://a/b/c/d;p?q#f", "g;x?y#s", "http://a/b/c/g;x?y#s" },
+ { "http://a/b/c/d;p?q#f", ".", "http://a/b/c/" },
+ { "http://a/b/c/d;p?q#f", "./", "http://a/b/c/" },
+ { "http://a/b/c/d;p?q#f", "..", "http://a/b/" },
+ { "http://a/b/c/d;p?q#f", "../", "http://a/b/" },
+ { "http://a/b/c/d;p?q#f", "../g", "http://a/b/g" },
+ { "http://a/b/c/d;p?q#f", "../..", "http://a/" },
+ { "http://a/b/c/d;p?q#f", "../../", "http://a/" },
+ { "http://a/b/c/d;p?q#f", "../../g", "http://a/g" },
+
+ // Our additional tests...
+ { "http://a/b/c/d;p?q#f", "#my::anchor", "http://a/b/c/d;p?q#my::anchor" },
+ { "http://a/b/c/d;p?q#f", "get?baseRef=viewcert.jpg", "http://a/b/c/get?baseRef=viewcert.jpg" },
+
+ // Make sure relative query's work right even if the query
+ // string contains absolute urls or other junk.
+ { "http://a/b/c/d;p?q#f", "?http://foo", "http://a/b/c/d;p?http://foo" },
+ { "http://a/b/c/d;p?q#f", "g?http://foo", "http://a/b/c/g?http://foo" },
+ {"http://a/b/c/d;p?q#f", "g/h?http://foo", "http://a/b/c/g/h?http://foo" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H?http://foo","http://a/b/c/g/H?http://foo" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H?http://foo?baz", "http://a/b/c/g/H?http://foo?baz" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H?http://foo;baz", "http://a/b/c/g/H?http://foo;baz" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H?http://foo#bar", "http://a/b/c/g/H?http://foo#bar" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H;baz?http://foo", "http://a/b/c/g/H;baz?http://foo" },
+ { "http://a/b/c/d;p?q#f", "g/h/../H;baz?http://foo#bar", "http://a/b/c/g/H;baz?http://foo#bar" },
+ { "http://a/b/c/d;p?q#f", R"(g/h/../H;baz?C:\temp)", R"(http://a/b/c/g/H;baz?C:\temp)" },
+ { "http://a/b/c/d;p?q#f", "", "http://a/b/c/d;p?q" },
+ { "http://a/b/c/d;p?q#f", "#", "http://a/b/c/d;p?q#" },
+ { "http://a/b/c;p/d;p?q#f", "../g;p" , "http://a/b/g;p" },
+
+ };
+
+ const int numTests = sizeof(tests) / sizeof(tests[0]);
+ int failed = 0;
+ nsresult rv;
+ for (auto & test : tests)
+ {
+ rv = makeAbsTest(test.baseURL, test.relativeURL,
+ test.expectedResult);
+ if (NS_FAILED(rv))
+ failed++;
+ }
+ if (failed>0) {
+ printf("%d tests FAILED out of %d\n", failed, numTests);
+ return NS_ERROR_FAILURE;
+ } else {
+ printf("All %d tests PASSED.\n", numTests);
+ return NS_OK;
+ }
+}
+
+void printusage(void)
+{
+ printf("urltest [-std] [-file <filename>] <URL> "
+ " [-abs <relative>]\n\n"
+ "\t-std : Generate results using nsStdURL.\n"
+ "\t-file : Read URLs from file.\n"
+ "\t-abs : Make an absolute URL from the base (<URL>) and the\n"
+ "\t\trelative path specified. If -abs is given without\n"
+ "\t\ta base URI standard RFC 2396 relative URL tests\n"
+ "\t\tare performed. Implies -std.\n"
+ "\t<URL> : The string representing the URL.\n");
+}
+
+int main(int argc, char **argv)
+{
+ if (test_common_init(&argc, &argv) != 0)
+ return -1;
+
+ if (argc < 2) {
+ printusage();
+ return 0;
+ }
+ {
+ nsCOMPtr<nsIServiceManager> servMan;
+ NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr);
+
+ // end of all messages from register components...
+ printf("------------------\n\n");
+
+ int32_t urlFactory = URL_FACTORY_DEFAULT;
+ bool bMakeAbs= false;
+ char* relativePath = 0;
+ char* url = 0;
+ for (int i=1; i<argc; i++) {
+ if (PL_strcasecmp(argv[i], "-std") == 0)
+ {
+ urlFactory = URL_FACTORY_STDURL;
+ if (i+1 >= argc)
+ {
+ printusage();
+ return 0;
+ }
+ }
+ else if (PL_strcasecmp(argv[i], "-abs") == 0)
+ {
+ if (!gFileIO)
+ {
+ relativePath = argv[i+1];
+ i++;
+ }
+ bMakeAbs = true;
+ }
+ else if (PL_strcasecmp(argv[i], "-file") == 0)
+ {
+ if (i+1 >= argc)
+ {
+ printusage();
+ return 0;
+ }
+ gFileIO = argv[i+1];
+ i++;
+ }
+ else
+ {
+ url = argv[i];
+ }
+ }
+ PRTime startTime = PR_Now();
+ if (bMakeAbs)
+ {
+ if (url && relativePath) {
+ doMakeAbsTest(url, relativePath);
+ } else {
+ doMakeAbsTest();
+ }
+ }
+ else
+ {
+ if (gFileIO) {
+ testURL(0, urlFactory);
+ } else {
+ testURL(url, urlFactory);
+ }
+ }
+ if (gFileIO)
+ {
+ PRTime endTime = PR_Now();
+ printf("Elapsed time: %d micros.\n", (int32_t)
+ (endTime - startTime));
+ }
+ } // this scopes the nsCOMPtrs
+ // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
+ return NS_FAILED(NS_ShutdownXPCOM(nullptr)) ? 1 : 0;
+}
diff --git a/netwerk/test/urltests.dat b/netwerk/test/urltests.dat
new file mode 100644
index 0000000000..d1f42fc76a
--- /dev/null
+++ b/netwerk/test/urltests.dat
@@ -0,0 +1,43 @@
+# Any blank lines and those beginning with # are comments and
+# ignored. To add additional test cases that could potentially
+# break URL parsing in mozilla add the input URL on a new line
+# and follow it with the expected output. Then run urltest on
+# this file and hopefully the expected output should match the
+# one from the program.
+# - Gagan Saksena 03/28/00
+
+http://username:password@hostname.com:80/pathname/./more/stuff/../path
+http,username:password,hostname.com,80,,/pathname/more/path
+
+username@host:8080/path
+,username,host,8080,,/path
+
+http://gagan/
+http,,gagan,-1,,/
+
+scheme:host/netlib
+scheme,,host,-1,,/netlib
+
+mailbox:///foo
+mailbox,,,-1,,/foo
+
+scheme:user@hostname.edu:80/pathname
+scheme,user,hostname.edu,80,,/pathname
+
+http://username:password@hostname:80/pathname
+http,username:password,hostname,80,,/pathname
+
+resource:/pathname
+resource,,,-1,,/pathname
+
+ftp://uname%here.com:pwd@there.com/aPath/a.html
+ftp,uname%here.com:pwd,there.com,-1,,/aPath/a.html
+
+http://www.inf.bme.hu?foo=bar
+http,,www.inf.bme.hu,-1,foo=bar,/?foo=bar
+
+http://test.com/aPath/a.html#/1/2
+http,,test.com,-1,,/aPath/a.html#/1/2
+
+http://user:pass@ipaddres:2/get?foo/something
+http,user:pass,ipaddres,2,foo/something,/get?foo/something
diff --git a/netwerk/wifi/moz.build b/netwerk/wifi/moz.build
new file mode 100644
index 0000000000..b6ddd2139e
--- /dev/null
+++ b/netwerk/wifi/moz.build
@@ -0,0 +1,65 @@
+# -*- 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',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ UNIFIED_SOURCES += [
+ 'nsWifiMonitorGonk.cpp',
+ ]
+else:
+ UNIFIED_SOURCES += [
+ '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',
+ 'win_xp_wifiScanner.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..ffac02654e
--- /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([array, size_is(aLen)] in nsIWifiAccessPoint accessPoints, in unsigned long aLen);
+
+ /*
+ * 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..995afaba59
--- /dev/null
+++ b/netwerk/wifi/nsWifiAccessPoint.cpp
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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;
+}
+
+nsWifiAccessPoint::~nsWifiAccessPoint()
+{
+}
+
+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.
+ CopyASCIItoUTF16(mSsid, aSsid);
+ 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..4bfb7c175c
--- /dev/null
+++ b/netwerk/wifi/nsWifiAccessPoint.h
@@ -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/. */
+
+#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();
+
+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..4613a107dc
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitor.cpp
@@ -0,0 +1,265 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/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"));
+}
+
+nsWifiMonitor::~nsWifiMonitor()
+{
+}
+
+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_NewThread(getter_AddRefs(mThread), this);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+
+ mListeners.AppendElement(nsWifiListener(new nsMainThreadPtrHolder<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(nsAutoPtr<WifiListenerArray> aListeners,
+ nsresult aResult)
+ : mListeners(aListeners),
+ mResult(aResult)
+ {}
+
+ private:
+ ~nsPassErrorToWifiListeners() {}
+ nsAutoPtr<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"));
+
+ PR_SetCurrentThreadName("Wifi Monitor");
+
+ nsresult rv = DoScan();
+ LOG(("@@@@@ wifi monitor run::doscan complete %x\n", rv));
+
+ nsAutoPtr<WifiListenerArray> currentListeners;
+ bool doError = false;
+
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ if (mKeepGoing && NS_FAILED(rv)) {
+ doError = true;
+ currentListeners = new WifiListenerArray(mListeners.Length());
+ for (uint32_t i = 0; i < mListeners.Length(); i++)
+ currentListeners->AppendElement(mListeners[i].mListener);
+ }
+ mThreadComplete = true;
+ }
+
+ if (doError) {
+ nsCOMPtr<nsIThread> thread = do_GetMainThread();
+ if (!thread)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRunnable> runnable(new nsPassErrorToWifiListeners(currentListeners, rv));
+ if (!runnable)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ thread->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(nsAutoPtr<WifiListenerArray> aListeners,
+ nsAutoPtr<nsTArray<nsIWifiAccessPoint*> > aAccessPoints)
+ : mListeners(aListeners),
+ mAccessPoints(aAccessPoints)
+ {}
+
+ private:
+ ~nsCallWifiListeners() {}
+ nsAutoPtr<WifiListenerArray> mListeners;
+ nsAutoPtr<nsTArray<nsIWifiAccessPoint*> > mAccessPoints;
+};
+
+NS_IMPL_ISUPPORTS(nsCallWifiListeners,
+ nsIRunnable)
+
+NS_IMETHODIMP nsCallWifiListeners::Run()
+{
+ LOG(("About to send data to the wifi listeners\n"));
+ for (size_t i = 0; i < mListeners->Length(); i++) {
+ (*mListeners)[i]->OnChange(mAccessPoints->Elements(), mAccessPoints->Length());
+ }
+ return NS_OK;
+}
+
+nsresult
+nsWifiMonitor::CallWifiListeners(const nsCOMArray<nsWifiAccessPoint> &aAccessPoints,
+ bool aAccessPointsChanged)
+{
+ nsAutoPtr<WifiListenerArray> currentListeners;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ currentListeners = new WifiListenerArray(mListeners.Length());
+
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ if (!mListeners[i].mHasSentData || aAccessPointsChanged) {
+ mListeners[i].mHasSentData = true;
+ currentListeners->AppendElement(mListeners[i].mListener);
+ }
+ }
+ }
+
+ if (currentListeners->Length() > 0)
+ {
+ uint32_t resultCount = aAccessPoints.Count();
+ nsAutoPtr<nsTArray<nsIWifiAccessPoint*> > accessPoints(
+ new nsTArray<nsIWifiAccessPoint *>(resultCount));
+ if (!accessPoints)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ 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(currentListeners, 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..3783d38bd3
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitor.h
@@ -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/. */
+
+#ifndef __nsWifiMonitor__
+#define __nsWifiMonitor__
+
+#include "nsIWifiMonitor.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.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 "nsITimer.h"
+#include "mozilla/Attributes.h"
+#include "nsIInterfaceRequestor.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() {}
+
+ nsMainThreadPtrHandle<nsIWifiListener> mListener;
+ bool mHasSentData;
+};
+
+#ifndef MOZ_WIDGET_GONK
+class nsWifiMonitor final : nsIRunnable, nsIWifiMonitor, nsIObserver
+{
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWIFIMONITOR
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+
+ nsWifiMonitor();
+
+ private:
+ ~nsWifiMonitor();
+
+ 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
+ nsAutoPtr<WindowsWifiScannerInterface> mWinWifiScanner;
+#endif
+};
+#else
+#include "nsIWifi.h"
+class nsWifiMonitor final : nsIWifiMonitor, nsIWifiScanResultsReady, nsIObserver
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWIFIMONITOR
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWIFISCANRESULTSREADY
+
+ nsWifiMonitor();
+
+ private:
+ ~nsWifiMonitor();
+
+ void ClearTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ }
+ void StartScan();
+ nsCOMArray<nsWifiAccessPoint> mLastAccessPoints;
+ nsTArray<nsWifiListener> mListeners;
+ nsCOMPtr<nsITimer> mTimer;
+};
+#endif
+
+#endif
diff --git a/netwerk/wifi/nsWifiMonitorGonk.cpp b/netwerk/wifi/nsWifiMonitorGonk.cpp
new file mode 100644
index 0000000000..017750549e
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitorGonk.cpp
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.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/Services.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+
+using namespace mozilla;
+
+LazyLogModule gWifiMonitorLog("WifiMonitor");
+
+NS_IMPL_ISUPPORTS(nsWifiMonitor,
+ nsIWifiMonitor,
+ nsIObserver,
+ nsIWifiScanResultsReady)
+
+nsWifiMonitor::nsWifiMonitor()
+{
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "xpcom-shutdown", false);
+ }
+ LOG(("@@@@@ wifimonitor created\n"));
+}
+
+nsWifiMonitor::~nsWifiMonitor()
+{
+}
+
+NS_IMETHODIMP
+nsWifiMonitor::StartWatching(nsIWifiListener *aListener)
+{
+ LOG(("@@@@@ nsWifiMonitor::StartWatching\n"));
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ if (!aListener) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mListeners.AppendElement(nsWifiListener(new nsMainThreadPtrHolder<nsIWifiListener>(aListener)));
+
+ if (!mTimer) {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mTimer->Init(this, 5000, nsITimer::TYPE_REPEATING_SLACK);
+ }
+ StartScan();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWifiMonitor::StopWatching(nsIWifiListener *aListener)
+{
+ LOG(("@@@@@ nsWifiMonitor::StopWatching\n"));
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ if (!aListener) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ if (mListeners[i].mListener == aListener) {
+ mListeners.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (mListeners.Length() == 0) {
+ ClearTimer();
+ }
+ return NS_OK;
+}
+
+void
+nsWifiMonitor::StartScan()
+{
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_GetService("@mozilla.org/telephony/system-worker-manager;1");
+ nsCOMPtr<nsIWifi> wifi = do_GetInterface(ir);
+ if (!wifi) {
+ return;
+ }
+ wifi->GetWifiScanResults(this);
+}
+
+NS_IMETHODIMP
+nsWifiMonitor::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, "timer-callback")) {
+ LOG(("timer callback\n"));
+ StartScan();
+ return NS_OK;
+ }
+
+ if (!strcmp(topic, "xpcom-shutdown")) {
+ LOG(("Shutting down\n"));
+ ClearTimer();
+ return NS_OK;
+ }
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsWifiMonitor::Onready(uint32_t count, nsIWifiScanResult **results)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ LOG(("@@@@@ About to send data to the wifi listeners\n"));
+
+ nsCOMArray<nsWifiAccessPoint> accessPoints;
+
+ for (uint32_t i = 0; i < count; i++) {
+ RefPtr<nsWifiAccessPoint> ap = new nsWifiAccessPoint();
+
+ nsString temp;
+ results[i]->GetBssid(temp);
+ // 00:00:00:00:00:00 --> 00-00-00-00-00-00
+ for (int32_t x=0; x<6; x++) {
+ temp.ReplaceSubstring(NS_LITERAL_STRING(":"), NS_LITERAL_STRING("-")); // would it be too much to ask for a ReplaceAll()?
+ }
+
+ nsCString mac;
+ mac.AssignWithConversion(temp);
+
+ results[i]->GetSsid(temp);
+
+ nsCString ssid;
+ ssid.AssignWithConversion(temp);
+
+ uint32_t signal;
+ results[i]->GetSignalStrength(&signal);
+
+ ap->setSignal(signal);
+ ap->setMacRaw(mac.get());
+ ap->setSSIDRaw(ssid.get(), ssid.Length());
+
+ accessPoints.AppendObject(ap);
+ }
+
+ bool accessPointsChanged = !AccessPointsEqual(accessPoints, mLastAccessPoints);
+ ReplaceArray(mLastAccessPoints, accessPoints);
+
+ nsTArray<nsIWifiAccessPoint*> ac;
+ uint32_t resultCount = mLastAccessPoints.Count();
+ for (uint32_t i = 0; i < resultCount; i++) {
+ ac.AppendElement(mLastAccessPoints[i]);
+ }
+
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ if (!mListeners[i].mHasSentData || accessPointsChanged) {
+ mListeners[i].mHasSentData = true;
+ mListeners[i].mListener->OnChange(ac.Elements(), ac.Length());
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWifiMonitor::Onfailure()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ LOG(("@@@@@ About to send error to the wifi listeners\n"));
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ mListeners[i].mListener->OnError(NS_ERROR_UNEXPECTED);
+ }
+
+ ClearTimer();
+ return NS_OK;
+}
diff --git a/netwerk/wifi/nsWifiScannerDBus.cpp b/netwerk/wifi/nsWifiScannerDBus.cpp
new file mode 100644
index 0000000000..182553e18f
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerDBus.cpp
@@ -0,0 +1,337 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/ipc/DBusMessageRefPtr.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));
+ MOZ_ASSERT(mConnection);
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+
+ MOZ_COUNT_CTOR(nsWifiScannerDBus);
+}
+
+nsWifiScannerDBus::~nsWifiScannerDBus()
+{
+ MOZ_COUNT_DTOR(nsWifiScannerDBus);
+}
+
+nsresult
+nsWifiScannerDBus::Scan()
+{
+ return SendMessage("org.freedesktop.NetworkManager",
+ "/org/freedesktop/NetworkManager",
+ "GetDevices");
+}
+
+nsresult
+nsWifiScannerDBus::SendMessage(const char* aInterface,
+ const char* aPath,
+ const char* aFuncCall)
+{
+ RefPtr<DBusMessage> msg = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call("org.freedesktop.NetworkManager",
+ aPath, aInterface, aFuncCall));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter argsIter;
+ dbus_message_iter_init_append(msg, &argsIter);
+
+ if (!strcmp(aFuncCall, "Get")) {
+ 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;
+ }
+ } else if (!strcmp(aFuncCall, "GetAll")) {
+ const char* param = "";
+ if (!dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING, &param)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ // http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
+ // Refer to function dbus_connection_send_with_reply_and_block.
+ const uint32_t DBUS_DEFAULT_TIMEOUT = -1;
+ 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.
+ if (!strcmp(aFuncCall, "GetAccessPoints")) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ if (!strcmp(aFuncCall, "GetDevices")) {
+ rv = IdentifyDevices(reply);
+ } else if (!strcmp(aFuncCall, "Get")) {
+ rv = IdentifyDeviceType(reply, aPath);
+ } else if (!strcmp(aFuncCall, "GetAccessPoints")) {
+ rv = IdentifyAccessPoints(reply);
+ } else if (!strcmp(aFuncCall, "GetAll")) {
+ rv = IdentifyAPProperties(reply);
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+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 = SendMessage("org.freedesktop.DBus.Properties", devicePath, "Get");
+ 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 = SendMessage("org.freedesktop.NetworkManager.Device.Wireless",
+ aDevicePath, "GetAccessPoints");
+ }
+
+ 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 = SendMessage("org.freedesktop.DBus.Properties", path, "GetAll");
+ 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;
+ dbus_message_iter_get_basic(&variant, &strength);
+ ap->setSignal(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;
+}
+
+} // 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..516eb5ff2f
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerDBus.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSWIFIAPSCANNERDBUS_H_
+#define NSWIFIAPSCANNERDBUS_H_
+
+#include "nsCOMArray.h"
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+#include <dbus/dbus.h>
+
+#include "mozilla/ipc/DBusConnectionRefPtr.h"
+
+class nsWifiAccessPoint;
+
+namespace mozilla {
+
+class nsWifiScannerDBus final
+{
+public:
+ explicit nsWifiScannerDBus(nsCOMArray<nsWifiAccessPoint>* aAccessPoints);
+ ~nsWifiScannerDBus();
+
+ nsresult Scan();
+
+private:
+ nsresult SendMessage(const char* aInterface,
+ const char* aPath,
+ const char* aFuncCall);
+ 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;
+};
+
+} // mozilla
+
+#endif // NSWIFIAPSCANNERDBUS_H_
diff --git a/netwerk/wifi/nsWifiScannerFreeBSD.cpp b/netwerk/wifi/nsWifiScannerFreeBSD.cpp
new file mode 100644
index 0000000000..4185d6989e
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerFreeBSD.cpp
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// 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..b8ca682c21
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerMac.cpp
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <mach-o/dyld.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+#include "osx_wifi.h"
+
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.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..6fbfa0eb77
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerSolaris.cpp
@@ -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/. */
+
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+
+#include "plstr.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);
+ ap->setSSID(essid_str, PL_strnlen(essid_str, DLADM_STRSIZE));
+ }
+ 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..ef18706e47
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerWin.cpp
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWifiMonitor.h"
+
+// moz headers (alphabetical)
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWifiAccessPoint.h"
+#include "win_wifiScanner.h"
+#include "win_xp_wifiScanner.h"
+#include "mozilla/WindowsVersion.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) {
+ if (IsWin2003OrLater()) {
+ mWinWifiScanner = new WinWifiScanner();
+ LOG(("Using Windows 2003+ wifi scanner."));
+ } else {
+ mWinWifiScanner = new WinXPWifiScanner();
+ LOG(("Using Windows XP wifi scanner."));
+ }
+
+ 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..5cc96729a1
--- /dev/null
+++ b/netwerk/wifi/osx_corewlan.mm
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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 "nsAutoPtr.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..8a72a84e23
--- /dev/null
+++ b/netwerk/wifi/osx_wifi.h
@@ -0,0 +1,120 @@
+// 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/tests/wifi_access_point_test.html b/netwerk/wifi/tests/wifi_access_point_test.html
new file mode 100644
index 0000000000..974a6be157
--- /dev/null
+++ b/netwerk/wifi/tests/wifi_access_point_test.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+<title>hi</title>
+<script>
+
+var count = 0;
+
+
+function test() {
+}
+
+test.prototype =
+{
+ onChange: function (accessPoints)
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ var d = document.getElementById("d");
+ d.innerHTML = "";
+
+ for (var i=0; i<accessPoints.length; i++) {
+ var a = accessPoints[i];
+ d.innerHTML = d.innerHTML + "<p>" + a.mac + " " + a.ssid + " " + a.signal + "</p>";
+ }
+
+ var c = document.getElementById("c");
+ c.innerHTML = "<p>" + count++ + "</p>";
+
+ },
+
+ onError: function (value) {
+ alert("error: " +value);
+ },
+
+ QueryInterface: function(iid) {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ if (iid.equals(Components.interfaces.nsIWifiListener) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+}
+
+
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+ var listener = new test();
+ var wifi_service = Components.classes["@mozilla.org/wifi/monitor;1"].getService(Components.interfaces.nsIWifiMonitor);
+
+ wifi_service.startWatching(listener);
+
+
+
+</script>
+</head>
+
+<body>
+<div id="d"><p></p></div>
+<div id="c"><p></p></div>
+</body>
+</html>
diff --git a/netwerk/wifi/win_wifiScanner.cpp b/netwerk/wifi/win_wifiScanner.cpp
new file mode 100644
index 0000000000..a462b96969
--- /dev/null
+++ b/netwerk/wifi/win_wifiScanner.cpp
@@ -0,0 +1,195 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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:
+ InterfaceScanCallbackData(uint32_t numInterfaces)
+ : mCurrentlyScanningInterfaces(numInterfaces)
+ {
+ mAllInterfacesDoneScanningEvent =
+ ::CreateEvent(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
+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 = 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..b43c238999
--- /dev/null
+++ b/netwerk/wifi/win_wifiScanner.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+// Moz headers (alphabetical)
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "win_wlanLibrary.h"
+
+class nsWifiAccessPoint;
+
+// This class allows the wifi monitor to use WinWifiScanner and WinXPWifiScanner interchangeably.
+class WindowsWifiScannerInterface {
+public:
+ virtual ~WindowsWifiScannerInterface() {}
+
+ virtual nsresult GetAccessPointsFromWLAN(nsCOMArray<nsWifiAccessPoint> &accessPoints) = 0;
+};
+
+
+class WinWifiScanner : public WindowsWifiScannerInterface {
+ public:
+ WinWifiScanner();
+ virtual ~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:
+ nsAutoPtr<WinWLANLibrary> mWlanLibrary;
+};
diff --git a/netwerk/wifi/win_wlanLibrary.cpp b/netwerk/wifi/win_wlanLibrary.cpp
new file mode 100644
index 0000000000..cf1052788a
--- /dev/null
+++ b/netwerk/wifi/win_wlanLibrary.cpp
@@ -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 "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 = LoadLibrary("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..3daf4618ea
--- /dev/null
+++ b/netwerk/wifi/win_wlanLibrary.h
@@ -0,0 +1,61 @@
+/* -*- 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(WinWLANLibrary* library, void* object)
+ : mLibrary(library)
+ , mObject(object)
+ {
+ }
+
+ ~ScopedWLANObject()
+ {
+ (*(mLibrary->GetWlanFreeMemoryPtr()))(mObject);
+ }
+
+ private:
+ WinWLANLibrary *mLibrary;
+ void *mObject;
+};
diff --git a/netwerk/wifi/win_xp_wifiScanner.cpp b/netwerk/wifi/win_xp_wifiScanner.cpp
new file mode 100644
index 0000000000..4dcd340341
--- /dev/null
+++ b/netwerk/wifi/win_xp_wifiScanner.cpp
@@ -0,0 +1,399 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Windows Vista uses the Native Wifi (WLAN) API for accessing WiFi cards. See
+// http://msdn.microsoft.com/en-us/library/ms705945(VS.85).aspx. Windows XP
+// Service Pack 3 (and Windows XP Service Pack 2, if upgraded with a hot fix)
+// also support a limited version of the WLAN API. See
+// http://msdn.microsoft.com/en-us/library/bb204766.aspx. The WLAN API uses
+// wlanapi.h, which is not part of the SDK used by Gears, so is replicated
+// locally using data from the MSDN.
+//\
+// Windows XP from Service Pack 2 onwards supports the Wireless Zero
+// Configuration (WZC) programming interface. See
+// http://msdn.microsoft.com/en-us/library/ms706587(VS.85).aspx.
+//
+// The MSDN recommends that one use the WLAN API where available, and WZC
+// otherwise.
+//
+// However, it seems that WZC fails for some wireless cards. Also, WLAN seems
+// not to work on XP SP3. So we use WLAN on Vista, and use NDIS directly
+// otherwise.
+
+// MOZILLA NOTE:
+// This code is ported from chromium:
+// https://chromium.googlesource.com/chromium/src/+/master/content/browser/geolocation/wifi_data_provider_win.cc
+// Based on changeset 42c5878
+
+#include "win_xp_wifiScanner.h"
+#include "nsWifiAccessPoint.h"
+#include <windows.h>
+#include <winioctl.h>
+#include <wlanapi.h>
+#include <string>
+#include <vector>
+
+// Taken from ndis.h for WinCE.
+#define NDIS_STATUS_INVALID_LENGTH ((NDIS_STATUS)0xC0010014L)
+#define NDIS_STATUS_BUFFER_TOO_SHORT ((NDIS_STATUS)0xC0010016L)
+
+namespace {
+// The limits on the size of the buffer used for the OID query.
+const int kInitialBufferSize = 2 << 12; // Good for about 50 APs.
+const int kMaximumBufferSize = 2 << 20; // 2MB
+
+// Length for generic string buffers passed to Win32 APIs.
+const int kStringLength = 512;
+
+// WlanOpenHandle
+typedef DWORD (WINAPI* WlanOpenHandleFunction)(DWORD dwClientVersion,
+ PVOID pReserved,
+ PDWORD pdwNegotiatedVersion,
+ PHANDLE phClientHandle);
+
+// WlanEnumInterfaces
+typedef DWORD (WINAPI* WlanEnumInterfacesFunction)(
+ HANDLE hClientHandle,
+ PVOID pReserved,
+ PWLAN_INTERFACE_INFO_LIST* ppInterfaceList);
+
+// WlanGetNetworkBssList
+typedef DWORD (WINAPI* WlanGetNetworkBssListFunction)(
+ HANDLE hClientHandle,
+ const GUID* pInterfaceGuid,
+ const PDOT11_SSID pDot11Ssid,
+ DOT11_BSS_TYPE dot11BssType,
+ BOOL bSecurityEnabled,
+ PVOID pReserved,
+ PWLAN_BSS_LIST* ppWlanBssList
+);
+
+// WlanFreeMemory
+typedef VOID (WINAPI* WlanFreeMemoryFunction)(PVOID pMemory);
+
+// WlanCloseHandle
+typedef DWORD (WINAPI* WlanCloseHandleFunction)(HANDLE hClientHandle,
+ PVOID pReserved);
+
+// Extracts data for an access point and converts to Gears format.
+bool UndefineDosDevice(const std::string& device_name);
+bool DefineDosDeviceIfNotExists(const std::string& device_name);
+HANDLE GetFileHandle(const std::string& device_name);
+// Makes the OID query and returns a Win32 error code.
+int PerformQuery(HANDLE adapter_handle, std::vector<char>& buffer, DWORD* bytes_out);
+bool ResizeBuffer(size_t requested_size, std::vector<char>& buffer);
+// Gets the system directory and appends a trailing slash if not already
+// present.
+bool GetSystemDirectory(std::string* path);
+
+bool ConvertToAccessPointData(const NDIS_WLAN_BSSID& data, nsWifiAccessPoint* access_point_data);
+int GetDataFromBssIdList(const NDIS_802_11_BSSID_LIST& bss_id_list,
+ int list_size,
+ nsCOMArray<nsWifiAccessPoint>& outData);
+} // namespace
+
+class WindowsNdisApi
+{
+public:
+ virtual ~WindowsNdisApi();
+ static WindowsNdisApi* Create();
+ virtual bool GetAccessPointData(nsCOMArray<nsWifiAccessPoint>& outData);
+
+private:
+ static bool GetInterfacesNDIS(std::vector<std::string>& interface_service_names_out);
+ // Swaps in content of the vector passed
+ explicit WindowsNdisApi(std::vector<std::string>* interface_service_names);
+ bool GetInterfaceDataNDIS(HANDLE adapter_handle, nsCOMArray<nsWifiAccessPoint>& outData);
+ // NDIS variables.
+ std::vector<std::string> interface_service_names_;
+ std::vector<char> _buffer;
+};
+
+// WindowsNdisApi
+WindowsNdisApi::WindowsNdisApi(
+ std::vector<std::string>* interface_service_names)
+ : _buffer(kInitialBufferSize) {
+ interface_service_names_.swap(*interface_service_names);
+}
+
+WindowsNdisApi::~WindowsNdisApi() {
+}
+
+WindowsNdisApi* WindowsNdisApi::Create() {
+ std::vector<std::string> interface_service_names;
+ if (GetInterfacesNDIS(interface_service_names)) {
+ return new WindowsNdisApi(&interface_service_names);
+ }
+ return NULL;
+}
+
+bool WindowsNdisApi::GetAccessPointData(nsCOMArray<nsWifiAccessPoint>& outData) {
+ int interfaces_failed = 0;
+ int interfaces_succeeded = 0;
+
+ for (int i = 0; i < static_cast<int>(interface_service_names_.size()); ++i) {
+ // First, check that we have a DOS device for this adapter.
+ if (!DefineDosDeviceIfNotExists(interface_service_names_[i])) {
+ continue;
+ }
+
+ // Get the handle to the device. This will fail if the named device is not
+ // valid.
+ HANDLE adapter_handle = GetFileHandle(interface_service_names_[i]);
+ if (adapter_handle == INVALID_HANDLE_VALUE) {
+ continue;
+ }
+
+ // Get the data.
+ if (GetInterfaceDataNDIS(adapter_handle, outData)) {
+ ++interfaces_succeeded;
+ } else {
+ ++interfaces_failed;
+ }
+
+ // Clean up.
+ CloseHandle(adapter_handle);
+ UndefineDosDevice(interface_service_names_[i]);
+ }
+
+ // Return true if at least one interface succeeded, or at the very least none
+ // failed.
+ return interfaces_succeeded > 0 || interfaces_failed == 0;
+}
+
+bool WindowsNdisApi::GetInterfacesNDIS(std::vector<std::string>& interface_service_names_out) {
+ HKEY network_cards_key = NULL;
+ if (RegOpenKeyEx(
+ HKEY_LOCAL_MACHINE,
+ "Software\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards",
+ 0,
+ KEY_READ,
+ &network_cards_key) != ERROR_SUCCESS) {
+ return false;
+ }
+ if (!network_cards_key) {
+ return false;
+ }
+
+ for (int i = 0; ; ++i) {
+ TCHAR name[kStringLength];
+ DWORD name_size = kStringLength;
+ FILETIME time;
+ if (RegEnumKeyEx(network_cards_key,
+ i,
+ name,
+ &name_size,
+ NULL,
+ NULL,
+ NULL,
+ &time) != ERROR_SUCCESS) {
+ break;
+ }
+ HKEY hardware_key = NULL;
+ if (RegOpenKeyEx(network_cards_key, name, 0, KEY_READ, &hardware_key) !=
+ ERROR_SUCCESS) {
+ break;
+ }
+ if (!hardware_key) {
+ return false;
+ }
+
+ TCHAR service_name[kStringLength];
+ DWORD service_name_size = kStringLength;
+ DWORD type = 0;
+ if (RegQueryValueEx(hardware_key,
+ "ServiceName",
+ NULL,
+ &type,
+ reinterpret_cast<LPBYTE>(service_name),
+ &service_name_size) == ERROR_SUCCESS) {
+ interface_service_names_out.push_back(service_name);
+ }
+ RegCloseKey(hardware_key);
+ }
+
+ RegCloseKey(network_cards_key);
+ return true;
+}
+
+bool WindowsNdisApi::GetInterfaceDataNDIS(HANDLE adapter_handle,
+ nsCOMArray<nsWifiAccessPoint>& outData) {
+ DWORD bytes_out;
+ int result;
+
+ while (true) {
+ bytes_out = 0;
+ result = PerformQuery(adapter_handle, _buffer, &bytes_out);
+ if (result == ERROR_GEN_FAILURE || // Returned by some Intel cards.
+ result == ERROR_INSUFFICIENT_BUFFER ||
+ result == ERROR_MORE_DATA ||
+ result == NDIS_STATUS_INVALID_LENGTH ||
+ result == NDIS_STATUS_BUFFER_TOO_SHORT) {
+ // The buffer we supplied is too small, so increase it. bytes_out should
+ // provide the required buffer size, but this is not always the case.
+ size_t newSize;
+ if (bytes_out > static_cast<DWORD>(_buffer.size())) {
+ newSize = bytes_out;
+ } else {
+ newSize = _buffer.size() * 2;
+ }
+ if (!ResizeBuffer(newSize, _buffer)) {
+ return false;
+ }
+ } else {
+ // The buffer is not too small.
+ break;
+ }
+ }
+
+ if (result == ERROR_SUCCESS) {
+ NDIS_802_11_BSSID_LIST* bssid_list =
+ reinterpret_cast<NDIS_802_11_BSSID_LIST*>(&_buffer[0]);
+ GetDataFromBssIdList(*bssid_list, _buffer.size(), outData);
+ }
+
+ return true;
+}
+
+namespace {
+#define uint8 unsigned char
+
+bool ConvertToAccessPointData(const NDIS_WLAN_BSSID& data, nsWifiAccessPoint* access_point_data)
+{
+ access_point_data->setMac(data.MacAddress);
+ access_point_data->setSignal(data.Rssi);
+ // Note that _NDIS_802_11_SSID::Ssid::Ssid is not null-terminated.
+ const unsigned char* ssid = data.Ssid.Ssid;
+ size_t len = data.Ssid.SsidLength;
+ access_point_data->setSSID(reinterpret_cast<const char*>(ssid), len);
+ return true;
+}
+
+int GetDataFromBssIdList(const NDIS_802_11_BSSID_LIST& bss_id_list,
+ int list_size,
+ nsCOMArray<nsWifiAccessPoint>& outData)
+{
+ // Walk through the BSS IDs.
+ int found = 0;
+ const uint8* iterator = reinterpret_cast<const uint8*>(&bss_id_list.Bssid[0]);
+ const uint8* end_of_buffer =
+ reinterpret_cast<const uint8*>(&bss_id_list) + list_size;
+ for (int i = 0; i < static_cast<int>(bss_id_list.NumberOfItems); ++i) {
+ const NDIS_WLAN_BSSID *bss_id =
+ reinterpret_cast<const NDIS_WLAN_BSSID*>(iterator);
+ // Check that the length of this BSS ID is reasonable.
+ if (bss_id->Length < sizeof(NDIS_WLAN_BSSID) ||
+ iterator + bss_id->Length > end_of_buffer) {
+ break;
+ }
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ if (ConvertToAccessPointData(*bss_id, ap)) {
+ outData.AppendObject(ap);
+ ++found;
+ }
+ // Move to the next BSS ID.
+ iterator += bss_id->Length;
+ }
+ return found;
+}
+
+
+bool UndefineDosDevice(const std::string& device_name) {
+ // We remove only the mapping we use, that is \Device\<device_name>.
+ std::string target_path = "\\Device\\" + device_name;
+ return DefineDosDevice(
+ DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE,
+ device_name.c_str(),
+ target_path.c_str()) == TRUE;
+}
+
+bool DefineDosDeviceIfNotExists(const std::string& device_name) {
+ // We create a DOS device name for the device at \Device\<device_name>.
+ std::string target_path = "\\Device\\" + device_name;
+
+ TCHAR target[kStringLength];
+ if (QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
+ target_path.compare(target) == 0) {
+ // Device already exists.
+ return true;
+ }
+
+ if (GetLastError() != ERROR_FILE_NOT_FOUND) {
+ return false;
+ }
+
+ if (!DefineDosDevice(DDD_RAW_TARGET_PATH,
+ device_name.c_str(),
+ target_path.c_str())) {
+ return false;
+ }
+
+ // Check that the device is really there.
+ return QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
+ target_path.compare(target) == 0;
+}
+
+HANDLE GetFileHandle(const std::string& device_name) {
+ // We access a device with DOS path \Device\<device_name> at
+ // \\.\<device_name>.
+ std::string formatted_device_name = "\\\\.\\" + device_name;
+
+ return CreateFile(formatted_device_name.c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode
+ 0, // security attributes
+ OPEN_EXISTING,
+ 0, // flags and attributes
+ INVALID_HANDLE_VALUE);
+}
+
+int PerformQuery(HANDLE adapter_handle,
+ std::vector<char>& buffer,
+ DWORD* bytes_out) {
+ DWORD oid = OID_802_11_BSSID_LIST;
+ if (!DeviceIoControl(adapter_handle,
+ IOCTL_NDIS_QUERY_GLOBAL_STATS,
+ &oid,
+ sizeof(oid),
+ &buffer[0],
+ buffer.size(),
+ bytes_out,
+ NULL)) {
+ return GetLastError();
+ }
+ return ERROR_SUCCESS;
+}
+
+bool ResizeBuffer(size_t requested_size, std::vector<char>& buffer) {
+ if (requested_size > kMaximumBufferSize) {
+ buffer.resize(kInitialBufferSize);
+ return false;
+ }
+
+ buffer.resize(requested_size);
+ return true;
+}
+
+} // namespace
+
+
+nsresult
+WinXPWifiScanner::GetAccessPointsFromWLAN(nsCOMArray<nsWifiAccessPoint> &accessPoints)
+{
+ if (!mImplementation) {
+ mImplementation = WindowsNdisApi::Create();
+ if (!mImplementation) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ accessPoints.Clear();
+ bool isOk = mImplementation->GetAccessPointData(accessPoints);
+ if (!isOk) {
+ mImplementation = 0;
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/wifi/win_xp_wifiScanner.h b/netwerk/wifi/win_xp_wifiScanner.h
new file mode 100644
index 0000000000..33ae4cae4e
--- /dev/null
+++ b/netwerk/wifi/win_xp_wifiScanner.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 WINXPWIFISCANNER_H_
+#define WINXPWIFISCANNER_H_
+
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "win_wifiScanner.h"
+
+class nsWifiAccessPoint;
+class WindowsNdisApi;
+
+// This class is wrapper into the Chromium WindowNdisApi class for scanning wifis
+// on Windows XP. When Firefox drops XP support, this code can go.
+class WinXPWifiScanner : public WindowsWifiScannerInterface {
+public:
+ nsresult GetAccessPointsFromWLAN(nsCOMArray<nsWifiAccessPoint> &accessPoints);
+ virtual ~WinXPWifiScanner() {}
+private:
+ nsAutoPtr<WindowsNdisApi> mImplementation;
+};
+
+#endif \ No newline at end of file